java - 从非 UI 线程调用 Snackbar.make() 是如何工作的?

标签 java android multithreading user-interface android-snackbar

我可以打电话Snackbar.make()从后台线程没有任何问题。这让我感到惊讶,因为我认为 UI 操作只能从 UI 线程进行。但这里绝对不是这种情况。

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

最佳答案

首先:make()不执行任何 UI 相关的操作,它只是创建一个新的 Snackbar实例。这是给show()的电话这实际上添加了 Snackbar到 View 层次结构并执行其他危险的 UI 相关任务。但是,您可以从任何线程安全地执行此操作,因为它被实现为在 UI 线程上调度任何显示或隐藏操作,而不管哪个线程调用 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() 的线程安全.

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并从记录中检索回调。现在发生了一些事情,在进行简单的空检查以查看回调是否有效之后,我们调用 show()在回调上,这是在 Snackbar 中实现的类 - 不在 SnackbarManager - 实际显示 Snackbar屏幕上。

乍一看,这可能看起来很奇怪,但这很有意义。 SnackbarManager只负责跟踪Snackbars的状态并协调它们,它不在乎如何Snackbar看起来,它是如何显示的,甚至是什么,它只是调用 show()方法在正确的时间回调告诉Snackbar来展示自己。

让我们回顾一下,直到现在我们从未离开过后台线程。 synchronized阻止在 show() SnackbarManager的方法确保没有其他 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显示 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;
        }
    });
}

所以这个处理程序在 UI 线程上运行,因为它是使用 UI looper 创建的(如 Looper.getMainLooper() 所示)。消息的有效载荷 - Snackbar - 被强制转换,然后取决于消息的类型 showView()hideView()Snackbar 上被调用. 这两个方法现在都在 UI 线程上执行!

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

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

滚动浏览我的答案,我意识到结果是 方式比预期的要长,但是当我看到这样的源代码时,我情不自禁!我希望你能欣赏一个长而深入的回答,或者我可能只是浪费了几分钟的时间!

关于java - 从非 UI 线程调用 Snackbar.make() 是如何工作的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37510565/

相关文章:

python - 多处理的queue.get()什么时候返回DONE?

C++ 多线程 block 主线程

java - 如何与大量的数字数组?

安卓 : upload Image and JSON using MultiPartEntityBuilder

android - 在android中每1/4秒获取音频文件的频率

android - 以编程方式在android中执行Touch事件

multithreading - 多核系统上的并行问题之外的线程有什么用?

java - 在 java 中使用运算符 "&"按位与

java - 在 ovverriden 方法中使用多态泛型

Java 线程 - 获取 ServerSocket 输入来更新 Swing