如何从非UI线程调用Snackbar.make()?
我可以从后台线程调用,没有任何问题。这让我感到惊讶,因为我认为UI操作只允许从UI线程进行。但这里绝对不是这样。Snackbar.make()
究竟是什么让与众不同?为什么当您从后台线程修改它时,这不会像任何其他UI组件一样导致异常?Snackbar.make()
我可以从后台线程调用,没有任何问题。这让我感到惊讶,因为我认为UI操作只允许从UI线程进行。但这里绝对不是这样。Snackbar.make()
究竟是什么让与众不同?为什么当您从后台线程修改它时,这不会像任何其他UI组件一样导致异常?Snackbar.make()
首先:不执行任何与UI相关的操作,它只是创建一个新实例。它是实际上将 添加到视图层次结构并执行其他危险的 UI 相关任务的调用。但是,您可以从任何线程安全地执行此操作,因为它被实现为在UI线程上计划任何显示或隐藏操作,而不管哪个线程调用。make()
Snackbar
show()
Snackbar
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();
}
}
}
现在,此方法调用稍微复杂一些。我不打算详细解释这里发生了什么,但总的来说,围绕它的块确保了对 的调用的线程安全。synchronized
show()
在块内,经理负责关闭当前显示的更新持续时间或重新安排,如果你相同的两次,当然创建新的。对于每个 a,都包含最初传递给 的两个参数,持续时间和回调:synchronized
Snackbars
show()
Snackbars
Snackbar
SnackbarRecord
SnackbarManager
mNextSnackbar = new SnackbarRecord(duration, callback);
在上面的方法调用中,这发生在中间,在第一个if的else语句中。
然而,唯一真正重要的部分 - 至少对于这个答案 - 就在底部,对.在这里,魔术发生了,下一个小吃店排队 - 至少是某种程度上。showNextSnackbarLocked()
这是以下各项的源代码: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
让我们倒带一会儿,到目前为止,我们从未离开过后台线程。该块在方法中确保没有其他可以干扰我们所做的一切,但是安排节目和关闭主要事件的内容仍然缺失。然而,当我们查看类中回调的实现时,这种情况现在将发生变化:synchronized
show()
SnackbarManager
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
Snackbar
MSG_DISMISS
Snackbar
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;
}
});
}
因此,此处理程序在 UI 线程上运行,因为它是使用 UI 循环器创建的(如 所示)。消息的有效负载 - the - 被强制转换,然后根据消息的类型或在 上调用。这两种方法现在都在 UI 线程上执行!Looper.getMainLooper()
Snackbar
showView()
hideView()
Snackbar
这两者的实现有点复杂,所以我不会详细介绍它们中究竟发生了什么。但是,很明显,这些方法负责将 添加到视图层次结构中,在视图层次结构出现和消失时对其进行动画处理,处理有关 UI 的其他内容。View
CoordinatorLayout.Behaviours
如果您有任何其他问题,请随时提问。
滚动浏览我的答案,我意识到这比预期的要长得多,但是当我看到这样的源代码时,我无法控制自己!我希望你能欣赏一个很长的深度答案,或者也许我只是浪费了几分钟的时间!
Snackbar.make
是完全安全的,不会被称为表单非 ui 线程。它在其管理器中使用一个处理程序,该处理程序在主循环器线程上运行,从而隐藏调用者形成其底层的复杂性。