6

我可以從後臺線程調用Snackbar.make()沒有任何問題。這讓我很驚訝,因爲我認爲UI操作只能從UI線程中獲得。但是這絕對不是這種情況。如何從非UI線程調用Snackbar.make()?

究竟是什麼使Snackbar.make()與衆不同?爲什麼當你從後臺線程修改它時,這不會導致像任何其他UI組件那樣的異常?

回答

7

首先:make()不執行任何與UI相關的操作,它只是創建一個新的Snackbar實例。這是對show()的調用,它實際上將Snackbar添加到視圖層次結構中,並執行其他危險的與UI相關的任務。但是,您可以從任何線程安全地執行此操作,因爲它可以在UI線程上安排任何顯示或隱藏操作,而不管哪個線程調用show()

如需更詳細的解答,讓我們來仔細看看行爲在Snackbar的源代碼:


讓我們開始在這一切的開始,用您的來電show()

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

正如你所看到的對show()的呼叫得到SnackbarManager的實例,然後將持續時間和回調傳遞給它。 SnackbarManager是一個單身人士。它負責顯示,安排和管理Snackbar。現在,讓我們繼續與show()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(); 
     } 
    } 
} 

現在這個方法的調用是一個稍微複雜一點。我不打算詳細解釋這裏發生了什麼事情,但總體來說,​​塊可以確保線程安全地撥打show()

裏面的​​塊經理採取解僱當前顯示Snackbars更新持續時間,或者如果你show()同一個兩次,當然創造新Snackbars重新安排照顧。對於每個Snackbar一個SnackbarRecord創建其中包含兩個參數最初傳遞給SnackbarManager,持續時間和回調:

mNextSnackbar = new SnackbarRecord(duration, callback); 

在上述方法中調用這種情況發生在中間,在第一如果else語句。

但是,唯一真正重要的部分 - 至少對於這個答案 - 是在底部,showNextSnackbarLocked()的呼叫。這就是魔術發生的地方,下一個Snackbar正在排隊 - 至少有點類似。

這是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; 
     } 
    } 
} 

正如你可以看到我們首先檢查是否有小吃吧是通過檢查mNextSnackbar不爲空排隊。如果不是,我們將SnackbarRecord設置爲當前的Snackbar,並從記錄中檢索回調。現在發生了一些類似的事情,經過一個微不足道的空檢查,看看回調是否有效,我們在回調中調用show(),該回調在Snackbar類中實現 - 而不是在SnackbarManager中 - 實際在屏幕上顯示Snackbar

起初這可能看起來很奇怪,但它很有意義。 SnackbarManager只是負責跟蹤Snackbars的狀態並協調它們,它並不關心Snackbar的外觀,它是如何顯示的或它甚至是什麼,它只是在正確的時刻將右邊的回調調用show()方法告訴Snackbar顯示自己。


讓我們回憶一下,直到現在我們從未離開過後臺線程。 SnackbarManagershow()方法中的​​塊確保沒有其他Thread會干擾我們所做的所有事情,但主要Thread上的顯示和解除事件的安排仍然缺失。這不過是要在回調中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)); 
    } 
}; 

所以在回調的消息被髮送到一個靜態的處理器,無論是MSG_SHOW展現SnackbarMSG_DISMISS再次隱藏它。 Snackbar本身作爲有效負載附加到消息上。現在,我們幾乎只要我們看看靜態處理的聲明做:因爲它使用的是UI尺蠖(由Looper.getMainLooper()所示)創建的UI線程上

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; 
     } 
    }); 
} 

所以這個處理程序運行。消息的有效負載 - Snackbar - 已被播出,然後根據消息的類型在Snackbar上調用showView()hideView()這兩種方法現在都在UI線程上執行!

這兩種方法的實現都很複雜,所以我不會詳細討論每個方案中發生的事情。然而,很明顯,這些方法可以將View添加到視圖層次結構中,在它出現和消失時對其進行動畫處理,處理CoordinatorLayout.Behaviours以及其他有關UI的內容。

如果您有任何其他問題隨時問。


滾動,我的答案,我意識到這竟然方式超過它應該是,但是當我看到這樣的源代碼,我不能幫助自己!我希望你能夠深深地感謝你,或者我可能浪費了幾分鐘的時間!

+0

不,你沒有浪費你的時間,非常感謝:) – q126y

-2

只有創建視圖層次結構的原始線程才能觸及其視圖。

如果使用onPostExecute你就可以訪問視圖

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

您完全忽略了這個問題的關鍵。 –

-1

Snackbar.make是被稱爲形式的非UI線程完全安全的。它在其管理器內部使用了一個處理器,該處理器在主循環線程上運行,從而隱藏了調用者形式的底層複雜性。

+0

管理員中的「處理程序」不起作用。它只是用於通知超時的SnackbarRecords。在Snackbar類中有一個單獨的'Handler',它實際上負責顯示或隱藏'Sn​​ackbar'。 –

+0

這仍然不等於2票。 Snackbar是從任何線程調用的完全安全的形式。重點在於它使用主循環中的處理程序來完成其工作。這就是原始問題所有者需要理解的內容。我不能解僱這些倒票,所以我也不會去打他們。 Th – Nazgul

+0

沒有硬的感覺,我沒有downvote:/ –