前言
对Android开发者来suo,Handler机制无疑是最重要的知识之一,大家肯定也已经看过诸多有关Handler的教学文章了,为什么你会看到这篇文章?显然是你还没学会,或者忘记了,或者想深究一下。好消息是,这篇文章,绝对会与众不同,一定会让你对Handler机制有更深入的理解。不信?你就读读看
本文将立志成为全网最通俗易懂的Handler机制教学文章!!!
你将会看到与众不同的关于ThreadLocal的讲解!
你将会看到与众不同的Handler机制源码剖析!
流程图贼多的文章,喜欢看图的朋友一定不能错过!
一. 机制介绍
以终为始,还是要问一问为什么,即我们为什么要学习Handler消息机制,Handler消息机制有什么作用?
首先,你要知道,Handler消息机制和Handler不一样哦,Handler只是指Handler这个具体类,而Handler消息机制是包括Handler在内的一系列相关类组成的一个机制。
Handler消息机制有四大作用
- 是整个App运行的一个基础
- 进行线程间的通信
- 发送延时任务
- 进行性能优化
下面,分别介绍一下这四个作用
1. 为什么说是整个App运行的一个基础?
我们看下面这个代码
ActivityThread的main
方法,是App的程序入口。在入口里面,调用了Looper.loop
方法,开启了死循环,使得App得以正常运行,而Lopper又是Handler消息机制里面一个很重要的角色,所以说它是整个App运行的基础
2. 关于线程间通信
线程间通信,可谓是Handler的看家本领。它最核心的功能,就是线程间通信,比如子线程向主线程进行通信,以解决子线程无法更新UI的问题。或者主线程向子线程进行通信,以执行一些耗时任务。
3. 关于发送延时任务
Handler可以通过一系列xxxDelay
方法,发送延时任务。
4. 关于进行性能优化
可以通过IdleHandler,或者通过重启消息循环以解除App崩溃等方式,进行App的性能优化
二. 角色介绍
Handler消息机制里面,有几个非常关键的角色,需要逐个捋清。下面,咱们挨个说说
1. Message
它是具体的消息类。看源码,对Message类的定义
总结一下,这个源码定义
(1)Message是可以被发送给Handler来进行处理的一个类
(2)这个类里面包含了一些描述信息(其实就是它的what
属性),和一些数据对象(其实就是它的data
属性)
(3)获取Message,建议使用Message.obtain()
或者Handler.obtainMessage()
方法来获取,以实现Message的复用效果
除了这些,还有一些值得注意的点
(1)target
属性,存储Message对应的Handler
(2)when
属性,确定Message的执行时间
(3)setAsynchronous
方法,设置Message是否异步
总之,关于Message,可以这样描述
Message是一个消息类,可以被Handler放到消息队列,也可以被取出,交给Handler来处理。它的what、when、data、target、setAsynchronous等一些属性和方法比较常用,同时它还实现了缓存复用,可以通过obtain来复用Message
2. Handler
老样子,还是看源码的定义
这个源码其实告诉了我非常多的东西。建议仔细研读一遍。我试图做一个归纳
(1)Handler唯一绑定一个线程,同时也绑定其MessageQueue、Looper,属于多对一的关系,即一个线程可以对应多个Handler
(2)主要有两个作用,一是调度Message/Runnable,二是进行线程间通信
针对前者,列举了一些调度Message/Runnable的具体方法,还提了一下延时任务
针对后者,说到主线程会专门维护一个MessageQueue,子线程可以通过Handler和主线程通信
总之,关于Handler,可以这样描述
Handler有两个作用,一是调度Message/Runnable(包括分发和处理),二是进行线程间通信。它和线程、MessageQueue、Looper是多对一的关系
3. MessageQueue
从源码中,我们可以了解到
(1)它持有一个会被Looper分发的Message的列表
(2)Message由Handler添加,而非MessageQueue直接添加
(3)可以通过Looper.myQueue()
来获取当前线程的MessageQueue
总之,关于MessageQueue,可以这样描述
MessgaeQueue是一个单链表构成的优先级队列,元素为Message,根据Message的when属性来确定优先级。由Handler来添加Message,由Looper来取出Message
4. Looper
源码依然告诉了我非常非常多的信息,让我试图归纳一下
(1)此类是用来开启,一个线程中,Message的循环的
(2)线程默认是没有消息循环的,需要手动创建,可以通过调用prepare
和loop
,来开启循环
(3)与Looper交互最多的是Handler
(4)给了一个典型的实现了消息循环的线程案例
总之,关于Looper,可以这样描述
可以理解为一个消息循环器,从MessageQueue中取出消息,交给Handler执行。创建方式是prepare,开启循环的方式是调用loop方法
5. ThreadLocal
这里的源码,略显难懂,但我也试图归纳一下
(1)ThreadLocal是与线程相关的,可以提供线程局部变量
(2)举了一个例子,说明ThreadLocal是如何提供线程局部变量的
只看这些,基本很难懂。因地制宜,对于理解ThreadLocal,我们或许可以不看源码的定义,转而看他的使用。也就是它的get
方法,和set
方法。
我们只看get
,因为看懂了get
方法,也就能看懂set
方法了
ThreadLocal的get方法
看get
方法就很清晰,比如我们调用ThreadLocal.get()
方法,它会这样执行:
首先拿到调用这个方法的线程,然后从这个线程中取出ThreadLocalMap对象,然后将ThreadLocal作为key,找到它对应的value,从而作为返回值返回。
那么这里,问题来了,ThreadLocalMap是什么呢?先不看源码,只看名字,大体能猜到,这是一个Map集合,那么既然是Map集合,key和value又分别是什么呢?我们进去看一下
可以看到,key就是ThreadLocal本身!
所以,我总结一下。
在每次调用ThreadLocal的get方法时,会首先拿到当前执行的线程,这个线程里面有一个变量,是ThreadLocalMap类型的,key是ThreadLocal,value就是我们想要得到的值
画图来看,就是这样
剖析ThreadLocal原理
又有一个问题,同一个ThreadLocal,在不同的线程,调用get方法,得到的value值是不一样的吗?
答案,是的。因为这就是ThreadLocal机制的作用呀,它的作用就是保存线程本地值,在不同的线程,需要映射为不同的值。它的原理,就是因为不同的线程,有不同的ThreadLocalMap ,即虽然key一样,但是Map不一样,所以value可以不同
类比我们生活中的例子,就像是我们同一个人(同一个ThreadLocal),在不同的环境(不同的Thread),承担着不同的角色(映射出不同的value)。比如我们在家这个环境中,承担着孩子的角色,为人父母的角色,在职场这个环境中,我们就承担着职工,同事的角色。真可谓是"艺术来源于生活,高于生活"呀
所以,我说,不同的线程之间Looper可以不同,是通过ThreadLocal来保证的,这句话,相信你也能很快理解了。就是因为不同的线程,ThreadLocalMap不同而已。
ThreadLocal总结
综上,关于ThreadLocal,可以这样描述
它能够映射线程本地变量,映射的原理,就是不同的线程,ThreadLocalMap不同。
总结
ok,以上就是针对Handler机制几大角色的非常详细又通俗易懂的解释。这些都只是一个开胃菜,更详细,更有含金量的,还在后面。
接下来,让我们来介绍一下,关于Handler机制的运行原理。打起精神来!各位!!!
三. Handler消息机制的运行原理
在此部分,我会先捋源码,然后给出一个非常详细的图加深理解,以此来讲清楚Handler消息机制的运行原理
一图以蔽之
这张图,可以说非常详细、生动地展现了Handler机制整体的运行原理。再结合上文,对各个角色的解释,相信你很容易就能看懂了。那么,对自己要求高的人,肯定还是不满于此,是不是还想扒开源码看看?OK,我来满足你的需求。
Handler消息机制源码
看源码,有个问题,即我们看的线索,怎么找,我们从哪一截开始探入,会比较好理解,不会迷乱在纷乱的源码世界中?这个,不同的人有不同的理解,我认为,可以从Handler放消息,即我们调用sendXXX、postXXX
方法开始看起,从Looper交给Handler进行消息处理作为结束,正好按照时间顺序,同时也形成闭环。
所以,先从这里看起
Handler怎么放的消息
也就是图中红色区域
在前文,Handler源码的定义中,有介绍到Handler发送消息的几种方式,我再贴一下
一般来说呢,我们使用post、sendMessage
这两种方法会多一些,看方法名我们也可以猜出来,其他几种方法,基本都是这两种方式的变形体,所以呢,我们只关注post
方法,和sendMessage
方法。
sendMessage方法的执行过程
由简到繁,我们先看sendMessage方法的执行过程
这里调用了sendMessageDelayed()
方法,将msg传进去了,然后有一个delayMills
为0,这个delayMills
的意思就是延时任务的延时时间,前面也介绍了,Handler有一个功能是可以执行延时任务。那么延时的时间,就是sendMessageDelayed
的delayMills
参数。
那进去sendMessageDelayed
方法
这个方法又调用了一个sendMessageAtTime
。
传入了一个uptimeMills
参数,这个参数,是之前的delayMills
,加上启动后的时间。所以前面的delayMills
是一个相对时间,然后再加上SystemClock.uptimeMillis()
,组成了一个绝对时间uptimeMills
。(其实这在后面会被赋值为Message的when
属性)
然后调用了Handler的enqueueMessage
方法
最后,调用到了MessageQueue的enqueueMessage
方法。自此,就把Message放入消息队列了。
一图以蔽之
让我来总结一下,调用sendMessage
,需要传入一个Message对象,然后通过层层传递,最终调用到了MessageQueue的enqueueMessage
方法,将Message加入消息队列。
post方法的执行过程
看完sendMessage
,我们再看post
方法。
一般来说呢,我们是这样调用post
方法的
我们进去post方法,看一看
what???它居然又调用了sendMessageDelayed
方法!我们需要看下这个getPostMessage
做了啥
哦~,原来它把Runnable封装为了Message,并且将自身作为了Message的callback
属性!
后续流程都是一样的了,就不继续跟了。
一图以蔽之
好,以上我们就把Handler将Message传递给MessageQueue的过程,捋清楚了。
MessageQueue怎么调度的消息
接下来,我们看MessageQueue是怎么把Message加进去的,怎么把Message取出来的,即图中的这一部分
MessageQueue的放Message的方法,是enqueueMessage
,看名字就知道,它的意思是将Message放入队列,里面一定包含了一些策略。MessageQueue的取出Message方法,是next
。下面,我们还是按照时间顺序,先enqueueMessage
方法,然后再看next
方法。
enqueueMessage方法的执行过程
enqueueMessage
方法,有点大,截图截不开,我就直接贴代码了。别担心,我会在关键地方,留下注释
java
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
// 不允许发送target为null的消息,也就是说这个方法放不了同步屏障消息
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when; // 这里将when赋值给了message的when属性
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// 把传入的message,作为新的头节点
msg.next = p;
mMessages = msg;
needWake = mBlocked; // 如果之前是阻塞状态,则唤醒
} else {
// 下面的英文注释说的非常详细,我可以再试图总结一下
// 只有队列的头节点为同步屏障消息,并且当前message,是最早加进来的异步消息时,才有可能需要唤醒
// 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;
}
}
// 把传进来的message插入了队列中
msg.next = p;
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
建议你仔细看几遍以上代码,然后,看下面的流程图
一图以蔽之
此方法执行完后,如果一切正常的话,就可以将Message入队了。
next方法的执行过程
下面,我们来看,从MessageQueue中取出Message的部分,也就是next
方法的执行过程
一样的,我还是直接贴代码,不截图了,因为一屏截不下
java
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
// 如果应用程序试图在退出后重新启动looper(这是不支持的),就会发生这种情况。
final long ptr = mPtr; // mPtr是native代码使用的
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0; // 阻塞时间
for (;;) { // 注意,这里是一个死循环
if (nextPollTimeoutMillis != 0) {
// 将当前线程中挂起的Binder命令刷新到内核驱动程序。
// 在执行可能会阻塞很长时间的操作之前调用此方法非常有用,
// 以确保所有挂起的对象引用已被释放,以防止进程持有对象的时间超过所需时间。
// 总之,这个方法在执行阻塞任务之前调用会好,又因为下面有可能会阻塞,所以在这里调用了这个方法。
Binder.flushPendingCommands();
}
// 阻塞,阻塞时长为nextPollTimeoutMillis
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
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());
}
// 此时,msg要么为null,要么为队首消息,要么为第一个异步消息
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 {
// -1表示一直阻塞
nextPollTimeoutMillis = -1;
}
// 当处理完所有该处理的message时,才去处理要不要quit
if (mQuitting) {
dispose();
return null;
}
// idle handles 只能在MessageQueue为空,或者队首的Message的when不符合要求时,才会执行
// 下面的英文其实说的更详细
// 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();
}
// 如果第一次循环,处理了mIdleHandlers里面的任务,那么第二次循环时
// pendingIdleHandlerCount为0,所以直接continue,而不会重新处理mIdleHandlers
// 注意:这里的循环指的是某一次next方法执行时,里面for死循环中的一次循环。
// 而又因为next方法会执行多次,所以第二次next方法执行时,又会重新启动一次for死循环,
// 那么这时,还是会判断是否要处理mIdleHandlers里面的任务。
if (pendingIdleHandlerCount <= 0) {
// No idle Handlers to run. Loop and wait some more.
mBlocked = true;
continue; // 注意一下这个continue。
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 运行IdleHandler里面的代码,只有第一次迭代的时候会执行到这里
// 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);
}
}
}
// 注意这里,不管上面的mIdleHandlers是否还有元素,都重置。因为queueIdle都已经执行过一次了
// Reset the idle Handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// 设置阻塞时长为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;
}
}
一图以蔽之
出现了!!!你绝没见过的流程图!!! 这个就稍显复杂了,不过多看几遍代码,和我画的流程图做对照,相信你很快就能理解。
需要注意的点,是IdleHandler。在这里,可以看到,它执行的时机,是取不出合适的Message,在阻塞线程之前,执行。同时,执行完之后,会刷新阻塞时长。
所以在这里,它也有坑,
一是有可能永远都不会执行,因为有可能一直都能取出合适的Message。
二是有可能delay正常的Message。比如队首的Message,需要等待1ms就可以执行,然后在阻塞1ms之前,发现有idleHandler可以执行,那么就去执行idleHandler,如果执行idleHandler的耗时为10ms,那么就delay了队首的Message的执行时机,delay了9ms。
尽管它有坑,但还是会在性能优化的场景中用到,用于在线程空闲时执行一些优化任务。
好,以上,关于MessageQueue的拿到Message,和交付Message,就全部介绍完了。
Looper怎么调度的消息
下面介绍Looper的拿到Message,和交付Message的过程,也就是这一部分
如何拿到消息
拿到Message,是在开启消息循环,即Looper.loop
中实现的
可以看到这个方法的注释,谷歌官方特意提示,一定要调用!所以我们在开启子线程的消息循环的时候,一定要调用Looper.loop
方法。里面有一个核心逻辑,是死循环 ,每一次循环,会调用loopOnce
方法,让我们进入到这个loopOnce
方法中,看一看
java
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
Message msg = me.mQueue.next(); // 这里有可能阻塞
if (msg == null) {
// msg为null,只有两种情况,即MessageQueue正在退出或者已经退出
return false;
}
......
msg.target.dispatchMessage(msg); // 把message交付给Handler
......
msg.recycleUnchecked(); // 回收message,缓存起来,以便下次复用
......
return true;
}
在每次loopOnce
中,一切正常的话,就可以取出一个消息。
如何交付给Handler的
把message
交付给Handler是怎么交付的呢?Message的target
属性就是Handler,所以在这里就是调用了Handler的dispatchMessage
方法,进去看下
这里就非常明朗了,在这里,进行最终关于message的处理,然后形成了闭环。
一图以蔽之(Looper的loop)
细看Handler对Message的处理
其实,这里值得再强调下,我们看到,这里有三种方式来处理message,分别是
我们一个一个地看。
处理方式一:handleCallback方法
第一个,handleCallback
,是Message的callback
属性不为null时,调用。在前面的内容中有过介绍,Message的callback
属性,就是调用Handler.post()
时,传入的Runnable。可以看下图
所以,如果此Message,是通过Handler.post
方式传进来的,那么就直接执行Runnable里面的run
方法。
这是第一种处理message的方式。
处理方式二:mCallback的handleMessage方法
第二种,是看Handler的mCallback
属性是否为null,如果不为null,则调用mCallback
的handleMessage
方法。这里的mCallback
和前面说的Message的callback
属性不同。这里的mCallback
是创建Handler的时候,通过构造方法得到的,
这个Callback和Runnable没有关系,而是一个自定义的接口,里面有一个方法,是handleMessage
所以第二种处理方式,是调用mCallback
的handleMessage
方法。如果返回了true,则处理结束,如果返回了false,那么就进入到第三种处理方式,即handleMessage
处理方式三:子类实现handleMessage方法
这里默认实现为空,所以此方式,一般是调用的子Handler的handleMessage
方法,来处理的Message。
一图以蔽之
为什么分了三种处理方式?
我个人认为,有两个原因。
(1)保证消息最终能够进行处理
Handler可以通过多种方式进行创建:比如可以直接new,或者new一个子类,同时呢,Handler的构造函数还有好几种。每一种创建方式,对应的消息处理方式,不一定是相同的。所以,分了三种,原因之一,是要把所有的消息处理方式全部覆盖到,尽可能防止有消息处理不到的情况。
(2)有些场景,可能需要对message多次处理,有第二种和第三种处理方式,可以满足此需求。
在代码上,关于第二种和第三种处理方式,分别是这样实现的
java
// 第二种处理方式,赋值给Handler的mCallback属性
val Handler1 = Handler(object : Handler.Callback {
override fun handleMessage(msg: Message): Boolean {
return true/false
}
})
// 第三种处理方式,派生子类,重写handleMessage方法
val Handler2 = object : Handler(){
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
}
}
这两种处理方式有一种关系,就是如果第二种处理方式返回了false,那么第三种处理方式还要执行。即有可能两种处理都会执行,这在某些降级处理场景,可能会被用到。
但其实呢,谷歌提示我们尽量避免创建子类,进而避免调用子类的消息处理方法,而优先用前两种处理Message的方式。原因来自这个注释:
在Callback的类注释中,有"尽量避免不得不实现Handler的子类"这样的字眼,所以得出了上面那个结论。
所以,为什么分了三种处理方式,总结下,就是两个原因
- 保证消息最终能够进行处理
- 有些场景,可能需要对Message多次处理,有第二种和第三种处理方式,可以满足此需求。
以上,就把Handler机制的全貌,都捋了一遍了。
三. 加餐:Handler那些事儿
1. 为什么子线程不能访问UI,非要整一个Handler?
因为在Android中,关于UI操作,是没有加锁的,所以不是线程安全的,所以只能是单线程访问才可以。因此Android只能单线程修改UI,而且这个单线程只能是主线程。
那么,为什么不加锁呢?
我能想到的一个很重要的原因,就是流畅性,UI修改一定要是瞬时就要变化的,如果有多线程互相抢着锁,那UI的访问及修改效率都会低很多,手机就会表现的很卡。
而且像UI这种,非常基础的能力,没必要做的这么复杂,如果访问一个UI控件,修改一个UI控件都要加锁的话,那就太复杂了,完全没必要。
总结
Android只能允许单线程修改UI,然后设计了Handler机制来实现子线程修改UI的需求。
2. MessageQueue的阻塞唤醒机制是如何实现的?
其实就是:epoll、pipe机制
什么是pipe?
在类Unix-like操作系统中,一切皆文件,包括管道pipe。管道可以像文件一样在文件系统中存在,并且可以使用文件描述符来引用它们。
管道的作用之一,就是可以实现,线程间的通信。一个线程与另外一个线程之间发生的读、写操作,都可以通过管道,来实现。
什么是epoll?
epoll 是 Linux 中的高效的 I/O 多路复用机制,它会监听一个或多个文件描述符的多个事件类型,其中之一是文件描述符的写入事件。
epoll如何实现阻塞、唤醒?
epoll能够实现阻塞、唤醒的原理,就是能够监听管道这个文件描述符的读操作和写操作,实现阻塞和唤醒。
比如,一个MessageQueue中,没有了消息,那么就应该被阻塞了,那么怎么保持一直阻塞的?什么时候会被唤醒?答案就是:epoll可以监听,pipe的文件描述符的写操作。
当有新的消息写入时,epoll就可以监听到,从而通知线程,实现唤醒。没有写入时,就能保持一直阻塞。
因此,可以这么理解:在没有数据可读时,MessageQueue主要使用了 native 层的 epoll 机制来监听文件描述符(通常是pipe)的写入事件,以实现持续的阻塞和唤醒。
一个更好理解的例子
举个生活中的例子,当有人给我打过来电话时,我的手机会通过亮屏,响手机铃声或者震动等方式来通知我。
OK,这里面,包含了两个方面。一是可以打电话,有电话线这条"管道"。二是可以通知我,让我知道,有人打电话过来了。
在这里,电话线就像是pipe,通知就像是epoll。在没有电话打来时,且我当前没有正在接听电话时,属于"读阻塞"的状态,此时如果有写入操作,则会进行唤醒。这里的写入,就是有新的人给我打来了电话,然后epoll通过通知我,唤醒了我,然后我就可以接听电话,即对写入的这条消息进行处理。同时呢,手机不光是在收到电话的时候可以通知,在比如收到微信视频通话的通知时,来了一条短信时,都可以发起通知,这对应epoll的可以监听多个文件描述符的事件的特点。
在MessageQueue中,哪些地方进行了阻塞
在next
方法中,总共有两个地方,对mBlocked
进行了赋值。
- 一是消息正常取出时,将
mBlocked
赋值为false
,表示不阻塞。 - 二是当没有可取出的消息,且也没有可以处理的IdleHandler时,赋值为
true
,表示阻塞。
3. 同步屏障消息和异步消息?
同步屏障消息即Message的target
属性为null
异步消息即Message的isAsynchronous
返回true
两者之间是如何运转的?
在没有遇到同步屏障消息时,同步消息和异步消息都是正常在MessageQueue上面根据when
属性排列,依次取出,执行。然而,当遇到同步屏障消息时,就会向后遍历,找到第一个异步消息 ,然后根据when
是否满足要求,决定如何处理。
这里呢,这种机制,一般会用在优先级比较高的,需要尽快执行的任务,比如绘制UI(draw、invalidate、requestLayout
等方法)
4. 消息循环如何退出
其实这块的内容非常多,完全可以单拎出一篇文章来。我们先从入口说起。
退出,是通过调用Looper的quit
或者quitSafely
来实现的。
两者都是调用了MessageQueue的quit
方法,只是传递的参数不同。让我们进去MessageQueue的quit
来瞧一瞧
safe
参数,顾名思义,就是描述是否安全退出。在这个方法中,首先会判断,是否允许退出,如果不允许,则抛出异常。如果允许,则看当前是否已经在退出中,如果是的话则return,不是,则继续。这里就用到传过来的参数safe
了。如果是true,则代表安全退出,否则是非安全退出。让我们看下这两个方法有什么区别?
非安全退出
先看非安全退出,也就是removeAllMessagesLocked
方法,它是找到Message队列的头节点,然后依次向后遍历,把所有的message
全部回收。非常简单粗暴
安全退出
如果是安全退出
在这里,它会判断队首的message
的when
属性,和now
的大小。如果now
小于when
,则说明队首的消息不满足被取出的条件,则调用removeAllMessagesLocked
方法,将所有的消息全部回收。否则就向后遍历到第一个不符合取出要求的message
,将它以及它后面的message
全部回收。 也就是说,when
满足要求的Message,还是会被Looper正常取出然后交给Handler进行消费。
回收Message后,做了什么?
然后,再回到MessageQueue的quit
方法来,我们看到,在回收完之后,还调用了一个nativeWake
方法
这里的目的其实是唤醒所有阻塞的线程,以免引起不必要的等待,因为消息循环,都已经退出了,所有该回收的消息都回收了,这时候就应该考虑释放线程的一些资源了,此时有可能有一些阻塞的线程,所以要把阻塞的线程全部唤醒,让他们知道,"消息循环已经退出了,你别再等了"。防止这些线程,无限期地阻塞下去。
一图以蔽之
退出之后,如果再继续放入消息,会发生什么呢?
我们知道,Handler的放入消息操作,最终会调用到MessageQueue的enqueueMessage
方法
在方法里面,会判断,mQuitting
是否为true,如果为true,则直接回收这个消息,然后返回false
所以这时候再放入消息,是不会被取出的,而是放一个回收一个。
退出之后,Looper的loop方法会发生什么呢?
我们都知道Looper的loop
是进行消息循环的方法。当退出的时候,MessageQueue的next
方法会返回null
此时,loopOnce
方法会返回false
然后,在loop
里面,当发现loopOnce
返回false时,直接return
从而退出消息循环
5. HandlerThread是什么
看类的定义
我们可以发现,HandlerThread是一个线程,并且此线程具有消息循环的能力。也就是说,它是一个封装了Handler机制的线程。
在run
方法里,有一个注意点,就是,给mLooper
赋值后,调用了notifyAll
方法进行唤醒。那么,是在哪里进行的阻塞呢?
啊哈!就是这里,当调用getLooper
方法,获取Looper的时候,如果发现mLooper
为null,则阻塞,直到它被成功赋值,才会唤醒阻塞在这的线程。
6. IntentService是什么
直接从类注释中找答案,最简单也最准确!
下面看下这个类的具体内容
java
@Deprecated
public abstract class IntentService extends Service {
private volatile Looper mServiceLooper;
@UnsupportedAppUsage
private volatile ServiceHandler mServiceHandler;
private String mName;
private boolean mRedelivery;
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
public IntentService(String name) {
super();
mName = name;
}
public void setIntentRedelivery(boolean enabled) {
mRedelivery = enabled;
}
@Override
public void onCreate() {
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
@Override
public void onDestroy() {
mServiceLooper.quit();
}
*/
@Override
@Nullable
public IBinder onBind(Intent intent) {
return null;
}
@WorkerThread
protected abstract void onHandleIntent(@Nullable Intent intent);
}
我们通过类注释,和类内容,可以总结出
它是个Service,并且内部维护了一个HandlerThread。它能够在子线程处理任务,并且只有一个子线程,同时只能处理一个子任务,并且当所有任务处理完时,自动关闭。
所以,它是一个可以在子线程执行耗时任务,同时只能执行一个任务,并且在所有任务执行完后,可以自动停止的Service。
7. Handler内存泄漏
什么是内存泄漏?
内存泄漏的根本原因,是这样的:一个长生命周期的对象,持有着一个短生命周期的对象的引用,同时此长生命周期的对象又被其他对象引用着,向上能够追溯到GC Root,从而导致这个短生命周期的对象无法在合适时机回收。
Handler发生内存泄漏的原因?
那么在Handler这里,很常见的一种内存泄漏的场景,是这样的:
Handler(长生命周期对象)持有了Activity(短生命周期的对象)的引用,同时Handler向上能够被主线程(GC Root)引用着,导致Activity无法在合适时机进行回收。
那么这个引用链,是这样的:
主线程-ThreadLocal-Looper-MessageQueue-Message-Handler-Activity
所以导致有可能发生内存泄漏。
如何解决Handler的内存泄漏问题?
解决方案有三个:
(1)不要用非静态内部类来使用Handler,不然Handler就会持有Activity的引用。
可以用静态内部类或者独立的外部类的方式,创建Handler,这样创建的Handler就不会自带Activity的引用了
(2)如果非要持有Activity引用,那么可以用弱引用包裹Activity
因为弱引用在垃圾回收时是一定会被回收的,所以可以解决内存泄漏的问题
(3)在短生命周期对象(比如Activity)结束时,释放Handler。
这种方式虽然可以解决,但是不太推荐,因为难以维护,容易忘记。如下:
java
@Override
protected void onDestroy() {
if(mHanlder != null){
mHandler.removeCallbacksAndMessages(null)
}
super.onDestroy();
}
8. 为什么死循环不会导致ANR?
来了来了,非常经典的问题!!!
为什么消息循环是死循环?
死循环是为了能够保证线程能持续运行下去,而不是执行完就退出了。
为什么这个死循环没有导致ANR?
这里的死循环并不满足ANR的条件。Android的ANR机制,它会检测主线程是否在一定时间内无法响应用户输入。而在消息循环这里,用户在屏幕上操作后,都会通过立即发送一个消息或者其他方式,响应用户的输入。所以这里并不满足Android认定的符合ANR的标准。所以不会ANR。
所以,这是两个维度,死循环只是为了能够让程序一直运行下去。和ANR没有关系。
所以真正触发ANR的并非Looper的死循环,而是某一次消息执行时阻塞了很久,即run
方法或者handleMessage
方法停滞了很久,比如在run
方法或者handleMessage
方法里面有一个死循环,导致主线程无法在一定时间内响应用户输入。
死循环,不会很耗费资源吗?
在这里也不会很消耗资源,因为在空闲时间会进行阻塞,在需要的时候会进行唤醒。就算有阻塞,用户做了什么操作,也是可以立刻向线程的MessageQueue发送消息,并唤醒线程的。
总结
以上,我就把关于Handler机制的核心知识全部介绍完了,此文花费了我大量精力,如果能对你有所帮助,那是我的荣幸!同时,如果有什么问题,也欢迎在评论区讨论。
下一篇文章,我将会着重介绍另一个Android非常重要的知识点:RecyclerView的回收复用机制。如果你觉得我写的还不错,可以点赞加关注哦~感谢你的支持!
好文推荐
在写此文时,我参考了以下几篇文章,强烈推荐大家阅读!