欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android Handler机制的工作原理详析

程序员文章站 2022-06-16 13:32:23
写在前面 上一次写完binder学习笔记之后,再去看一遍activity的启动流程,因为了解了binder的基本原理,这次看印象会更深一点,学习效果也比以前好很多。本...

写在前面

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

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