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

我可以从后台线程调用,没有任何问题。这让我感到惊讶,因为我认为UI操作只允许从UI线程进行。但这里绝对不是这样。Snackbar.make()

究竟是什么让与众不同?为什么当您从后台线程修改它时,这不会像任何其他UI组件一样导致异常?Snackbar.make()


答案 1

首先:不执行任何与UI相关的操作,它只是创建一个新实例。它是实际上将 添加到视图层次结构并执行其他危险的 UI 相关任务的调用。但是,您可以从任何线程安全地执行此操作,因为它被实现为在UI线程上计划任何显示或隐藏操作,而不管哪个线程调用。make()Snackbarshow()Snackbarshow()

有关更详细的答案,让我们仔细看看源代码中的行为:Snackbar


让我们从一切开始的地方开始,调用:show()

public void show() {
    SnackbarManager.getInstance().show(mDuration, mManagerCallback);
}

如您所见,调用以获取 的实例,然后传递持续时间和回调。是单例。它是负责显示,调度和管理.现在让我们继续在 上实现 :show()SnackbarManagerSnackbarManagerSnackbarshow()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();
        }
    }
}

现在,此方法调用稍微复杂一些。我不打算详细解释这里发生了什么,但总的来说,围绕它的块确保了对 的调用的线程安全。synchronizedshow()

在块内,经理负责关闭当前显示的更新持续时间或重新安排,如果你相同的两次,当然创建新的。对于每个 a,都包含最初传递给 的两个参数,持续时间和回调:synchronizedSnackbarsshow()SnackbarsSnackbarSnackbarRecordSnackbarManager

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;
        }
    }
}

正如您首先看到的,我们通过检查是否不为空来检查小吃店是否排队。如果不是,我们将 设置为当前,并从记录中检索回调。现在发生了一些回合,在进行一个简单的空检查以查看回调是否有效之后,我们调用回调,该回调在类中实现 - 而不是在 - 中实际显示在屏幕上。mNextSnackbarSnackbarRecordSnackbarshow()SnackbarSnackbarManagerSnackbar

起初,这可能看起来很奇怪,但它很有道理。它只是负责跟踪和协调它们的状态,它不关心外观如何,如何显示甚至是什么,它只是在正确的时刻调用正确的回调方法来告诉自己显示自己。SnackbarManagerSnackbarsSnackbarshow()Snackbar


让我们倒带一会儿,到目前为止,我们从未离开过后台线程。该块在方法中确保没有其他可以干扰我们所做的一切,但是安排节目和关闭主要事件的内容仍然缺失。然而,当我们查看类中回调的实现时,这种情况现在将发生变化:synchronizedshow()SnackbarManagerThreadThreadSnackbar

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_SHOWSnackbarMSG_DISMISSSnackbar

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()SnackbarshowView()hideView()Snackbar

这两者的实现有点复杂,所以我不会详细介绍它们中究竟发生了什么。但是,很明显,这些方法负责将 添加到视图层次结构中,在视图层次结构出现和消失时对其进行动画处理,处理有关 UI 的其他内容。ViewCoordinatorLayout.Behaviours

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


滚动浏览我的答案,我意识到这比预期的要长得多,但是当我看到这样的源代码时,我无法控制自己!我希望你能欣赏一个很长的深度答案,或者也许我只是浪费了几分钟的时间!


答案 2

Snackbar.make是完全安全的,不会被称为表单非 ui 线程。它在其管理器中使用一个处理程序,该处理程序在主循环器线程上运行,从而隐藏调用者形成其底层的复杂性。


推荐