【Android小品】从使用出发小试分析Handler机制相关类源码

来源:互联网 发布:safari淘宝不显示图片 编辑:程序博客网 时间:2024/04/30 20:11

这里澄清一下,我写博客基本都不是周期性更新的,也没有特定的计划。所以可能看到我很多的系列的文章都是“断开的”,有些系列只有一两篇。这是学习任务十分繁重,我不愿因为写文章而写文章,遇到自己很容易理解的,不需要记录下来的我就不会再写一篇博客。
学习Android这么长时间以来一直对源码阅读有所畏惧,因为一看源码就觉得涉及东西太复杂,有些时候突然冒出来一个函数、变量不知道什么意思。最近看了一些源码阅读方面的建议,打算自己来实验一下。分析比较简单的Android Handler类以及相关类的源码。

一、查看相关类的官方文档

以前阅读源码经常犯一个错误,就是从头开始看。这样总是导致纠结于细枝末节,以致最终无法读下去。所以这次我打算从“入口”出发,先看文档找出相关类,并且了解使用方法,然后再从入口方法去一步一步去了解。


Handler

这里写图片描述

Handler是通过MessageQueueMessage对象和Runnerable对象进行传输和处理的类。每个Handler都与一个线程以及该线程的MessageQueue关联。实例化一个Handler之后,它将被绑定到创建它的线程以及该线程的MessageQueue上。然后Handler会将Message放入MessageQueue中。当某个Message从队列中出来的时候就执行它。SendMessage…()函数发送的数据会在Handler的handleMessage(Message)方法中被接收。
UI线程已经有一个为ActivityBroadcastReceiverWindow等高级对象的MessageQueue。你也可以创建你自己的线程,并且通过UI线程的Handler与UI线程通信。(在最新的Android版本中,你可以在MessageQueue中传入Runnable对象。)

Handler有两个作用
1. 在未来的某个时间点执行任务
2. 多线程通信

使用以下方法来安排Message对象的执行。

post(Runnable) postAtTime(Runnable, long) postDelayed(Runnable, long) sendEmptyMessage(int) sendMessage(Message) sendMessageAtTime(Message, long) sendMessageDelayed(Message, long)

通过Handler类的了解,我们发现了一些相关类(MessageQueue)。所以再去查看Handler涉及到的几个重要类的文档。


MessageQueue

这里写图片描述

MessageQueue是存放被需要被Looper类发送的消息的容器(看名字是个队列)。想要把Message加入MessageQueue,需要通过与当前Looper关联的Handler对象来进行。
你可以通过Looper.myQueue()方法获取当前线程的,与当前线程Looper相关的MessageQueue


可见Android使用队列来管理Message的,为什么要这样做呢?带着问题去看源码。我们又看到了Looper


Looper

这里写图片描述

Looper类被用来维持线程的Message循环。一般线程默认没有关联Looper(或者说没有开启)。如果需要的话,请在线程内调用Looper.prepare(), 然后调用Looper.loop()

Google爸爸还给我们留了示例代码。这个挺重要的,毕竟是在教我们怎么Handler和Looper。OK,收下来

 class LooperThread extends Thread {      public Handler mHandler;      public void run() {  Looper.prepare();  mHandler = new Handler() {      public void handleMessage(Message msg) {          // process incoming messages here      }  };  Looper.loop();      }  }

Looper字面意思就是循环器(这个是Google爸爸的自造词,loop+er)。而且是用来处理Message的循环。貌似很神秘,到时候着重看看。


Message

这里写图片描述

Message是包含一些消息,并且可以被发给Handler的容器。Message对象有两个附加的int型成员变量,以及一个附加的Object对象。让你可以不用分配更多存储空间。
不建议使用构造器来直接创建Message,而应该使用Message.obtain()或者Handler.obtainMessage()来进行创建。这样可以使用在对象池中缓存的Message。


可见Message就是一个容器而已,特别之处在于它内部用了对象池缓存来提高效率!看源码的时候打算去看看它的实现。


这时候我们已经通过文档找到了与Handler机制有关的所有类。

二、从使用出发

当然,我假设大家与我一样已经熟练使用Handler了,只是没看过源码。使用Handler当然要从Handler开始咯,所以我们可以先不管其他几个类,只需要脑子里大概知道它们的作用即可。我们从Handler开始就好。

Handler handler = new Handler() {    @Override    public void handleMessage(Message msg) {        super.handleMessage(msg);    }};.....若干行代码之后......handler.sendEmptyMessage(int what);handler.sendEmptyMessageAtTime(int what,long uptimeMills);handler.sendEmptyMessageDelayed(int what,long delayMills);handler.sendMessage(Message msg);handler.sendMessageAtFrontOfQueue(Message msg);handler.sendMessageAtTime(Message msg,long uptimeMills);handler.sendMessageDelayed(Message msg,long delayMills);

我们知道handler.sendxxx()调用之后handleMessage()就会被调用,实现Message对象的传输,接下来就回到正题,我们来从sendxxx()开始一步一步追踪一下流程。

三、流程追踪(send…Message(…))

以下都是Handler的代码


这里写图片描述
从源码我们发现前面几个sendxxx()都是调用了其他sendxxx(),再从几个函数名来看,发现其实有逻辑关系。例如sendMessage()就是延迟为0,sendEmptyMessage(),就是发送的Message中不包含任何其他的东西,只有最基本的一个空壳……所以直接刨根问底,找到最终调的那个方法。
从sendMessage()开始我们追踪它的调用链。

Created with Raphaël 2.1.0public final boolean sendMessage(Message msg)final boolean sendMessageDelayed(Message msg, long delayMillis)boolean sendMessageAtTime(Message msg, long uptimeMillis) boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)(MessageQueue)queue.enqueueMessage(msg, uptimeMillis);
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {        msg.target = this;        if (mAsynchronous) {            msg.setAsynchronous(true);        }        return queue.enqueueMessage(msg, uptimeMillis);    }

好,我们发现最终应该是调用了enqueueMessage,enqueue从字面上理解就是加入队列的意思!好,我们发现形形色色的sendxxx() 最终就是需要两个值。第一:Message对象(前面分析文档说了,是个容器),这无可厚非,没有数据你发神马去。第二:uptimeMillis,就是延迟时间。第三:MessageQueue一个队列。
通过调用链流程图我们发现,这个MessageQueue是某一步窜出来的。查看源码发现应该是在sendMessageAtTime(..)方法中传入的。而这个queue实际上是Handler的某个成员变量。
这里写图片描述
这里写图片描述

疑问1:这个mQueue是如何被传进来的捏?不过为了连贯性,我们后面再看。继续“追杀”到MessageQueue里面去追踪。我们的MessageQueue被传到哪里去了。


boolean enqueueMessage(Message msg, long when) {if (msg.target == null) {    /*这个是基本的参数可靠性判断,经查看源码(Message)msg.target是Handler,应该就是发送它的Handler【图3.1】*/    throw new IllegalArgumentException("Message must have a target.");}if (msg.isInUse()) {    /*message.isInUse()其实是想要返回一个旗标,当然这里用到了一种比较好的设计:    (flags & FLAG_IN_USE) == FLAG_IN_USE,FLAG_IN_USE【图3.2】肯定是一个旗标咯,flags是一个Message的一个成员变量,里面是旗标组。用二进制与操作,直接就可以找到flags里面的指定位(标识是否inuse的那一位)是否为FLAG_IN_USE。*/    throw new IllegalStateException(msg + " This message is already in use.");}/*用本身作为同步监视器。其他线程与UI线程交互就是调用运行在UI线程的handler的send...()方法。所以这个涉及到多线程操作,必须要进行线程同步控制。否则...你懂的*/synchronized (this) {    if (mQuitting) {        /*从【msg.target + " sending messag......】已经可以看出,这是个线程是否还“活着”的旗标,因为前面看文档发现了,文档说MessageQueue和Looper都是与线程关联的,如果线程死亡了,当然也就不能在发消息了咯。*/        IllegalStateException e = new IllegalStateException(        msg.target + " sending message to a Handler on a dead thread");        Log.w("MessageQueue", e.getMessage(), e);        msg.recycle();        return false;    }    /*通过markInUse()方法就可以对inUse这个旗标打上true,防止重复操作。前面那个if(msg.isInUse())可以结合这里理解,如果inUse中又被调用则报错。*/    msg.markInUse();    msg.when = when;    //这个mMessages是MessageQueue的一个成员变量【图3.3】    Message p = mMessages;    boolean needWake;    if (p == null || when == 0 || when < p.when) {        //如果mMessages为空就说明队列为空(源码注释:New head, wake up the event queue if blocked.)        //我们惊奇的发现这个Message还有个Next方法!        msg.next = p;        mMessages = msg;        needWake = mBlocked;    } else {        /*在队列中部加入Message。一般来说除非在队列头部有“障碍”而且这个message还是队列中第一个message对象或者我们无需“唤醒”event队列(源码注释: Inserted within the middle of the queue.  Usually we don't have to wake up the event queue unless there is a barrier at the head of the queue and the message is the earliest asynchronous message in the queue.)*/        needWake = mBlocked && p.target == null && msg.isAsynchronous();        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.next        prev.next = msg;    }    //这里如果需要“唤醒”,进行唤醒。(后面就要转到JNI了,我不是很理解什么是以及为何要awake,不过对机制理解上应该不会造成什么影响)    if (needWake) {        nativeWake(mPtr);    }}//返回值代表的意思是是否成功加入了队列,但是成功加入队列不一定代表就能执行。如果Looper提前停止,则就//不会执行了。【图3.4】return true;}

图3.1
这里写图片描述
图3.2
这里写图片描述
图3.3
这里写图片描述
图3.4
这里写图片描述

果然,我们找到了最后的大boss,也印证了我们的猜想:Message是放在MessageQueue里面的!不过这个MessageQueue没有直接使用JAVA类集框架Queue接口的类。而是自己进行了实现。下面我们对实现部分进行分析。
这里写图片描述

可以看到用了链表的形式进行保存。

/**上面的部分代码精简,情况②:如果队列不为空切Message是delay的而且还比第一个晚。将Message插入到指定位置(前面先执行,后面后执行),如果**///"前一个 Message"Message prev;for (;;) {    //“指针”后移,将【前一个Message(prev变量)】设置为【当前的Message(p变量)】     prev = p;    //“指针”后移【当前的Message(p变量)】设置为【前一个Message所指的next变量(prev.next变量)】    p = p.next;    if (p == null || when < p.when) {        //如果现在的“指针”p为空,就说明已经到了队列尾部,不再进行后移操作        //或者如果【要加入的Message(msg参数)】的when在当前p之前        //就需要把【要加入的Message(msg参数)】插在【当前的Message(p变量)】和【前一个Message(prev变量)】中间        break;    }}//将【要加入的Message(msg参数)】的next引用指向msg.next = p; 【当前的Message(p变量)】//将【前一个Message(prev变量)】的next引用指向【要加入的Message(msg参数)】prev.next = msg;

小结:

send…Message(…)底层就是把Message加入到MessageQueue中。MessageQueue自己实现了自己的链表来存储一个个Message。MessageQueue是一个有序队列,越前面的Message越会被早执行。


下面我们来看看系统是如何从MessageQueue中取出Message的。

四、流程追踪(handleMessage(…))

这个我们需要反向去追踪,因为这个是系统调用的,有点小难度。

Created with Raphaël 2.1.0【Looper】public static void loop()【Handler】public void dispatchMessage(Message msg)【Handler】public void handleMessage(Message msg)

源码干脆利索,没转弯直接追踪到了是:(Looper)loop()调用了dispatchMessage(…)。这里为什么说Google爸爸没有卖关子呢?是因为handleMessage(…)只是个让你重写的空方法而已,可以忽略不计。
这里写图片描述


Looper.loop()的调用机制比较复杂,我们来仔细分析一下。仍然是从使用出发。Looper是这样用的:

Looper.preapre();Handler handler=new Handler(){...}Looper.loop();

分析Looper.prepare()

在调用Looper.prepare()之后实际上会准备好线程所对应的MessageQueueLooper过程出奇的简单。
竟然只是用了一个ThreadLocal变量,存储了Looper而已。
这里写图片描述
看了new Looper()之后恍然大悟。原来MessageQueue啊、Thread啊都是Looper()类的成员变量而已。
这里写图片描述

分析Looper.loop()

先观察代码
这里写图片描述

看的头晕了?其实还好吧,请看下我的批注

这里写图片描述

图① (直接发现了native方法)
这里写图片描述

图② (就是一个打Log的封装类)
这里写图片描述

注意红色部分这里不是重点哈哈,重点是用黄色荧光笔划出的部分。这里你直接看到了从MessageQueue中取出Message的过程。哈哈,搞定了,我们从源码中实实在在看到了把Message放入MessageQueue的过程,以及从MessageQueue中取出Message的过程。

小结:

Looper对象包含了MessageQueue,而MessageQueue用链表形式存储Message。Looper对象会在MessageQueueMessage的时候读取MessageMessagetarget属性存放了对应的Handler,通过dispatchMessage(…)发送该Message给handleMessage(…)和CallBack.handleMessage(…)。整个调用链完成了。

五、不..不,还没有搞定

这里写图片描述

Message msg = queue.next(); // might block

经过断点调试会发现,next()方法确实会在没有消息传送过来。
我们去看看MessageQueue的next()方法

这里写图片描述
这里写图片描述

这里由于这个方法比较复杂,Google爸爸为我们提供了注释。其实看注释理解起来就很简单啦。你也能够发现以下这样的字句:
这里写图片描述
这不就很明显是在取出下一个Message吗?
不过在断点之后,我们发现在没有消息的时候是阻塞在了下面这个方法上:
这里写图片描述
这里写图片描述
又是一个native方法。

由于C语言才刚刚掌握、C++才看了一半儿,Android一些native库的方法更加是没有接触过。不过分析到这儿我们已经得到了更新的认识!

小结:

Handler机制估计使用了系统的Binder机制进行通信。因为根据文档所说,Handler的一大作用就是实现线程间通信的嘛。所以send…Message(….)和被系统调用的handleMessage(…)就一定不运行在一个线程中。不难推断,也不难验证,handleMessage(…)一定是运行在Looper所在的线程中。send…Message(….)可以是运行在Looper所在的线程或者其他线程中。而追踪到了Native方法,又频繁啊看到Binder关键字,估计Handler通信最底层是Binder通信啦。然后我们发现在Looper.loop()中有一个死循环,MessageQueue.next(…)也有一个死循环。以此来实现不断的收到消息。

当然有Looper.prepare()Looper.loop()必然有Looper.quit()。否则还不永远循环下去啦!这里要注意看文档,我们应该调用Looper.quitSafely()。红色标出了两者的区别之处:如果在实际开发中send…Message(…)返回了true,但是调用了quit()了,就可能导致无法在handleMessage()收到对应的Message,这样可能会造成很多问题。
这里写图片描述

继续追踪到(MassageQueue)quit(boolean safe)!发现又是native方法,结束的标志只是将一个旗标设置了一下。估计死循环看到旗标改变就退出了吧。
这里写图片描述

小提示:因为Looper.loop()是死循环,所以只要Looper不停之后的代码就不会再执行了哦!正确的方法应该是在对应的Handler的handleMessage里面处理相关逻辑

六、Message的recycle

前面发现文档说要用Message.obtain()而不是直接new Message()。我们来看看Message的对象池吧!
先看recycle方法
这里写图片描述

这里写图片描述
哈哈,竟然又用了链表。这样做其实相当于在Message内部也维护了一个链表。当recycle的时候只需要把Message对象“清空”,然后将它从MessageQueue中“取下来”链接到对象池就好啦。其实这个对象池并不像我们想象的那么复杂。

再来看obtain()来验证我们的猜想,看!就是如此。
其实上面的源码中有使用Message对象缓存哦!
这里写图片描述

七、解答疑问一

疑问1:这个(MessageQueue)的mQueue是如何被传进来的捏?

这里写图片描述
又是Looper里面出来的,我们可以发现其实Looper在Handler机制中发挥了中流砥柱的作用。它组合了很多MessageQueueThread等重要实例。也是主要功能的实现者,要引起重视呀!

不过貌似我们在开发中用来更新UI时使用Handler发现并没有Looper这个重量级人物的出现。这是为啥呢?
这里写图片描述
原来,从Main函数开始FrameWork就已经帮我们做了这部分工作,
这就是为什么在UI线程中不用再自己去Looper.preapre()…….


关注作者

作者:唐家勋
邮箱:mrsteventang@gmail.com
QQ:649196486
保留版权,抄袭必究
希望您能在评论区指出宝贵意见,也欢迎关注我的微信号与我交流互动
我的微信公众号:xiaotangruanjian


更新进程

日期 日志 作者 2016年11月17日 创建文档 唐家勋
0 0