Android Handler机制的工作原理详析
写在前面
上一次写完binder学习笔记之后,再去看一遍activity的启动流程,因为了解了binder的基本原理,这次看印象会更深一点,学习效果也比以前好很多。本来打算直接来写activity的启动流程的,但总觉得handler也需要写一下,知道handler和binder的原理后,再去看activity的启动流程,应该也没什么问题了。虽然网上已经有很多handler相关的文章了,而且handler机制的上层原理也并不难,还是决定写一下,因为我想构建自己的知识体系。也希望给看我博客的朋友们一个无缝衔接的阅读体验。
handler机制涉及到的类主要有handler、message、looper、messagequeue、threadlocal等。虽然我们最熟悉的是handler和message这两个类,但是在我们开始可以使用handler之前,looper是为我们做了一些事情的。
本文的源码是基于android-28的
looper
在使用handler之前,我们必须得初始化looper,并让looper跑起来。
looper.prepare(); ... looper.loop();
执行上面两条语句之后,looper就可以跑起来了。先来看看对应的源码:
public static void prepare() { prepare(true); } private static void prepare(boolean quitallowed) { if (sthreadlocal.get() != null) { throw new runtimeexception("only one looper may be created per thread"); } sthreadlocal.set(new looper(quitallowed)); } private looper(boolean quitallowed) { mqueue = new messagequeue(quitallowed); mthread = thread.currentthread(); }
必须保证一个线程中有且只有一个looper对象,所以在初始化looper的时候,会检查当前线程有没有looper对象。looper的初始化会创建一个messagequeue。创建完looper后会放到threadlocal中去,关于threadlocal,后面会说到。
public static void loop() { // 判断当前线程有没有初始化looper 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; } ... final long tracetag = me.mtracetag; if (tracetag != 0 && trace.istagenabled(tracetag)) { trace.tracebegin(tracetag, msg.target.gettracename(msg)); } try { // target指的是handler msg.target.dispatchmessage(msg); } finally { if (tracetag != 0) { trace.traceend(tracetag); } } ... msg.recycleunchecked(); } }
方法比较长,所以只把最核心的代码放了出来。省略掉的代码中有一个比较有意思的:我们可以指定一个阈值比如说200,当message的处理超过200ms时,就会输出log。这可以在开发中帮助我们发现一些潜在的性能问题。可惜的是,设置阈值的方法是隐藏的,无法直接调用,所以这里就不放出代码了,感兴趣的朋友自己翻一下源码吧。
简化后的代码可以看出逻辑十分简单,可以说looper在当中扮演着搬砖工的角色,从messagequeue中取出message,然后交给handler去分发,再去messagequeue中取出message...无穷无尽,就像愚公移山一样。
看到这里,应该多多少少会觉得有点不对劲,因为这里是一个死循环,按道理来说会一直占着cpu资源的,并且消息也总有处理完的时候,难道处理完就从消息队列返回null,然后looper结束吗?显然不是,注意看注释might block。
messagequeue
答案就在messagequeue里面,直接来看一下next():
message next() { ... int pendingidlehandlercount = -1; // -1 only during first iteration int nextpolltimeoutmillis = 0; for (;;) { if (nextpolltimeoutmillis != 0) { binder.flushpendingcommands(); } nativepollonce(ptr, nextpolltimeoutmillis); synchronized (this) { // try to retrieve the next message. return if found. final long now = systemclock.uptimemillis(); message prevmsg = null; message msg = mmessages; if (msg != null && msg.target == null) { // stalled by a barrier. find the next asynchronous message in the queue. do { prevmsg = msg; msg = msg.next; } while (msg != null && !msg.isasynchronous()); } if (msg != null) { if (now < msg.when) { // next message is not ready. set a timeout to wake up when it is ready. nextpolltimeoutmillis = (int) math.min(msg.when - now, integer.max_value); } else { // got a message. mblocked = false; if (prevmsg != null) { prevmsg.next = msg.next; } else { mmessages = msg.next; } msg.next = null; if (debug) log.v(tag, "returning message: " + msg); msg.markinuse(); return msg; } } else { // no more messages. nextpolltimeoutmillis = -1; } // process the quit message now that all pending messages have been handled. if (mquitting) { dispose(); return null; } // if first time idle, then get the number of idlers to run. // idle handles only run if the queue is empty or if the first message // in the queue (possibly a barrier) is due to be handled in the future. if (pendingidlehandlercount < 0 && (mmessages == null || now < mmessages.when)) { pendingidlehandlercount = midlehandlers.size(); } if (pendingidlehandlercount <= 0) { // no idle handlers to run. loop and wait some more. mblocked = true; continue; } if (mpendingidlehandlers == null) { mpendingidlehandlers = new idlehandler[math.max(pendingidlehandlercount, 4)]; } mpendingidlehandlers = midlehandlers.toarray(mpendingidlehandlers); } // run the idle handlers. // we only ever reach this code block during the first iteration. for (int i = 0; i < pendingidlehandlercount; i++) { final idlehandler idler = mpendingidlehandlers[i]; mpendingidlehandlers[i] = null; // release the reference to the handler boolean keep = false; try { keep = idler.queueidle(); } catch (throwable t) { log.wtf(tag, "idlehandler threw exception", t); } if (!keep) { synchronized (this) { midlehandlers.remove(idler); } } } // reset the idle handler count to 0 so we do not run them again. pendingidlehandlercount = 0; // while calling an idle handler, a new message could have been delivered // so go back and look again for a pending message without waiting. nextpolltimeoutmillis = 0; } }
代码有点长,这次不打算省略掉一些了,因为这里面还有一个小彩蛋。
方法中最重要的应该就是这一行了
nativepollonce(ptr, nextpolltimeoutmillis);
简单来说,当nextpolltimeoutmillis == -1时,挂起当前线程,释放cpu资源,当nextpolltimeoutmillis >= 0时会延时指定的时间激活一次线程,让代码继续执行下去。这里涉及到了底层的pipe管道和epoll机制,就不再讲下去了(其实是因为讲不下去了)。这也就可以回答上面的问题了,当没有消息的时候只需要让线程挂起就行了,这样可以保证不占用cpu资源的同时保住looper的死循环。
然后我们来看消息是如何取出来的。messagequeue中有一个message,message类中又有一个message成员next,可以看出message是一个单链表结构。消息的顺序是根据时间先后顺序排列的。一般来说,我们要取的message就是第一个(这里先不考虑异步消息,关于异步消息以后会讲到的,又成功给自己挖了一个坑哈哈),如果当前时间大于等于message中指定的时间,那么将消息取出来,返回给looper。由于此时nextpolltimeoutmillis的值为0,所以当前面的消息处理完之后,looper就又来取消息了。
如果当前的时间小于message中指定的时间,那么设置nextpolltimeoutmillis值以便下次唤醒。还有另外一种当前已经没有消息了,nextpolltimeoutmillis会被设置为-1,也就是挂起线程。别急,还没那么快呢,接着往下看。
紧接着的逻辑是判断当前有没有idlehandler,没有的话就continue,该挂起就挂起,该延时就延时,有idlehandler的话会执行它的queueidle()方法。这个idlehandler是干什么的呢?从名字应该也能猜出个一二来,这里就不再展开讲了。关于它的一些妙用可以看我之前写的android 启动优化之延时加载。执行完queueidle()方法后,会将nextpolltimeoutmillis置为0,重新看一下消息队列中有没有新的消息。
handler
上面将取消息的流程都讲清楚了,万事俱备,就差往消息队列中添加消息了,该我们最熟悉的handler出场了。handler往队列中添加消息,主要有两种方式:
handler.sendxxx(); handler.postxxx();
第一种主要是发送message,第二种是runnable。无论是哪种方式,最终都会进入到messagequeue的enqueuemessage()方法。
boolean enqueuemessage(message msg, long when) { ... synchronized (this) { ... msg.markinuse(); msg.when = when; message p = mmessages; boolean needwake; if (p == null || when == 0 || when < p.when) { // new head, wake up the event queue if blocked. msg.next = p; mmessages = msg; needwake = mblocked; } else { // 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; } // we can assume mptr != 0 because mquitting is false. if (needwake) { nativewake(mptr); } } return true; }
一般情况下,我们通过handler发送消息的时候,会通过systemclock.uptimemillis()获取一个开机时间,然后messagequeue就会根据这个时间来对message进行排序。所以enqueuemessage()方法中就分了两种情况,一种是直接可以在队头插入的。一种是排在中间,需要遍历一下,然后寻一个合适的坑插入。when == 0对应的是handler的sendmessageatfrontofqueue()和postatfrontofqueue()方法。needwake的作用是根据情况唤醒looper线程。
上面有一点还没有讲,就是looper从messagequeue中取出message后,会交由handler进行消息的分发。
public void dispatchmessage(message msg) { if (msg.callback != null) { handlecallback(msg); } else { if (mcallback != null) { if (mcallback.handlemessage(msg)) { return; } } handlemessage(msg); } }
优先级顺序是message自带的callback,接着是handler自带的callback,最后才是handlemessage()这个回调。
threadlocal
还记得looper中有一个threadlocal吧,把它放到最后来讲是因为它可以单独拿出来讲,不想在上面干扰到整个流程。
threadlocal是一个数据存储类,它最神奇的地方就是明明是同一个threadlocal对象,但是在不同线程中可以存储不同的对象,比如说在线程a中存储了"hello",而在线程b中存储了"world"。它们之间互相不干扰。
在handler机制中,由于一个looper对应着一个线程,所以将looper存进threadlocal最合适不过了。
threadlocal比价常用的就set()和get()方法。分别来看看怎么实现的吧。
public void set(t value) { thread t = thread.currentthread(); threadlocalmap map = getmap(t); if (map != null) map.set(this, value); else createmap(t, value); }
首先是去获取threadlocalmap,找得到的话直接设置值,找不到就创建一个。
threadlocalmap getmap(thread t) { return t.threadlocals; }
看到这里,大概也能明白了。每个线程thread中有一个threadlocalmap对象。通过threadlocal.set()方法,实际上是去获取当前线程中的threadlocalmap,线程不同,获取到的threadlocalmap自然也不同。
再来看看这个threadlocalmap是什么来头。看类的注释中有这么一句话:
threadlocalmap is a customized hash map suitable only for maintaining thread local values.
从注释中可以知道这就是一个定制的hashmap,并且它的entry类指定了key只能为threadlocal类型的。所以直接将它看成是一个hashmap就好了。
get()方法也好理解,就是从map中取出值而已。大概看一下就好了。
public t get() { thread t = thread.currentthread(); threadlocalmap map = getmap(t); if (map != null) { threadlocalmap.entry e = map.getentry(this); if (e != null) { @suppresswarnings("unchecked") t result = (t)e.value; return result; } } return setinitialvalue(); }
写在最后
虽然在开始写之前,觉得handler机制比较简单,好像没啥必要写,但真正要写起来的时候还是得去深入了解代码的细节,然后才发现有些地方以前理解得也不够好。能理解和能写出来让别人理解,其实是不同的层次了。
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。
推荐阅读
-
Android消息通信机制Handler详解,Handler,Looper,MessageQueue,源码解析,讲解这几个类怎么配合工作的
-
Android的消息机制Handler原理分析
-
Android消息机制之ThreadLocal的工作原理
-
Android的消息机制之ThreadLocal的工作原理
-
从源码的角度分析Android中的Handler机制的工作原理
-
Handler消息机制的一些原理(直接用code讲解)——Android开发
-
Android的线程通信:消息机制原理(Message,Handler,MessageQueue,Looper),异步任务AsyncTask,使用JSON
-
Android Handler机制的工作原理详析
-
Android的Handler机制原理
-
Android中的消息机制二(Handler的工作过程)