博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
刨根问底——Handler
阅读量:5989 次
发布时间:2019-06-20

本文共 4779 字,大约阅读时间需要 15 分钟。

        提起Handler,很多人首先想到的就是子线程执行耗时操作,主线程更新UI。那么这种机制内部是怎么实现的呢?为什么我们只需要在UI线程声明初始化一个Handler,然后在子线程发送一个消息,最后就能根据这条消息来执行后续的操作?其实这种机制不仅仅只是通过Handler来实现的,它还需要Looper、MessageQueue、Message来协同处理。

MessageQueue,顾名思义,就是一个由Message组成的Queue,它(MessageQueue)的内部维护着一条队列,每当我们通过Handler发送一条Message后,这条Message都会被添加到MessageQueue的队列尾部,其实这条消息队列只起到存储消息的作用,并不具备任何循环、处理消息的作用。Looper在这个机制中扮演者循环器的作用,它会不断的从MessageQueue中取到队列头部的Message,然后交给Handler来处理这条Message。至此,思路已经很明显了,Handler发送Message至MessageQueue,同时Looper不断地尝试读取MessageQueue中的Message,发现Message之后将它取出交由Handler来处理,由于Handler的handleMessage是执行在主线程的(假设我们在主线程初始化的Handler),所以此时我们可以在该处执行一些更新UI的操作。

现在我们来将该机制分解成三部分:1.Handler发送Message之后这条Message是怎么进入MessageQueue的;2.Looper是如何循环获取Message的;3.Message是怎么被Handler处理的。

Handler发送Message之后Message是怎么进入到了MessageQueue中的?

说到这个,我们就不得不介绍一下Handler中的sendMessageAtTime这个方法了,这个方法我们平时基本上不会用到,因为我们用的最多的是Handler的sendMessage或者是sendMessageDelayed,又或者是调用Message的sendToTarget方法(handler.obtainMessage().sendToTarget()),那为什么还要介绍这个方法呢?因为我们通过Handler和Message的源码可以看到,无论是调用上述的哪个方法,最后都会走到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);}复制代码

我们可以看到,执行到这里的时候我们已经追踪到了消息进入队列的入口,我们点进enqueueMessage这个方法中看一下

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

我们可以看到,Message是通过这个方法来将消息放入队列的(注意msg.target = this,后面我们会用到),我们再进入MessageQueue中来看一下enqueueMessage这个方法(方法中的一段)

Message prev;for (;;) {    prev = p;    p = p.next;    if (p == null || when < p.when) {        break;    }    if (needWake && p.isAsynchronous()) {        needWake = false;    }}msg.next = p; // invariant: p == prev.nextprev.next = msg;复制代码

在这里,p代表当前消息队列中的队列头部,prev代表p指向的Message的前一条,我们可以看到,在for循环中,prev和p一直从队列头部索引到队列尾部,当跳出for循环时,p指向的Message为null,说明最后一条消息就是prev这个变量所指向的Message,当最后两个表达式执行完毕后,msg被添加到了队列的尾部,到这里Message进入的MessageQueue中的路线就结束了。

.Looper是如何循环获取Message的

前面我们说到,MessageQueue只是Message的容器,它本身不具备选择Message派发给相应Handler的功能,这时就需要Looper来发挥作用了。我们知道Looper被初始化后,都需要调用loop方法,那么这个方法是做什么的?我们来看一下

for (;;) {    Message msg = queue.next(); // might block    if (msg == null) {        // No message indicates that the message queue is quitting.        return;    }    // This must be in a local variable, in case a UI event sets the logger    final Printer logging = me.mLogging;    if (logging != null) {        logging.println(">>>>> Dispatching to " + msg.target + " " +                msg.callback + ": " + msg.what);    }    final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;    final long traceTag = me.mTraceTag;    if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {        Trace.traceBegin(traceTag, msg.target.getTraceName(msg));    }    final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();    final long end;    try {        msg.target.dispatchMessage(msg);        end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();    } finally {        if (traceTag != 0) {            Trace.traceEnd(traceTag);        }    }复制代码

我们来重点关注一下第2行和倒数第7行的表达式,这里面的queue就是MessageQueue了,我们可以看到,Looper在不断的尝试从MessageQueue中获取队列头部的Message,然后会获取这条Message的target并且执行dispatchMessage()方法,这个target又是个啥?进入我们刚刚看到过的Handler的enqueueMessage方法中,我们可以看到,在该方法的第1行中就是为Message的target赋值,所以target的值是一个Handler,也就是我们发送Message的这个Handler,咦?好像要到最后一步了,Message已经交给Handler了,接下来就是我们最后要说的内容了,Handler是怎么处理这条Message的。

Handler是怎么处理这条Message的?

现在Message已经又交到了Handler的手里,我们来看一下Handler的dispatchMessage方法

public void dispatchMessage(Message msg) {    if (msg.callback != null) {        handleCallback(msg);    } else {        if (mCallback != null) {            if (mCallback.handleMessage(msg)) {                return;            }        }        handleMessage(msg);    }}复制代码

我们可以看到这个方法的逻辑非常简单,如果msg被设置了回调方法,就执行它的回调方法,否则就执行Handler里面的相关方法。通常我们并没有为Message设置回调函数,并且都是直接new一个不带回调函数的Handler,所以我们的方法只剩了最后一条执行路线,调用handleMessage方法,这个方法看着好眼熟啊!这不就是我们new Handler的时候实现的那个方法吗?我们点进去看一下

/** * Subclasses must implement this to receive messages. */public void handleMessage(Message msg) {}复制代码

还真是这个方法。从这里开始就是我们熟悉的操作了,在handleMessage中通过what来匹配,然后来执行不同的逻辑,由于我们是在主线程实现的这个方法,所以我们可以在这里面更新相关的UI界面,这样就实现了在子线程中执行操作,在主线程中更新UI的功能了。

到这里我们Handler的消息派发机制大致上已经说完了,由于文笔太烂只说了Handler大致的机制,没有涉及到的知识有很多,比如Looper是怎么存储和获取的、msg的callback是怎么设置的等等。我想只要先掌握了机制的大致流程,以后深入某个功能的时候才不会迷失在海量的代码中。

写这些东西只是想对自己学过的东西做一下笔记,如果对您有了一丝丝的帮助,那就再好不过了。多帮我提提建议啊,文中若有不足,敬请谅解!

转载于:https://juejin.im/post/5a4de624f265da43310e3d3e

你可能感兴趣的文章
学以致用二十七-----Centos7.5二进制安装mysql5.7.23
查看>>
常用的JavaScript工具类库收藏
查看>>
ubuntu安装node
查看>>
Linux怎样创建FTP服务器
查看>>
魏武挥:那些出走的媒体人们
查看>>
GIt分支
查看>>
一次由ip_conntrack跟踪连接库满导致的大量丢包现象排除
查看>>
Mongodb的安装与CRUD操作
查看>>
C#实战-圆半径
查看>>
我的友情链接
查看>>
Cocos2d-x for WindowsPhone:从开始到一个场景再一张图片
查看>>
windows 2008 FTP登录报错
查看>>
CentOS 6.5搭建本地OpenStack软件源
查看>>
Cobbler实现自动化批量安装Linux系统系列一:安装先决性组件篇
查看>>
支付宝api指南
查看>>
Windows bat脚本的for语句
查看>>
单片mongoDB
查看>>
LNMP-源码 PHP7
查看>>
简单干净的C#方法设计案例:SFCUI.AjaxValue()之三
查看>>
敏捷开发免费管理工具——火星人预览之五:常见问题问答
查看>>