q12*_*26y 11 java user-interface multithreading android android-snackbar
我可以Snackbar.make()从后台线程调用没有任何问题.这对我来说是令人惊讶的,因为我认为UI操作只允许来自UI线程.但这绝对不是这种情况.
究竟有什么Snackbar.make()不同?当您从后台线程修改它时,为什么这不会导致像任何其他UI组件一样的异常?
Xav*_*ler 13
首先:make()不执行任何与UI相关的操作,它只是创建一个新Snackbar实例.show()实际上是将调用添加Snackbar到视图层次结构并执行其他危险的UI相关任务的调用.但是,您可以从任何线程安全地执行此操作,因为它实现为在UI线程上安排任何显示或隐藏操作,而不管调用哪个线程show().
有关更详细的答案,让我们仔细看看源代码中的行为Snackbar:
让我们从一切开始,打电话给show():
public void show() {
SnackbarManager.getInstance().show(mDuration, mManagerCallback);
}
Run Code Online (Sandbox Code Playgroud)
正如你可以看到调用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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
现在这个方法调用有点复杂.我不打算详细解释这里发生了什么,但总的来说,synchronized围绕它的块可以确保调用的线程安全性show().
在synchronized块内,经理负责解雇当前显示的Snackbars更新持续时间或重新安排,如果你show()是同一个两次,当然创建新的Snackbars.对于每个创建的Snackbara SnackbarRecord,其中包含最初传递给的两个参数SnackbarManager,持续时间和回调:
mNextSnackbar = new SnackbarRecord(duration, callback);
Run Code Online (Sandbox Code Playgroud)
在上面的方法调用中,这发生在中间,在第一个if的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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,我们首先通过检查Snackbar是否mNextSnackbar为空来检查Snackbar是否排队.如果不是,我们将其设置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));
}
};
Run Code Online (Sandbox Code Playgroud)
因此,在回调中,将消息发送到静态处理程序,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;
}
});
}
Run Code Online (Sandbox Code Playgroud)
所以这个处理程序在UI线程上运行,因为它是使用UI looper创建的(如图所示Looper.getMainLooper()).消息的有效负载 - 是Snackbar- 然后根据消息的类型showView()或者在消息hideView()上调用Snackbar.这两种方法现在都在UI线程上执行!
这两者的实现都很复杂,所以我不会详细说明每一个中究竟发生了什么.然而,显而易见的是,这些方法负责View将视图层次结构添加到视图层次结构中,在它出现和消失时设置动画,处理CoordinatorLayout.Behaviours关于UI的其他内容.
如果您有任何其他问题,请随时提出.
通过我的回答滚动我意识到这竟然方式长于它应该是,但是当我看到这样的源代码,我不能帮助自己!我希望你能够深入了解一个深入的答案,或者我可能只是浪费了几分钟的时间!