6

Tôi có thể gọi Snackbar.make() từ chủ đề nền mà không gặp bất kỳ sự cố nào. Điều này gây ngạc nhiên cho tôi vì tôi cho rằng các hoạt động giao diện người dùng chỉ được phép từ chuỗi giao diện người dùng. Nhưng đó chắc chắn không phải là trường hợp ở đây.Làm thế nào để gọi Snackbar.make() từ công việc không phải UI?

Chính xác điều gì làm cho Snackbar.make() khác biệt? Tại sao điều này không gây ra ngoại lệ giống như bất kỳ thành phần giao diện người dùng nào khác khi bạn sửa đổi nó từ một chuỗi nền?

Trả lời

7

Trước hết: make() không thực hiện bất kỳ hoạt động nào liên quan đến giao diện người dùng, nó chỉ tạo một phiên bản Snackbar mới. Đó là cuộc gọi tới show() thực sự thêm Snackbar vào hệ thống phân cấp chế độ xem và thực hiện các tác vụ liên quan đến giao diện người dùng nguy hiểm khác. Tuy nhiên, bạn có thể làm điều đó một cách an toàn từ bất kỳ chuỗi nào vì nó được triển khai để lên lịch cho bất kỳ hoạt động hiển thị hoặc ẩn nào trên chuỗi giao diện người dùng bất kể luồng nào được gọi là show().

Đối với một câu trả lời chi tiết hơn chúng ta hãy xem xét kỹ hơn các hành vi trong mã nguồn của Snackbar:


Hãy bắt đầu từ đâu tất cả bắt đầu, với cuộc gọi của bạn để show():

public void show() { 
    SnackbarManager.getInstance().show(mDuration, mManagerCallback); 
} 

Như bạn có thể thấy cuộc gọi đến show() nhận một phiên bản của SnackbarManager và sau đó chuyển thời lượng và gọi lại cho nó. SnackbarManager là một singleton. Lớp học của nó sẽ chăm sóc hiển thị, lên lịch và quản lý một Snackbar. Bây giờ cho phép tiếp tục thực hiện các show() trên SnackbarManager:

public void show(int duration, Callback callback) { 
    synchronized (mLock) { 
     if (isCurrentSnackbarLocked(callback)) { 
      // Means that the callback is already in the queue. We'll just update the duration 
      mCurrentSnackbar.duration = duration; 

      // If this is the Snackbar currently being shown, call re-schedule it's 
      // timeout 
      mHandler.removeCallbacksAndMessages(mCurrentSnackbar); 
      scheduleTimeoutLocked(mCurrentSnackbar); 
      return; 
     } else if (isNextSnackbarLocked(callback)) { 
      // We'll just update the duration 
      mNextSnackbar.duration = duration; 
     } else { 
      // Else, we need to create a new record and queue it 
      mNextSnackbar = new SnackbarRecord(duration, callback); 
     } 

     if (mCurrentSnackbar != null && cancelSnackbarLocked(mCurrentSnackbar, 
       Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE)) { 
      // If we currently have a Snackbar, try and cancel it and wait in line 
      return; 
     } else { 
      // Clear out the current snackbar 
      mCurrentSnackbar = null; 
      // Otherwise, just show it now 
      showNextSnackbarLocked(); 
     } 
    } 
} 

Bây giờ cuộc gọi phương pháp này là phức tạp hơn một chút. Tôi sẽ không giải thích chi tiết những gì đang xảy ra ở đây, nhưng nói chung khối synchronized xung quanh điều này đảm bảo an toàn chủ đề của các cuộc gọi đến show().

Bên trong khối synchronized người quản lý sẽ chăm sóc của bác bỏ hiện thấy Snackbars cập nhật thời hạn hoặc gia hạn nếu bạn show() cùng một hai lần và tất nhiên tạo mới Snackbars. Đối với mỗi Snackbar một SnackbarRecord được tạo ra, trong đó có hai tham số ban đầu truyền cho SnackbarManager, thời gian và gọi lại:

mNextSnackbar = new SnackbarRecord(duration, callback); 

Trong cuộc gọi phương pháp trên điều này xảy ra ở giữa, trong các báo cáo khác của người đầu tiên nếu .

Tuy nhiên, phần thực sự quan trọng - ít nhất là cho câu trả lời này - nằm ngay ở dưới cùng, gọi tới số showNextSnackbarLocked().Điều này xảy ra sự kỳ diệu và Snackbar tiếp theo được xếp hàng - ít nhất là loại.

Đây là mã nguồn của showNextSnackbarLocked():

private void showNextSnackbarLocked() { 
    if (mNextSnackbar != null) { 
     mCurrentSnackbar = mNextSnackbar; 
     mNextSnackbar = null; 

     final Callback callback = mCurrentSnackbar.callback.get(); 
     if (callback != null) { 
      callback.show(); 
     } else { 
      // The callback doesn't exist any more, clear out the Snackbar 
      mCurrentSnackbar = null; 
     } 
    } 
} 

Như bạn có thể nhìn thấy đầu tiên chúng ta kiểm tra xem một Snackbar được xếp hàng đợi bằng cách kiểm tra nếu mNextSnackbar không phải là null. Nếu không, chúng tôi đặt SnackbarRecordSnackbar hiện tại và truy xuất cuộc gọi lại từ bản ghi. Bây giờ một cái gì đó vòng quanh xảy ra, sau khi một kiểm tra null tầm thường để xem nếu gọi lại là hợp lệ chúng tôi gọi show() trên gọi lại, được thực hiện trong lớp Snackbar - không phải trong SnackbarManager - để thực sự hiển thị Snackbar trên màn hình.

Lúc đầu, điều này có vẻ lạ, tuy nhiên nó có ý nghĩa rất nhiều. SnackbarManager chỉ chịu trách nhiệm theo dõi trạng thái của Snackbars và điều phối chúng, không quan tâm đến cách hiển thị Snackbar, cách hiển thị hoặc thậm chí là gì, nó chỉ gọi phương thức show() trên gọi lại đúng vào đúng thời điểm báo cho số Snackbar để hiển thị chính nó.


Hãy tua lại một lúc, cho đến bây giờ chúng tôi không bao giờ rời chuỗi nền. Các synchronized khối trong phương pháp show() của SnackbarManager đảm bảo rằng không có khác Thread có thể can thiệp vào tất cả mọi thứ chúng tôi đã làm, nhưng những gì lịch lãm, miễn nhiệm các sự kiện trên chính Thread vẫn còn thiếu. Tuy nhiên điều đó sẽ thay đổi ngay bây giờ khi chúng ta nhìn vào thực hiện các cuộc gọi lại trong lớp Snackbar:

private final SnackbarManager.Callback mManagerCallback = new SnackbarManager.Callback() { 
    @Override 
    public void show() { 
     sHandler.sendMessage(sHandler.obtainMessage(MSG_SHOW, Snackbar.this)); 
    } 

    @Override 
    public void dismiss(int event) { 
     sHandler.sendMessage(sHandler.obtainMessage(MSG_DISMISS, event, 0, Snackbar.this)); 
    } 
}; 

Vì vậy, trong khi gọi lại một thông điệp được gửi đến một handler tĩnh, hoặc MSG_SHOW để hiển thị các Snackbar hoặc MSG_DISMISS để ẩn nó một lần nữa. Bản thân số Snackbar được đính kèm với thông báo dưới dạng tải trọng. Bây giờ chúng ta đang gần hoàn tất ngay khi chúng tôi nhìn vào tờ khai đó handler tĩnh:

private static final Handler sHandler; 
private static final int MSG_SHOW = 0; 
private static final int MSG_DISMISS = 1; 

static { 
    sHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() { 
     @Override 
     public boolean handleMessage(Message message) { 
      switch (message.what) { 
       case MSG_SHOW: 
        ((Snackbar) message.obj).showView(); 
        return true; 
       case MSG_DISMISS: 
        ((Snackbar) message.obj).hideView(message.arg1); 
        return true; 
      } 
      return false; 
     } 
    }); 
} 

Vì vậy, xử lý này chạy trên thread UI vì nó được tạo ra bằng cách sử dụng looper UI (như được chỉ ra bởi Looper.getMainLooper()). Tải trọng của tin nhắn - Snackbar - được đúc và sau đó tùy thuộc vào loại tin nhắn hoặc showView() hoặc hideView() được gọi trên Snackbar. Cả hai phương pháp này hiện được thực hiện trên luồng giao diện người dùng!

Việc triển khai cả hai loại này phức tạp, vì vậy tôi sẽ không đi sâu vào chi tiết về những gì xảy ra chính xác trong mỗi trường hợp. Tuy nhiên, rõ ràng là các phương pháp này quan tâm đến việc thêm View vào phân cấp chế độ xem, làm động nó khi nó xuất hiện và biến mất, xử lý CoordinatorLayout.Behaviours và các nội dung khác liên quan đến giao diện người dùng.

Nếu bạn có bất kỳ câu hỏi nào khác, vui lòng hỏi.


cuộn qua câu trả lời của tôi, tôi nhận ra rằng đây hóa ra cách dài hơn nó được cho là được, tuy nhiên khi tôi nhìn thấy mã nguồn như thế này tôi không thể giúp bản thân mình! Tôi hy vọng bạn đánh giá cao câu trả lời sâu sắc, hoặc có lẽ tôi có thể đã lãng phí một vài phút thời gian của tôi!

+0

Không, bạn không lãng phí thời gian của bạn, nhiều đánh giá cao :) – q126y

-2

Chỉ chuỗi ban đầu đã tạo phân cấp chế độ xem mới có thể chạm vào chế độ xem của nó.

Nếu bạn sử dụng onPostExecute bạn sẽ có thể truy cập vào các quan điểm

protected void onPostExecute(Object object) { .. } 
+0

Bạn hoàn toàn thiếu điểm của câu hỏi. –

-1

Snackbar.make là hoàn toàn an toàn khỏi bị gọi là hình thức chủ đề phi ui. Nó sử dụng một trình xử lý bên trong trình quản lý của nó, hoạt động trên luồng looper chính và do đó ẩn dạng người gọi là các phức tạp bên dưới của nó.

+0

'Người xử lý' trong người quản lý không tham gia vào việc này. Nó chỉ là sử dụng để thông báo 'SnackbarRecords' của thời gian chờ. Có một 'Handler' riêng biệt trong lớp 'Snackbar' thực sự quan tâm đến việc phô diễn hoặc che giấu' Snackbar'. –

+0

vẫn không giảm xuống 2 phiếu bầu. Snackbar là hình thức hoàn toàn an toàn được gọi từ bất kỳ chủ đề nào. Vấn đề là nó sử dụng một trình xử lý trên looper chính để thực hiện công việc của mình. Đó là những gì chủ sở hữu câu hỏi ban đầu cần phải hiểu. Tôi không thể loại bỏ những phiếu bầu xuống vì vậy tôi cũng không thể chống lại họ. Th – Nazgul

+0

Không có cảm giác khó khăn, tôi đã không downvote:/ –

Các vấn đề liên quan