How does calling Snackbar.make () work from a thread other than the UI?

I can call Snackbar.make() from the background thread without any problems. This is surprising to me, as I thought that UI operations are only allowed from the UI thread. But this is definitely not the case.

What exactly makes Snackbar.make() different? Why does this not cause exceptions, such as any other component of the user interface when it changes from the background thread?

+6
source share
3 answers

First of all: make() does not perform any UI-related operations, it just creates a new instance of Snackbar . This is a show() call that actually adds Snackbar to the view hierarchy and performs other dangerous UI tasks. However, you can do this safely from any thread, because it is implemented to schedule any show or hide operation in the user interface thread, regardless of which thread is called show() .

For a more detailed answer, let's take a closer look at the behavior in the Snackbar source code:


Start from the beginning, with your show() call:

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

As you can see, the show() call gets an instance of SnackbarManager , and then passes the duration and callback. SnackbarManager is a singleton. His class is dedicated to displaying, planning, and managing Snackbar . Now let's continue with the show() implementation on 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(); } } } 

Now this method call is a little more complicated. I will not explain in detail what is happening here, but in general, the synchronized block around this ensures the security of call flows on show() .

Inside the synchronized block, the manager will take care of dismissing Snackbars for duration or rescheduling updates if you show() the same twice and, of course, create a new Snackbars . For each Snackbar , a Snackbar is created that contains two parameters originally passed to the SnackbarManager , duration and callback:

 mNextSnackbar = new SnackbarRecord(duration, callback); 

In the above method, the call is made in the middle, in the else statement, if.

However, the only really important part - at least for this answer - is at the bottom below, the call to showNextSnackbarLocked() . This is where the magic happens, and the next Snackbar is in line - at least, sort of.

This is the source code of 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; } } } 

As you can see first, we check if Snackbar is running if mNextSnackbar not null. If this is not the case, we set SnackbarRecord as the current Snackbar and get the callback from the record. Now, something like a round happens after a trivial null check to check if the callback is valid, call show() in the callback, which is implemented in the Snackbar class, and not in the SnackbarManager , to actually show the Snackbar on the screen.

It may seem strange at first, but it makes a lot of sense. SnackbarManager simply responsible for tracking the status of Snackbars and coordinating them, no matter what Snackbar looks Snackbar , how it is displayed or what it is, it simply calls the show() method the right callback at the right time to tell Snackbar show itself.


Rewind for a moment, so far we have never left the background thread. The synchronized block in the show() SnackbarManager ensured that no other Thread can interfere with everything we did, but what event schedules and rejects on the main Thread are still missing. However, this will change right now when we look at the implementation of the callback in the Snackbar class:

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

Thus, in the callback, the message is sent to the static handler, either MSG_SHOW to show Snackbar or MSG_DISMISS to hide it again. Snackbar itself Snackbar attached to the message as a payload. Now we are almost done, as soon as we look at the declaration of this static handler:

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

Thus, this handler works in the user interface thread since it is created using the user interface looper (as indicated by Looper.getMainLooper() ). The message payload - Snackbar - is selected, and then, depending on the type of message, showView() or hideView() is Snackbar in Snackbar . Both of these methods are now executed in the user interface thread!

The implementation of both of them is rather complicated, so I will not go into details of what exactly happens in each of them. However, it should be obvious that these methods will take care of adding the View to the View hierarchy, animating it when it appears and disappears, dealing with CoordinatorLayout.Behaviours and other UI materials.

If you have other questions, feel free to ask.


Scrolling through my answer, I understand that this turned out to be a way longer than anticipated, however, when I see the source code like this, I cannot help myself! I hope you appreciate the long answer, or maybe I would spend a few minutes on my time!

+7
source

Snackbar.make completely safe from being Snackbar.make by a non-ui thread form. It uses a handler inside its manager, which works on the main looper thread and, thus, hides the calling form that lies at its base.

-one
source

Only the source stream that created the view hierarchy can touch its views.

If you use onPostExecute, you can access views

 protected void onPostExecute(Object object) { .. } 
-2
source

All Articles