一人一猫旅行记之Handler原理

如果在主线程(也叫UI线程)中执行一些耗时操作,会出现ANR问题。为了避免ANR,需要将耗时操作,如网络请求啊、数据库操作啊、读取文件等等的操作,开启一个子线程来处理。
在耗时操作执行完毕后,直接在子线程中更新UI怎么样呢?
一般来说,会出现下面这个错误:

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6891)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1048)
        at android.view.View.requestLayout(View.java:19781)
        at android.view.View.requestLayout(View.java:19781)
        at android.view.View.requestLayout(View.java:19781)
        at android.view.View.requestLayout(View.java:19781)
        at android.view.View.requestLayout(View.java:19781)
        at android.view.View.requestLayout(View.java:19781)
        at androidx.constraintlayout.widget.ConstraintLayout.requestLayout(ConstraintLayout.java:3172)
        at android.view.View.requestLayout(View.java:19781)
        at android.widget.TextView.checkForRelayout(TextView.java:7368)
        at android.widget.TextView.setText(TextView.java:4480)
        at android.widget.TextView.setText(TextView.java:4337)
        at android.widget.TextView.setText(TextView.java:4312)
        at icbc.agree.tmpapppp.MainActivity$2.run(MainActivity.java:37)

从上面的代码提示,不难看出UI是只能在主线程中更新的,这也是为什么主线程也被称为UI线程的原因。
但是我们在onCreate中启动一个子线程去更改一个TextView的文本,会惊奇的发现程序竟然可以正常运行,这是怎么回事呢?
其实是因为,检查线程的工作是ViewRootImpl来完成的,但是在onCreate中启动子线程,可能在ViewRootImpl对象创建之前便已经执行了,TextView的setText方法,不相信的话可以在setText之前sleep一段时间看看哈。

既然不可以在子线程更新UI,那么我们需要一个机制来通知主线程进行UI更新,这便是Handler的职责!

Handler是如何实现的呢?
要想搞明白Handler的原理,我们需要认识几个概念:
Message:Handler传递和处理的数据的载体
Message Queue:消息队列
Looper:一个不断从消息队列中取出数据的迭代器

在这里插入图片描述
Handler的工作流程大致如上图所示,Handler通过sendMessage等方法,发送一个Message,将其放到MessageQueue中,Looper不断的取出队列中的数据,并进行分发处理。

接下来,从安卓系统源码再来看一下Handler的工作原理:
首先,我们看一下主线程中的工作

public static void main(String[] args) {
        <-省略部分不相关代码->
        Looper.prepareMainLooper();

        <-省略部分不相关代码->

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

我们看到Looper.prepareMainLooper();这行代码,可以到Looper中看一下它的作用,其实最终是调用的prepare方法,而它的作用就是创建一个消息队列,代码如下:

		mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();

在主线程中的最后是调用的loop(),现在我们看一下对应的代码:

public static void loop() {
         final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;
		<-省略代码部分->
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            <-省略代码部分->
            try {
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

           <-省略代码部分->
        }
    }

我们分析下上面这段代码,我们获取到Message Queue,然后开启一个无限循环,通过queue.next()取出来Message,如果消息为空,说明消息队列中没有消息了,所以停止循环
最后的msg.target.dispatchMessage(msg);应该很熟悉了吧?这个target就是发送消息的Handler。
现在我们再去看一下,Handler的部分:
无论是sendMessage还是sendEmptyMessage,最终都是调用的sendMessageAtTime,所以我们直接看sendMessageAtTime的源码部分。

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

继续往下看

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

找到target了吧?有兴趣的可以继续往下找找代码,其实就是将Message加到消息队列中
好的,至此Handler完整的流程便梳理完了!

评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符 “速评一下”
©️2020 CSDN 皮肤主题: 深蓝海洋 设计师:CSDN官方博客 返回首页