当前位置: 移动技术网 > IT编程>移动开发>Android > Android Handler机制的工作原理详析

Android Handler机制的工作原理详析

2019年07月29日  | 移动技术网IT编程  | 我要评论

国学经典,陈礼燕,平糖王

写在前面

上一次写完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机制比较简单,好像没啥必要写,但真正要写起来的时候还是得去深入了解代码的细节,然后才发现有些地方以前理解得也不够好。能理解和能写出来让别人理解,其实是不同的层次了。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对移动技术网的支持。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网