【Android小品】从使用出发小试分析Handler机制相关类源码
来源:互联网 发布:safari淘宝不显示图片 编辑:程序博客网 时间:2024/04/30 20:11
这里澄清一下,我写博客基本都不是周期性更新的,也没有特定的计划。所以可能看到我很多的系列的文章都是“断开的”,有些系列只有一两篇。这是学习任务十分繁重,我不愿因为写文章而写文章,遇到自己很容易理解的,不需要记录下来的我就不会再写一篇博客。
学习Android这么长时间以来一直对源码阅读有所畏惧,因为一看源码就觉得涉及东西太复杂,有些时候突然冒出来一个函数、变量不知道什么意思。最近看了一些源码阅读方面的建议,打算自己来实验一下。分析比较简单的Android Handler类以及相关类的源码。
一、查看相关类的官方文档
以前阅读源码经常犯一个错误,就是从头开始看。这样总是导致纠结于细枝末节,以致最终无法读下去。所以这次我打算从“入口”出发,先看文档找出相关类,并且了解使用方法,然后再从入口方法去一步一步去了解。
Handler
Handler是通过MessageQueue对Message对象和Runnerable对象进行传输和处理的类。每个Handler都与一个线程以及该线程的MessageQueue关联。实例化一个Handler之后,它将被绑定到创建它的线程以及该线程的MessageQueue上。然后Handler会将Message放入MessageQueue中。当某个Message从队列中出来的时候就执行它。SendMessage…()函数发送的数据会在Handler的handleMessage(Message)方法中被接收。
UI线程已经有一个为Activity、BroadcastReceiver、Window等高级对象的MessageQueue。你也可以创建你自己的线程,并且通过UI线程的Handler与UI线程通信。(在最新的Android版本中,你可以在MessageQueue中传入Runnable对象。)
Handler有两个作用
1. 在未来的某个时间点执行任务
2. 多线程通信
使用以下方法来安排Message对象的执行。
通过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()开始我们追踪它的调用链。
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(…))
这个我们需要反向去追踪,因为这个是系统调用的,有点小难度。
源码干脆利索,没转弯直接追踪到了是:(Looper)loop()调用了dispatchMessage(…)。这里为什么说Google爸爸没有卖关子呢?是因为handleMessage(…)只是个让你重写的空方法而已,可以忽略不计。
Looper.loop()的调用机制比较复杂,我们来仔细分析一下。仍然是从使用出发。Looper是这样用的:
Looper.preapre();Handler handler=new Handler(){...}Looper.loop();
分析Looper.prepare()
在调用Looper.prepare()之后实际上会准备好线程所对应的MessageQueue和Looper过程出奇的简单。
竟然只是用了一个ThreadLocal变量,存储了Looper而已。
看了new Looper()之后恍然大悟。原来MessageQueue啊、Thread啊都是Looper()类的成员变量而已。
分析Looper.loop()
先观察代码
看的头晕了?其实还好吧,请看下我的批注
图① (直接发现了native方法)
图② (就是一个打Log的封装类)
注意红色部分这里不是重点哈哈,重点是用黄色荧光笔划出的部分。这里你直接看到了从MessageQueue中取出Message的过程。哈哈,搞定了,我们从源码中实实在在看到了把Message放入MessageQueue的过程,以及从MessageQueue中取出Message的过程。
小结:
Looper对象包含了MessageQueue,而MessageQueue用链表形式存储Message。Looper对象会在MessageQueue有Message的时候读取Message。Message的target属性存放了对应的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机制中发挥了中流砥柱的作用。它组合了很多MessageQueue、Thread等重要实例。也是主要功能的实现者,要引起重视呀!
不过貌似我们在开发中用来更新UI时使用Handler发现并没有Looper这个重量级人物的出现。这是为啥呢?
原来,从Main函数开始FrameWork就已经帮我们做了这部分工作,
这就是为什么在UI线程中不用再自己去Looper.preapre()…….
关注作者
作者:唐家勋
邮箱:mrsteventang@gmail.com
QQ:649196486
保留版权,抄袭必究
希望您能在评论区指出宝贵意见,也欢迎关注我的微信号与我交流互动
更新进程
- 【Android小品】从使用出发小试分析Handler机制相关类源码
- 【Android小品】从使用出发完全理解View(ViewGroup)测量机制,并分析部分源码(修复图片)
- [Android] 从源码分析 Handler 消息机制
- Android 从源码分析Handler消息机制
- Android从源码分析一:Looper,Handler消息机制
- 从源码分析Android中Handler的消息传递机制
- Android Handler机制源码分析
- 消息机制Handler及相关源码分析
- Handler机制-从源码角度分析
- 菜鸟从源码分析Handler消息机制
- Handler消息处理机制---从源码分析
- Android 源码分析 —— 从 Toast 出发
- 【Android】从源码中探讨Handler机制
- Android 从源码看Handler消息机制
- 【Android】从源码角度看Handler机制
- Android源码分析之Handler机制
- Android消息机制 Handler源码分析
- Android Handler 消息响应机制源码分析
- spring拾遗(二)——NamedJdbcTemplate使用归纳
- P2P网络Chord环的构造
- nginx的upstream目前支持5种方式的分配
- [hackerrank]Walking the Longest Path (Approximation Problem)
- 硬RAID可以为NVMe SSD数据可靠性保驾护航吗?
- 【Android小品】从使用出发小试分析Handler机制相关类源码
- 日期算星座
- Cassandra安装Windows版本使用cassandra -cli命令出现7199端口占用问题解决
- Caffe源码解析7:Pooling_Layer
- 线性布局(LinearLayout)
- Dalvik和Java字节码的对比
- 广州网络营销师吴家成教你网站网页标题title优化设置
- 125. Valid Palindrome
- 指针与数组