如何从非UI线程调用Snackbar.make()?

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以显示SnackbarMSG_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的其他内容.

如果您有任何其他问题,请随时提出.


通过我的回答滚动我意识到这竟然方式长于它应该是,但是当我看到这样的源代码,我不能帮助自己!我希望你能够深入了解一个深入的答案,或者我可能只是浪费了几分钟的时间!