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)) {
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 {
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!