从非 UI 线程调用 Snackbar.make() 如何工作?
How does calling Snackbar.make() from non-UI thread work?
我可以毫无问题地从后台线程调用 Snackbar.make()
。这让我感到惊讶,因为我认为 UI 操作只允许从 UI 线程进行。但这里绝对不是这种情况。
究竟是什么让 Snackbar.make()
与众不同?当您从后台线程修改它时,为什么这不会像任何其他 UI 组件一样导致异常?
只有创建视图层次结构的原始线程才能触及它的视图。
如果您使用 onPostExecute,您将能够访问视图
protected void onPostExecute(Object object) { .. }
Snackbar.make
是完全安全的,不会被非 ui 线程调用。它在其管理器内部使用一个处理程序,该处理程序在主循环线程上运行,因此隐藏了调用者的底层复杂性。
首先:make()
不执行任何UI相关操作,它只是创建一个新的Snackbar
实例。它是对 show()
的调用,它实际上将 Snackbar
添加到视图层次结构并执行其他危险的 UI 相关任务。但是,您可以从任何线程安全地执行此操作,因为它被实现为在 UI 线程上安排任何显示或隐藏操作,而不管哪个线程调用 show()
.
为了获得更详细的答案,让我们仔细看看 Snackbar
:
的源代码中的行为
让我们从一切开始的地方开始,请拨打 show()
:
public void show() {
SnackbarManager.getInstance().show(mDuration, mManagerCallback);
}
如您所见,对 show()
的调用获取 SnackbarManager
的实例,然后将持续时间和回调传递给它。 SnackbarManager
是单例。 class 负责显示、安排和管理 Snackbar
。现在让我们继续在 SnackbarManager
:
上执行 show()
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()
的线程安全。
在 synchronized
块内,经理负责关闭当前显示的 Snackbars
更新持续时间或重新安排,如果你 show()
相同的两次,当然会创建新的 Snackbars
.对于每个 Snackbar
,都会创建一个 SnackbarRecord
,其中包含最初传递给 SnackbarManager
的两个参数、持续时间和回调:
mNextSnackbar = new SnackbarRecord(duration, callback);
在上面的方法调用中,这发生在中间,在第一个 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;
}
}
}
正如您首先看到的,我们通过检查 mNextSnackbar
是否不为空来检查 Snackbar 是否已排队。如果不是,我们将 SnackbarRecord
设置为当前 Snackbar
并从记录中检索回调。现在发生了一些迂回的事情,在一个简单的 null 检查以查看回调是否有效之后,我们在回调上调用 show()
,它是在 Snackbar
class 中实现的——而不是在SnackbarManager
- 在屏幕上实际显示 Snackbar
。
乍一看这似乎很奇怪,但它很有意义。 SnackbarManager
只负责跟踪 Snackbars
的状态并协调它们,它不关心 Snackbar
的外观,显示方式甚至是什么,它只是调用show()
方法在正确的时刻正确的回调告诉 Snackbar
显示自己。
让我们回顾一下,直到现在我们都没有离开过后台线程。 SnackbarManager
的 show()
方法中的 synchronized
块确保没有其他 Thread
可以干扰我们所做的一切,但是什么安排了主要 Thread
仍然失踪。然而,当我们查看 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));
}
};
因此在回调中,一条消息被发送到静态处理程序,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 循环器创建的(如 Looper.getMainLooper()
所示)。消息的有效负载 - Snackbar
- 被转换,然后根据消息的类型在 Snackbar
上调用 showView()
或 hideView()
。 这两个方法现在都在UI线程上执行!
这两个的实现有点复杂,所以我不会详细说明它们各自到底发生了什么。然而,很明显,这些方法负责将 View
添加到视图层次结构,在它出现和消失时对其进行动画处理,处理 CoordinatorLayout.Behaviours
和其他有关 UI 的内容。
如果您有任何其他问题,请随时提出。
翻阅我的答案后,我发现结果 比预期的要长,但是当我看到这样的源代码时,我情不自禁!我希望你能理解一个很长很深入的答案,或者我可能只是浪费了几分钟的时间!
我可以毫无问题地从后台线程调用 Snackbar.make()
。这让我感到惊讶,因为我认为 UI 操作只允许从 UI 线程进行。但这里绝对不是这种情况。
究竟是什么让 Snackbar.make()
与众不同?当您从后台线程修改它时,为什么这不会像任何其他 UI 组件一样导致异常?
只有创建视图层次结构的原始线程才能触及它的视图。
如果您使用 onPostExecute,您将能够访问视图
protected void onPostExecute(Object object) { .. }
Snackbar.make
是完全安全的,不会被非 ui 线程调用。它在其管理器内部使用一个处理程序,该处理程序在主循环线程上运行,因此隐藏了调用者的底层复杂性。
首先:make()
不执行任何UI相关操作,它只是创建一个新的Snackbar
实例。它是对 show()
的调用,它实际上将 Snackbar
添加到视图层次结构并执行其他危险的 UI 相关任务。但是,您可以从任何线程安全地执行此操作,因为它被实现为在 UI 线程上安排任何显示或隐藏操作,而不管哪个线程调用 show()
.
为了获得更详细的答案,让我们仔细看看 Snackbar
:
让我们从一切开始的地方开始,请拨打 show()
:
public void show() {
SnackbarManager.getInstance().show(mDuration, mManagerCallback);
}
如您所见,对 show()
的调用获取 SnackbarManager
的实例,然后将持续时间和回调传递给它。 SnackbarManager
是单例。 class 负责显示、安排和管理 Snackbar
。现在让我们继续在 SnackbarManager
:
show()
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()
的线程安全。
在 synchronized
块内,经理负责关闭当前显示的 Snackbars
更新持续时间或重新安排,如果你 show()
相同的两次,当然会创建新的 Snackbars
.对于每个 Snackbar
,都会创建一个 SnackbarRecord
,其中包含最初传递给 SnackbarManager
的两个参数、持续时间和回调:
mNextSnackbar = new SnackbarRecord(duration, callback);
在上面的方法调用中,这发生在中间,在第一个 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;
}
}
}
正如您首先看到的,我们通过检查 mNextSnackbar
是否不为空来检查 Snackbar 是否已排队。如果不是,我们将 SnackbarRecord
设置为当前 Snackbar
并从记录中检索回调。现在发生了一些迂回的事情,在一个简单的 null 检查以查看回调是否有效之后,我们在回调上调用 show()
,它是在 Snackbar
class 中实现的——而不是在SnackbarManager
- 在屏幕上实际显示 Snackbar
。
乍一看这似乎很奇怪,但它很有意义。 SnackbarManager
只负责跟踪 Snackbars
的状态并协调它们,它不关心 Snackbar
的外观,显示方式甚至是什么,它只是调用show()
方法在正确的时刻正确的回调告诉 Snackbar
显示自己。
让我们回顾一下,直到现在我们都没有离开过后台线程。 SnackbarManager
的 show()
方法中的 synchronized
块确保没有其他 Thread
可以干扰我们所做的一切,但是什么安排了主要 Thread
仍然失踪。然而,当我们查看 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));
}
};
因此在回调中,一条消息被发送到静态处理程序,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 循环器创建的(如 Looper.getMainLooper()
所示)。消息的有效负载 - Snackbar
- 被转换,然后根据消息的类型在 Snackbar
上调用 showView()
或 hideView()
。 这两个方法现在都在UI线程上执行!
这两个的实现有点复杂,所以我不会详细说明它们各自到底发生了什么。然而,很明显,这些方法负责将 View
添加到视图层次结构,在它出现和消失时对其进行动画处理,处理 CoordinatorLayout.Behaviours
和其他有关 UI 的内容。
如果您有任何其他问题,请随时提出。
翻阅我的答案后,我发现结果 比预期的要长,但是当我看到这样的源代码时,我情不自禁!我希望你能理解一个很长很深入的答案,或者我可能只是浪费了几分钟的时间!