Handler机制实现原理总结
2023/6/14 18:24:53
本文主要是介绍Handler机制实现原理总结,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
Handler一般用于线程间通信,如常用的子线程使用handler让主线程更新UI。那么这是怎么实现的呢?
我们先把这个大问题分解成多个小问题:
-
post();postDelayed();sendMessage();sendEmptyMessage();
等方法有什么不同? - Handler为什么需要一个Looper,为什么它不能为空?
- Handler为什么可以做到线程间通信?
-
postDelayed()
为什么可以让线程延迟执行?
接下来带着这些疑惑去寻找答案。
post();postDelayed();sendMessage();sendEmptyMessage();
等方法有什么不同?
它们最终都是调用同一个方法:sendMessageAtTime()
,只是参数不同,Handler帮我们进行了一下封装。
先看post();postDelayed();
这两个方法,查看源码可以发现,这两个方法都是调用sendMessageDelayed(Message, long)
。只是post()的时间这个参数是0。
代码如下:
public final boolean post(@NonNull Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } public final boolean postDelayed(@NonNull Runnable r, long delayMillis) { return sendMessageDelayed(getPostMessage(r), delayMillis); } private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
关键是将Runable任务封装成Message的这个getPostMessage()
。
这里并不是简单地将Runable封装成Message,这里还有一个Message回收池机制的实现。将在下文展开介绍。
再看sendMessage();sendEmptyMessage();
这两个方法:
public final boolean sendMessage(@NonNull Message msg) { return sendMessageDelayed(msg, 0); } public final boolean sendEmptyMessage(int what){ return sendEmptyMessageDelayed(what, 0); } public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { Message msg = Message.obtain(); msg.what = what; return sendMessageDelayed(msg, delayMillis); }
可以看到这四个方法最终都是调用sendMessageDelayed()
:
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); }
上面sendMessageDelayed()
的实现都很简单,但需要注意的是这里使用了SystemClock.uptimeMillis()
,它返回的是设备的开机时间(不包括息屏睡眠时间),这个时间和Handler的消息可以延迟触发有关。将在后面详细介绍。
Handler为什么需要一个Looper,为什么它不能为空?
因为MessageQueue是通过Looper获取到的。
而Message需要通过MessageQueue来等待执行。
在创建Handler时如果检测到Looper为空,将会抛出NullPointerException错误
如果不是通过构造函数传入的Looper,比如Handler#Callback,构造方法会通过Looper.myLooper()
获取到当前线程的Looper。
Looper.myLooper()
可以获取到当前线程的Looper是因为ThreadLocal的特性。
代码如下:
public Handler(@Nullable Callback callback, boolean async) { mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread " + Thread.currentThread() + " that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }
Handler为什么可以做到线程间通信?
Handler最长使用的大概是子线程通知UI线程更新UI吧。
我们通过post();sendMessage();
等方法提交一个Message时,这个Message会被放入MessageQueue。在创建Handler时会得到一个Looper,Looper会循环从MessageQueue取出Message处理,而每个Looper属于一个线程,如果该Looper是UI线程的,那Message就是在UI线程处理。
知其然亦应知其所以然。
我们从Handler#post()
这个方法开始研究。
在上面已经讲过,post()
最终调用的是sendMessageAtTime()
,这个方法首先是获取了与该Handler绑定Looper的MessageQueue对象,然后通过一些参数设置,最后执行MessageQueue#enqueueMessage()
方法。
MessageQueue#enqueueMessage()
看方法名就知道这个方法主要工作是对Message排队处理:
boolean enqueueMessage(Message msg, long when) { //... synchronized (this) { if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); } //... msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { msg.next = p; mMessages = msg; needWake = mBlocked; } else { Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } //... } msg.next = p; // invariant: p == prev.next prev.next = msg; } //... } return true; }
我去掉了一些和排队无关的代码,上面这段代码并不难,就是以链表的形式将Message进行排列。
首先判断这个Message是否正在被使用。
Message什么情况下是被使用状态呢?
其实这个也和Message回收池有些关系。
我们new的对象和回收池中取出的Message默认状态是0,当Message进入MessageQueue等待处理时就是被使用状态。从MessageQueue取出,被处理完成回收的Message其状态又会被重置为0 。
回到Message排队问题,然后判断三个条件:
- Message链表是否为空
- when == 0
- 这种情况只有调用
Handler#sendMessageAtFrontOfQueue()
才会出现
- 这种情况只有调用
- when < p.when
- when表示消息将在什么时候执行,数字越小的排在前面
当其中一个条件满足时,将当前Message插入链表头部。
既然Message时怎么放入MessageQueue这块已经弄清楚了,那接着看一下Looper。看看是怎么取出Messages,又是怎么处理的
Looper
如果使用过Looper就应该知道,这是一个循环。
查看这个类的注释,可以看到它提供了一个简单的用法。
Looper.prepare(); Looper.loop();
主要有两个方法:
- 第一个在当前线程创建Looper对象并放入ThreadLocal中,
- 第二个循环MessageQueue,取出其中的Message在当前线程处理
Looper实现在这里不进行深入,只讲一下和MessageQueue有关的。
先看loop()
:
//Looper public static void loop() { final Looper me = myLooper(); //... for (;;) { if (!loopOnce(me, ident, thresholdOverride)) { return; } } } private static boolean loopOnce(final Looper me, final long ident, final int thresholdOverride) { Message msg = me.mQueue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return false; } //。。。 msg.target.dispatchMessage(msg); //。。。 msg.recycleUnchecked(); return true; } //Handler public void dispatchMessage(@NonNull Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
可以看到loop()
里面用for写了一个死循环,它执行了loopOnce()
,它是真正取出Message并执行的方法。
在dispatchMessage()
方法内,首先判断callback是否为空,它是Message的Runable,就是我们使用post();postDelayed();
提交到Runable。
然后判断Handler#Callback
,是否为空,这个是在Handler构造方法传入的Callback,这里也解释了当我们实现了callback时可以跨线程通信的原因。
当next()
返回null会结束for循环。我们Android主线程没有消息为什么还可以继续运行?
我们创建Looper对象都是通过其静态方法来创建的,而Looper的构造方法有一个参数quitAllowed,这个参数为True时MessageQueue不会因为消息为空而退出。
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
postDelayed()
为什么可以让线程延迟执行?
让我们回到上面Looper#loopOnce()
这个方法,我在上面没有介绍怎么从MessageQueue取出Message就是留给这个问题的。
线上关键代码:
//MessageQueue Message next() { for (;;) { synchronized (this) { //获取当前的开机时间 final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; if (msg != null) { //判断当前开机时间是否小于msg的开机时间 //如果为false表示这条消息应该被拿出去处理了 if (now < msg.when) { nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. 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; } } //。。。 } } }
这块代码还是很多的,我去掉了和该问题无关的代码。
这里先获取了当前开机时间,然后和Message的when对比,如果当前开机时间比when大,表示这条消息到了处理时间,直接return。
我们使用postDelayed(Runnable,0)
时,执行到sendMessageDelayed()
后会加上SystemClock.uptimeMillis()
最后变成when值,也就是SystemClock.uptimeMillis()+0
,
因此MessageQueue取出消息时,该Message会被立即执行,而延迟xxx时间是一样的道理。
Message回收池是怎么回事?
让我们回到上面展示的Looper#loopOnce()
这个方法,可以看到当消息被取出来处理后,调用了msg.recycleUnchecked();
回收当前Message:
void recycleUnchecked() { // Mark the message as in use while it remains in the recycled object pool. // Clear out all other details. flags = FLAG_IN_USE; what = 0; arg1 = 0; arg2 = 0; obj = null; replyTo = null; sendingUid = UID_NONE; workSourceUid = UID_NONE; when = 0; target = null; callback = null; data = null; synchronized (sPoolSync) { if (sPoolSize < MAX_POOL_SIZE) { next = sPool; sPool = this; sPoolSize++; } } }
这个方法会重置Message参数,然后判断当前回收池有没有达到上限,上限是50个,没有达到会把这个Message插入链表等待再次使用。
public static Message obtain() { synchronized (sPoolSync) { if (sPool != null) { Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; } } return new Message(); }
Message.obtain()
会检查回收池,如果回收池不为空,从链表头部取出一个对象并返回。
在Google官方文档也能看到,Google建议我们通过Message.obtain()
获取一个新的Message对象,而不是直接new。
总结
handler的这个机制是由几个类一起协作共同实现的。它们分别是:
- Handler
- Handler:负责协调各个类的工作,以达到这个机制的功能。
- Looper
- Looper:一个Looper对象属于一个线程,由它来管理此线程里的MessageQueue(消息队列)
- MessageQueue
- MessageQueue:消息队列,负责管理Message,以及延迟Message处理
- Message
- Message:用于存放需要发送的数据,将数据包装为消息对象。管理回收池等。
扩展知识
- MessageQueue
这篇关于Handler机制实现原理总结的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-27数据结构与算法面试题详解及练习
- 2024-12-27网络请求面试题详解与实战
- 2024-12-27数据结构和算法面试真题详解与实战教程
- 2024-12-27网络请求面试真题解析与实战教程
- 2024-12-27数据结构和算法大厂面试真题详解与实战指南
- 2024-12-27TS大厂面试真题解析与应对策略
- 2024-12-27TS大厂面试真题详解与解析
- 2024-12-27网站安全入门:如何识别和修复漏洞
- 2024-12-27SQL注入基础教程
- 2024-12-27初学者指南:理解和修复跨域漏洞