1、choreographer你知道吗?
2、消息队列内部怎么排序?
3、looper源码你说一下?
4、handler,looper和线程的关系?
5、为什么一个线程只能一个looper,你知道threadlocal吗?
6、主线程为什么没有被loop阻塞?
7、Handler 同步消息屏障了解吗?
8、一个线程有几个Handler?
9、Handler内存泄漏原因?
10、为什么主线程可以直接new Handler?
11、子线程中维护的Looper,消息队列无消息的时候处理方案是什么?有什么用?
12、多个Handler往MessageQueue中添加数据,发消息时各个Handler可能处于不同线程,内部是如何确保线程安全的?
13、使用Message时如何创建它?
14、IdHandler?
15、post()与sendMessage()区别
16、IntentService是什么?
17、子线程中有几行代码需要在主线程执行,有几种方法?
18、handler延时操作
19、Android消息机制解析(native层)能说一下吗?
20、 对调用sendMessageDelayed(xxx, 5秒)发送了一个Message, 这个5s 是怎么实现的
1、choreographer你知道吗?
2、消息队列内部怎么排序?
Handler中的消息队列,也就是MessageQueue,从名字看是一个队列,但是底层是单链表结构,因为单链表结构比较适合插入和删除操作。
入队操作是通过final类MessageQueue的enqueueMessage方法实现的,换句话说就是MessageQueue.enqueueMessage()向消息队列添加消息,android9.0的完整代码如下:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
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;
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;
}
可以看到
1、入参时候的 when 参数,就是我们平时调用 sendMessageDelayed 方法时传入的延时 + 当前系统时间,为了方便起见,我们在后边的分析中只取延时,忽略系统时间
2、入队之前,先将当前消息的发送时间赋值为当前消息的 when 属性,即 msg.when = when
3、使用局部变量 p 来记录当前的消息,默认值为 null,然后开始逻辑判断
4、MessageQueue.next()从消息队列中获取消息使用for循环
消息入队逻辑推演:
在主线程发送几个消息如下:
mHandler.sendEmptyMessage(100);
mHandler.sendEmptyMessageDelayed(200, 1000);
mHandler.sendEmptyMessageDelayed(300, 500);
现在我们梳理一下这三条消息入队的顺序
1、第一条消息进入enqueueMessage方法,when=0
if (p == null || when == 0 || when < p.when){}
p==null判断成立,因为mMessages默认值为null,然后将p赋值给第一条消息的next属性(标准的单链表结构,利用next属性相连),然后将第一条消息赋值给全局的mMessages对象
2、第二条消息进入 enqueueMessage 方法,when = 1000
Message p = mMessages;
现在 mMessages 对象是有值的,是第一条消息对象,mMessages.next = null,mMessages.when = 0。
那么我们来看 if 判断条件:
1、p == null,不成立,当前 p 是 mMessages 对象,即第一条消息对象
2、when == 0,不成立,第二条消息的 when 应该是 1000
3、when < p.when,即 1000 < 0,不成立
三个条件都不能满足,那就来到 else 分支,我们继续一点点梳理:
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;
1、先声明一个 Message 对象 prev,用来标记前一条消息,即 previous message,然后开启一个死循环
2、在循环中,将当前的消息对象 p 赋值给 prev 变量,然后取 p 的下一个节点,赋值给 p,此时判断 p == null 是成立的,因为之前 p.next = null,现在 p = p.next,所以 p == null 也是成立的,跳出循环
3、跳出循环时,prev 是 mMessages 对象,即第一条插入的数据,p 是 mMessages.next,是 null
4、然后将待插入消息的 next 属性设为 p,即 null,然后将待插入消息赋值给 prev.next,即 mMessages.next,就将先后两条消息连接起来了
到现在为止,MessageQueue 的样子应该是:
mMessages = {Message(when = 0, next = {Message(when = 1000, next = null)})}
第三条消息进入 enqueueMessage 方法,when = 500
Message p = mMessages;
同样的,mMessages 不为空,指向首条插入的 Message 对象,when = 0,next 不为空,而传入的 when = 500,很明显 if 条件不能被满足:
if (p == null || when == 0 || when < p.when){}
// p 不为空,传入的 when = 500,而 p.when = 0,所以 if 条件不能成立
然后进入 else 分支:
Message prev;
for (; ;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
}
msg.next = p;
prev.next = msg;
依然是开启死循环,从 mMessages 开始依次向后遍历,但是与第二次插入不同的是,当遍历到第二条消息对象时,即 prev = 首条消息,p = p.next(第二条消息) 时:
p == null 不成立
when (500) < p.when (1000) 成立,跳出循环
跳出循环时,prev = 首条消息,p = 第二条消息
然后将待插入消息的 next 指向 p,即原本的第二条消息,将 prev 的 next 指向待插入消息,即将第三条消息插到了原本的第一条和第二条消息中间,到此为止也就完成了消息的插入。
小结:
当我们顺着代码逻辑,完整地走过这几次消息入队后,也就很清楚的看到 MessageQueue 的链表本质了。
概括地来说,MessageQueue 只持有一个 mMessages 的消息对象,然后利用 Message 的 next 属性进行多个消息之间的链接,同时使用 when 属性对消息进行排序,when 的值越小,在链表中的排序越靠前。
以我们上边的推演为例,MessageQueue 最后的形式应该是:
mMessages = {
Message(when = 0, next = {
Message(when = 500, next = {
Message(when = 1000, next = null)
})
})
}
回答问题二参考:https://www.jianshu.com/p/9efe3b48b730
Android消息队列中的Message入列和出列,都是基于单链表实现,排序的核心变量就是Message内部的when变量,when变量是一个时间戳,由Handler给该变量赋值,延迟消息,定时消息,都是根据when变量来实现的。在分析队列的逻辑时,发现了和jni部分的通信,主要是nativePollOnce,nativeWake方法,这两个方法实际是实现了空队列阻塞,以及唤醒功能,底层使用epoll机制实现。
拓:为什么 MessageQueue 要使用链表的形式来存储?
其实我们出去面试经常会被问到类似的问题,本质上这个问题就是:数组和链表的区别在哪里?
数组的优势在于使用角标查找方便,时间复杂度为 O(1),但是插入的时候就会很费劲,要把整个数组依次向前向后移动来为新插入的数据腾出空间,时间复杂度为 O(n);相反的,链表的优势却恰恰在于插入。
如果你看完了上面的文章,你会发现我们使用链表插入数据的时候,只需要将原本的 next 指针换一个指向,再将新数据的 next 指向原数据的 指向就可以完成插入,整个时间复杂度也是 O(1) 级别的;但是我们在找目标位置的时候就太痛苦了,需要一个死循环来从头开始遍历 Message 的 next 对象,知道找到目标对象才能完成插入,如果就是要插入到最后一个位置,那整个时间复杂度也是 O(n) 级别的。
而 Handler 的使用场景正是频繁插入,但是每次都只取最前面的消息处理,所以对于这种情况,天然的应该使用链表来进行存储而不是数组。
3、looper源码你说一下?
1、首先看一下looper是如何初始化的
Handler初始化的时候,有一段注解说,Looper.myLooper()要获取到Looper,必须要先调用Looper.prepare(),而且Looper的构造函数是私有的,不能从其他类初始化,Looper的构造函数源码:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Looper.prepare()源码如下:
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.prepare(), 否则就会报 RuntimeException 。
如果是第一次调用,会创建一个 Looper 放到 sThreadLocal 中。
在构造 Looper 的时候,创建了属于这个 Looper 的消息队列 MessageQueue ,暂且不管,后面在细看。
2、开启消息循环
想要开启消息循环,需要通过Looper.loop(),loop()方法源码如下:
public static void loop() {
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) {
return;
}
//有省略代码
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
//有省略代码
msg.recycleUnchecked();
}
}
虽然整个方法比较长,但是主干代码其实非常简单:拿到消息队列 queue 以后,进入死循环。
循环中就做一件事:从消息队列中获取到一个可以执行的 Message,接着调用这个消息的 target(即 Handler)的 dispatchMessage 方法,消息就分发出去了。批注上might block的意思就是没有消息了,就阻塞。
如何从队列中拿到一个可执行的消息,就要靠 MessageQueue 了。
3、消息队列:MessageQueue
构造Looper的时候,会创建消息队列,即MessageQueue,源码如下:
// MessageQueue.java
private native static long nativeInit();
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
这里就和 NDK 发生关联了,在构造方法中,通过本地方法 nativeInit 创建了一个对象 mPtr ,这个对象就是 NDK 层的 NativeMessageQueue 。
4、消息压入
在了解如何获取可执行消息之前,我们需要先知道消息是如何被压入队列的。
说明:Hanlder 的消息队列是一个单向链表队列,从队列的头部一直链接到队列的尾部。
还记得 Handler 发送消息的时候,最后调用的是 MessageQueue 的 enqueueMessage 吗?来看看这个方法(注:代码省略了一些异常判断片段):
// MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
// 省略一些代码...
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
//【1】拿到队列头部
Message p = mMessages;
boolean needWake;
//【2】如果消息不需要延时,或者消息的执行时间比头部消息早,插到队列头部
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 {
//【3】消息插到队列中间
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;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
主要分为3个步骤(见以上代码标注)。
1、 mMessages 是队列的第一消息,获取到它
2、 判断消息队列是不是空的,是则将当前的消息放到队列头部;如果当前消息不需要延时,或当前消息的执行时间比头部消息早,也是放到队列头部。
3、如果不是以上情况,说明当前队列不为空,并且队列的头部消息执行时间比当前消息早,需要将它插入到队列的中间位置。
如何判断这个位置呢?依然是通过消息被执行的时间。
通过遍历整个队列,当队列中的某个消息的执行时间比当前消息晚时,将消息插到这个消息的前面。
可以看到,消息队列是一个根据消息【执行时间先后】连接起来的单向链表。
想要获取可执行的消息,只需要遍历这个列表,对比当前时间与消息的执行时间,就知道消息是否需要执行了。
5、获取可执行消息
在 Looper.loop() 循环中,通过 queue.next() 来获取可执行消息,直接来看看这个方法。
// MessageQueue.java
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//【1】调用 NDK 层方法,线程阻塞挂起,进入等待
// nextPollTimeoutMillis = -1 时,进入无限等待,直到有人唤醒
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//【2】判断队列是否插入了同步屏障,是则只执行异步消息
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
//【3】消息没到时间,重新计算等待时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
//【4】消息时间已到,重新拼接链表,并返回该消息
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// 没有消息,进入无限等待
nextPollTimeoutMillis = -1;
}
if (mQuitting) {
dispose();
return null;
}
//【5】判读是否有空闲监听器,有的话,进行回调
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
// 只有for循环的第一次为 -1 ,
// 执行一次以后,pendingIdleHandlerCount变成 0,
// 不再执行空闲监听回调
if (pendingIdleHandlerCount <= 0) {
// mBlocked 为true,表明线程阻塞挂起
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// 回调空闲监听器
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);
}
}
}
// 这里设置为0,则上面的空闲监听器不再执行了
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
获取消息主要分为 5 个步骤(见以上代码标注):
1、进入 for 循环来遍历消息队列,调用 NDK 方法 nativePollOnce 挂起线程,第一次进入时,等待时间为 0 ,继续往下执行;
2、判断队列是否插入了同步屏障,是则只执行异步消息(同步屏障暂且放一边,后面详细介绍),如果没有同步屏障,则获取到的是队列头部消息;
3、对比当前时间与消息执行时间,如果时间没到,计算需要等待的时间,重新进入等待;
4、如果消息执行时间已到,重新拼接链表,并返回该消息,此时,Looper 将会得到一个消息,将它分发给 Handler 处理。
5、最后是一个空闲监听处理,作用是当队列中没有需要执行的消息时,说明线程进入空闲状态,这时候可以去执行一些其他的任务。
6、唤醒线程
当消息队列为空的时候,Loop 会进入无限阻塞挂起,如果这时候用户发送了一个消息,这时候如何唤醒线程呢?
细心的你可能已经发现了,在上面 enqueueMessage 的时候,有一个地方会唤醒线程,看回这个方法:
// MessageQueue.java
boolean enqueueMessage(Message msg, long when) {
// 省略一些代码...
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
//【1】拿到队列头部
Message p = mMessages;
boolean needWake;
//【2】如果消息不需要延时,或者消息的执行时间比头部消息早,插到队列头部
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 {
//【3】消息插到队列中间
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;
}
if (needWake) {
// 唤醒
nativeWake(mPtr);
}
}
return true;
}
注意到最后的这个 needWake 标志了吗?如果该变量为 true ,就会通过 NDK 方法 nativeWake 来唤醒挂起的线程。
两种情况会唤醒线程:
1、【队列为空 || 消息无需延时 || 或消息执行时间比队列头部消息早】 && 【线程处于挂起状态时(mBlocked = true)】
2、【线程挂起(mBlocked = true)&& 消息循环处于同步屏障状态】,这时如果插入的是一个异步消息,则需要唤醒
消息机制总结:
1、准备 Looper:Looper.prepare()
2、创建消息队列 MessageQueue:在构造 Looper 的时候被创建
3、创建 Handler: 用户自定义 Handler
4、开启 Loop 循环:Looper.loop()。循环遍历消息队列,判断是否到达执行时间
5、用户发送消息:通过 Handler 的 sendMessageDelay 等方法,将消息压入队列,等待被 Looper 遍历和执行
4、handler,looper和线程的关系?
首先看发起消息的入口–Handler的源码,看他是如何被构造出来的:
默认构造:
// Handler.java
public Handler() {
this(null, false);
}
public Handler(@Nullable Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
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.java
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 持有一个 Looper,Looper 持有一个 MessageQueue。最主要来看第一行代码 mLooper = Looper.myLooper();
// Looper.java
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
看到 ThreadLocal 马上就可以知道,Looper 是和线程相关的,同时也说明了一个线程中只会有一个 Looper 。
从注释中还能得到一个信息,如果要获取到一个非空的 Looper ,那么必须在这个线程中调用一次 Looper.prepare() 。从Looper.prepare()方法的源码,如下:
// Looper.java
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.prepare(), 否则就会报 RuntimeException 。
如果是第一次调用,会创建一个 Looper 放到 sThreadLocal 中。
在构造 Looper 的时候,创建了属于这个 Looper 的消息队列 MessageQueue 。
小结:
Handler 的消息最终在哪个线程中进行处理,与 Looper 有着直接的关联
从上面的分析知道,Looper 确实是和线程直接关联的。
5、为什么一个线程只能一个looper,你知道threadlocal吗?
ThreadLocal是用在多线程中,用于保存当前线程的上下文信息。在任意需要的地方都可以获取,在不同的线程中,通过同一个ThreadLocal获取到不同的对象。
原理如图:
ThreadLocal的实现原理:在每个线程中使用ThreadLocalMap将键值对<ThreadLocal,Object>保存在使用线性探测法实现的hash表中(HashMap是链接法实现的hash表)。
这个Thread就是线程对象,每个线程只有一个,可以通过Thread.currentThread()方法拿到,也可以在new Thread()时拿到,不管什么方式拿到的都是同个对象,这个对象里面有一个哈希表,每一个Entry的key就是ThreadLocal的弱引用,而value就是set进来的局部对象。
可以看到这个哈希表是线程唯一的,而里面的每一个Entry,对应一个ThreadLocal对象,比如说我在线程A中用到了3个ThreadLocal,都设置了不同的局部对象,那么这个哈希表就有3个Entry对象。
ThreadLocalMap:
上面所说的线程Thread对象中的哈希表,其真实类型就是ThreadLocalMap,它其实是一个简化版哈希表,初始容量为16,当然,只有真的放东西Entry数组才会初始化,threshold为容量的2/3,超过了就触发扩容,扩容也是比较简单的,就是直接扩大为原容量的2倍,至于哈希冲突问题,如果冲突就采用线性探测的方法解决。
这里还有个问题,就是如何根据key确定放到哪个桶里,可以看下代码:
int i = key.threadLocalHashCode & (len-1);
threadLocalHashCode在初始化时被赋值:
public class ThreadLocal<T> {
......
private final int threadLocalHashCode = nextHashCode();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
......
}
也就是说,这个threadLocalHashCode,就是0x61c88647的整数倍,然后跟len-1进行与操作,就得到了桶的下标,至于为什么是0x61c88647这个数值,网上有分析文章,有兴趣可以自己查看。
内存泄漏
根据上面的整体原理图,可以得到一条引用链:
Thread对象 --> threadLocals --> Entry[] --> WeakReference<ThreadLocal<?>>和value
因为Thread对象的生命周期是比较长的,只有当线程退出后,Thread对象才会被回收,那么在线程退出前,我们的ThreadLocal被WeakReference包着,如果外部没有强引用,在内存不足时gc会自动回收了,但那个value就不会了,需要我们自己手动去清空引用。
因此这里存在一个内存泄漏的问题,如果某个局部对象使用ThreadLocal保存了,然后用完之后没有清除掉,线程又还没退出,就可能导致内存泄漏,当然解决的方法也很简单,调用remove方法就可以:
sThreadLocal.remove();
另外,并不是说这种场景下我们不调用remove方法,value引用就一直不会被置空,ThreadLocal内部做了优化,在get和set时会主动置空那些key被gc自动回收的value引用,不过一般我们的ThreadLocal都是定义为static,这种情况就不可能会被gc自动回收了。
Netty的FastThreadLocal
如果同个线程用到了多个ThreadLocal,也就是Entry[]会有多个元素,那么每次在搜索某个ThreadLocal对应在哪个桶这个过程就会比较耗时,特别是对于服务端并发量很大的情况下,性能损耗会很明显,因此FastThreadLocal就做了优化,直接把ThreadLocal的哈希表去掉了,改为用变量index记录当前ThreadLocal对应的数组下标,也就是空间换时间,实现代码:FastThreadLocal.java,当然,在Android中对同个线程的ThreadLocal哈希表频繁操作这种场景可能并不多见。
在Looper.prepare()的源码中:
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是在sThreadLocal.set中初始化的,我们看looper是如何和线程绑定的,看ThreadLocal的set函数的源码:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
value就是looper,获取当前线程,然后获取当前线程对应的ThreadLocalMap ,我们发现Thread的源码里面有一个变量:
ThreadLocal.ThreadLocalMap threadLocals = null;
也就是说每个线程都有一个ThreadLocalMap ,用来保存上下文,一个线程对应一个map。ThreadLocal根据版本不同有不同,但是原理是一样的,ThreadLocal本身作为key,都是在每个线程中使用ThreadLocalMap将键值对<ThreadLocal,Object>保存在使用线性探测法实现的hash表。
ThreadLocal和Looper是一一对应的关系,Looper中ThreadLocal对象的源码如下:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
https://blog.csdn.net/wenyuan65/article/details/102633599
6、主线程为什么没有被loop阻塞
原回答地址:https://stackoverflow.com/questions/38818642/android-what-is-message-queue-native-poll-once-in-android
因为主线程负责绘制UI和处理各种事件,所以Runnable有一个处理所有这些事件的循环。循环由Looper管理,其工作非常简单:它处理MessageQueue中的所有消息。消息被添加到队列中,例如响应输入事件,帧渲染回调甚至您自己的Handler.post调用。有时主线程没有工作要做(即队列中没有消息),这可能发生在例如刚完成渲染单帧后(线程刚刚绘制了一帧并准备好下一帧,只需等待一段时间)。 MessageQueue类中的两个Java方法对我们来说很有趣:Message next()和boolean enqueueMessage(Message,long)。消息next(),顾名思义,接收并返回队列中的下一条消息。如果队列为空(并且没有任何内容可以返回),则该方法调用native void nativePollOnce(long,int),该块将阻塞,直到添加新消息。此时你可能会问nativePollOnce如何知道何时醒来。这是一个非常好的问题。将Message添加到队列时,框架会调用enqueueMessage方法,该方法不仅会将消息插入队列,还会调用native static void nativeWake(long),如果需要唤醒队列的话。 nativePollOnce和nativeWake的核心魔力发生在native(实际上是C ++)代码中。 Native MessageQueue使用名为epoll的Linux系统调用,该调用允许监视IO事件的文件描述符。 nativePollOnce在某个文件描述符上调用epoll_wait,而nativeWake写入描述符,这是IO操作之一,epoll_wait等待。然后内核从等待状态中取出epoll等待线程,并且线程继续处理新消息。如果您熟悉Java的Object.wait()和Object.notify()方法,您可以想象nativePollOnce是Object.wait()和NativeWake for Object.notify()的粗略等价物,因为它们的实现完全不同:nativePollOnce使用epoll,Object.wait()使用futex Linux调用。值得注意的是,nativePollOnce和Object.wait()都不会浪费CPU周期,因为当线程进入任一方法时,它会因线程调度而被禁用。如果这些方法实际上浪费了CPU周期,那么所有空闲应用程序将使用100%的CPU,加热并降低设备的速度。
循环的阻塞是通过NDK层,借助Linux的epoll实现阻塞等待功能。
拓:什么是epoll?
epoll 是 Linux 内核为处理大批量文件描述符而作了改进的 poll,是 Linux 下多路复用IO接口 select/poll 的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统 CPU 利用率。epoll 是 Linux 中用来监听 IO 事件的工具。一个进入等待的句柄,一旦监听到事件发生,就会被唤醒,继续往下执行。
7、Handler 同步消息屏障?
同步屏障是用来阻挡同步消息执行的。在日常使用中,很少去关心 Handler 的消息是同步还是异步,这是因为默认的消息都是 同步消息 。来看看消息的同步和异步是怎么设置的。
通过Handler设置
// Handler.java
public Handler() {
this(null, false);
}
public Handler(@Nullable Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
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 都是同步的,是否同步会保在 mAsynchronous 中。
当然有一个系统可以用,但对开发者不可见的方法,是可以创建异步 Handler 的。
// Handler.java
@UnsupportedAppUsage
public Handler(boolean async) {
this(null, async);
}
那么 mAsynchronous 是如何使用的呢?
来看看消息入队列的方法:
// Handler.java
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
没错又是这个方法,可以看到,如果 Handler 的 mAsynchronous == true ,消息就会设置为 异步 。
所以,开发者是无法通过 Handler 来实现异步消息的。
但是可以直接通过消息设置呀~
通过消息体配置
其实就是上面的方法啦:
msg.setAsynchronous(true);
同步消息屏障如何运作
让我们回到消息队列获取可执行消息的地方,看代码标注【2】:
// MessageQueue.java
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//【1】调用 NDK 层方法,线程阻塞挂起,进入等待
// nextPollTimeoutMillis = -1 时,进入无限等待,直到有人唤醒
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//【2】判断队列是否插入了同步屏障,是则只执行异步消息
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
//【3】消息没到时间,重新计算等待时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
//【4】消息时间已到,重新拼接链表,并返回该消息
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// 没有消息,进入无限等待
nextPollTimeoutMillis = -1;
}
if (mQuitting) {
dispose();
return null;
}
// 省略无关代码...
}
// 省略无关代码...
}
}
可以看到,当消息队列的头部插入了一个消息,并且这个消息的 target == null 的时候,就是启用了同步消息屏障。
这时候,会判断消息是否是同步的,如果是同步的则直接跳过,继续寻找队列中的异步消息。
换而言之,同步消息都被挡住了,只有异步消息可以执行。
如何启动/清除同步消息屏障
依然是在 MessageQueue 中:
/**
* @hide
*/
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
/**
* @hide
*/
public void removeSyncBarrier(int token) {
synchronized (this) {
Message prev = null;
Message p = mMessages;
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
if (p == null) {
throw new IllegalStateException("The specified message queue synchronization "
+ " barrier token has not been posted or has already been removed.");
}
final boolean needWake;
if (prev != null) {
prev.next = p.next;
needWake = false;
} else {
mMessages = p.next;
needWake = mMessages == null || mMessages.target != null;
}
p.recycleUnchecked();
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}
很简单,同步消息屏障就是根据屏障启动的时间,插入到消息队列中对应的位置,和普通的消息压入是类似的。
你可能已经注意到了,这几个方法对开发者都是不可见的,都有 @hide 的标记,也就是说,开发者是不被允许启动同步消息屏障的。
同步消息屏障的作用
如果你看过 Android 系统 View 的绘制流程,应该会知道 View 的绘制也是通过 Handler 来驱动的。
如果在启动绘制之前,用户(开发者)插入了一个非常耗时的消息到队列中,那就会导致 UI 不能按时绘制,导致卡顿掉帧。
同步消息屏障就可以用来保证 UI 绘制的优先性。
当你请求绘制 View 的时候,最终调用是在系统的 ViewRootImpl 中,来看下相关的代码:
// ViewRootImpl.java
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 启动同步消息屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
先启动同步消息屏障,之后 mChoreographer 中的消息都采用了异步的方式,保证消息的流畅,最终会回调 mTraversalRunnable 。最后在绘制时,解除同步消息屏障,详见以下代码:
// ViewRootImpl.java
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 清除同步消息屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
// 启动绘制流程
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
八、一个线程有几个Handler?
只要内存够用,想搞几个搞几个啊。
九、Handler内存泄漏原因?
如果直接在Activity中初始化一个Handler对象,会报如下错误:
在Java中,非静态内部类会持有一个外部类的隐式引用,可能会造成外部类无法被GC;
比如这里的Handler,就是非静态内部类,它会持有Activity的引用从而导致Activity无法正常释放。
而单单使用静态内部类,Handler就不能调用Activity里的非静态方法了,所以加上「弱引用」持有外部Activity。
private static class MyHandler extends Handler {
//创建一个弱引用持有外部类的对象
private final WeakReference<MainActivity> content;
private MyHandler(MainActivity content) {
this.content = new WeakReference<MainActivity>(content);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
MainActivity activity= content.get();
if (activity != null) {
switch (msg.what) {
case 0: {
activity.notifyUI();
}
}
}
}
}
转换成Kotlin:(Tips:Kotlin 中的内部类,默认是静态内部类,使用inner修饰才为非静态)
private class MyHandler(content: MainActivity) : Handler() {
//创建一个弱引用持有外部类的对象
private val content: WeakReference<MainActivity> = WeakReference(content)
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
val activity = content.get()
if (activity != null) {
when (msg.what) {
0 -> {
activity.notifyUI()
}
}
}
}
}
十、为什么主线程可以直接new Handler?
ActivityThread.java 的main()源码如下:
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
// It will be in the format "seq=114"
long startSeq = 0;
if (args != null) {
for (int i = args.length - 1; i >= 0; --i) {
if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
startSeq = Long.parseLong(
args[i].substring(PROC_START_SEQ_IDENT.length()));
}
}
}
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
已经执行了 Looper.prepareMainLooper(); Looper.loop(); ,也就是说系统已经帮我们完成了Looper的初始化操作。
十一、子线程中维护的Looper,消息队列无消息的时候处理方案是什么?有什么用?
根据MessageQueue.java的源码分析:
///5、如果是-1,表示无限等待,直到有事件发生为止。如果值为0,无需等待,立即返回。
///nativePollOnce会调用linux函数,最终会触发epoll机制,epoll机制中,在native层级也会有一个messageQueue,native层级的messageQueue调用epoll函数,根据传的timeoutMillis值(也就是之前几步中的nextPollTimeoutMillis)进行变化。
private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
private native static void nativeWake(long ptr);
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.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
**///4、睡眠,也就是线程挂起了。第三步开始新一轮循环之后,nextPollTimeoutMillis此时为-1**
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.
///2、如果没有消息了,nextPollTimeoutMillis 设为-1
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;
///3、nextPollTimeoutMillis此时=-1(第二步设置的),continue跳出此次循环,开始下一次循环
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;
}
}
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
///9、让Looper.loop()中的msg为空
if (mQuitting) {
return;
}
///10、mQuitting = true;
mQuitting = true;
///6、在调用looper的quit的时候,先把消息都recycleUnchecked()
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
///7、唤醒线程
nativeWake(mPtr);
}
}
private void removeAllMessagesLocked() {
Message p = mMessages;
while (p != null) {
Message n = p.next;
p.recycleUnchecked();
p = n;
}
mMessages = null;
}
源码中注解的5步骤解释了挂起过程。looper如何退出呢?在阻塞状态想停掉,首先要唤醒,在调用looper的quit的时候,先把消息都recycleUnchecked(),然后 nativeWake(mPtr)唤醒线程。
如果想退出for循环,一定是Looper中loop的msg返回空,源码如下:
///8、想退出for循环,一定是Looper中loop的msg返回空
public static void loop() {
///省略代码
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
///省略代码
}
怎么让msg为空?一定要走MessageQueue中的next中的
if (mQuitting) {
dispose();
return null;
}
同上面代码的批注9,此时看到批注10设置了mQuitting = true; ,那么这里返回的就是null,Looper.loop循环中的msg是null,也就直接return了。
子线程释放了资源,其他地方也可以调用子线程了。主线程能释放吗?肯定不能释放,因为,看MessageQueue.java的quit源码如下:
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
///省略代码
}
Main thread not allowed to quit. 主线程不允许退出,会抛出异常。这么设计的原因,看ActivityThread.java的handleMessage源码,看到Android是handler作为驱动的,AMS最终是用handler进行管理的,围绕handler来的,所以不可能被替换。当然四大组件的底层是binder。
十三、多个Handler往MessageQueue中添加数据,发消息时各个Handler可能处于不同线程,内部是如何确保线程安全的?
因为加了锁,看MessageQueue.java中的enqueueMessage源码:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
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;
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;
}
synchronized (this) ,synchronized 是一个内置锁,因为加锁和解锁都是由JVM完成的,
这里的this代表对一个线程的messageQueue访问的时候,其他的对象不能访问。
十三、使用Message时如何创建它?
涉及到享元设计模式,obtain,先看一下MessageQueue.quit中remove消息的源码过程如下:
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
///1、remove message
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
private void removeAllMessagesLocked() {
Message p = mMessages;
while (p != null) {
Message n = p.next;
//2、message进行了recycleUnchecked()
p.recycleUnchecked();
p = n;
}
mMessages = null;
}
//3、并不是真的将消息干掉,而是将消息里的内容都去掉。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 = -1;
when = 0;
target = null;
callback = null;
data = null;
///4、可以看到是头插,每生成一个节点放到头部
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
以上四步解释了MessageQueue.quit中remove消息的过程,下面看一下Message.obtain源码:
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();
}
从消息池取出一个消息,放到需要用到的链表,没有了创建和销毁的过程,避免内存抖动。
14、IdleHandler?
Q:IdleHandler 有什么用?
简单来说,IdleHandler 是 Handler 中提供的一种可以在 Looper 事件循环的过程中,MessageQueue 出现空闲的时候,允许我们执行一些任务的机制。
Q:IdleHandler 如何使用?
1、IdleHandler 被定义在 MessageQueue 中,它是一个接口,需要实现 queueIdle() 方法,返回值的含义是 keep,表示是否是一个持续的回调。keep 为 false 时,这个 IdleHandler 在执行一遍后就会被移除。
/**
* Callback interface for discovering when a thread is going to block
* waiting for more messages.
*/
public static interface IdleHandler {
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
*/
boolean queueIdle();
}
2、IdleHandler 被 MessageQueue 管理,对应的提供了 addIdleHandler() 和 removeIdleHandler() 方法。将其存入 mIdleHandlers 这个 ArrayList 中。
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
public void removeIdleHandler(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.remove(handler);
}
}
Q:IdleHandler 的 queueIdle() 什么时候被调用?
队列出现空闲的 2 种场景:
1、 MessageQueue 为空,没有 Message;
2、MessageQueue 中最近待处理的 Message,是一个延迟消息(when>currentTime),需要滞后执行;
Q:当 mIdleHandlers 不为空时,为什么不会进入死循环?
1、关键在于 pendingIdleHanderCount 的值。
2、在 pendingIdleHandlerCount 为 -1 时,才会尝试执行 mIdleHander;
3、pendingIdleHandlerCount 在 next() 中初始时为 -1,执行一遍后被置为 0,所以不会重复执行;
Message next() {
///...
///step1
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
///...
///step2
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
///step3
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);
///...
}
///step4
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
Q:IdleHandler 适合做什么?
1、适合执行一些不重要的任务,说白了就是对执行时机没有那么高要求的任务;
2、因为 IdleHandler 只有在事件循环空闲时才会执行,所以它处理任务的时机,是不可控的。
详细请看:
https://mp.weixin.qq.com/s/mR7XIVbaKsB4q-Rxe1ip2g
15、post()与sendMessage()区别
1、post和sendMessage功能其实差不多,post其实也是通过sendMessage来实现的,都是发送消息到Handler所在的线程的消息队列中
2、post的用法更方便,经常会post一个Runnable,处理的代码直接写在Runnable的run方法中,其实就是将这个Runnable发送到Handler所在线程(一般是主线程)的消息队列中。sendMessage方法主线程处理方法一般则是写在handleMessage中。代码示例如下:
post
final Handler handler = new Handler();
new Thread(new Runnable() {
@Override
public void run() {
final String response = get(url);
handler.post(new Runnable() {
@Override
public void run() {
//doSomeThing
}
});
}
}).start();
sendMessage
final Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 1) {
//doSomeThing
}
}
};
new Thread(new Runnable() {
@Override
public void run() {
Message msg = new Message();
msg.what = 1;
handler.sendMessage(msg);
}
});
view.post和handler.post区别:
view.post其实内部是获取到了view所在线程(即ui线程)的handler,并且调用了handler的post方法
16、IntentService是什么?
IntentService是google在原生的Service基础上通过创建子线程的Service。也就是说IntentService是专门为android开发者提供的能在service内部实现耗时操作的service。我们可以通过重写onHandleIntent方法实现耗时操作的回调处理,而且IntentService在耗时操作完成后,会主动销毁自己,IntentService可以通过多次启动来完成多个任务,而IntentService只会被创建一次,每次启动的时候只会触发onStart方法。内部是实现了Handler异步处理耗时操作的过程,一般多用在Service中需要处理耗时操作的功能。
提问:为什么IntentService中能实现耗时操作?
在onCreate中,通过HandlerThread来开启一条线程,而HandlerThread线程中会跟我们平常用的Handler不太一样,在run方法中创建了looper对象,所以HandlerThread能让IntentService在子线程中使用handler达到耗时操作。
17、子线程中有几行代码需要在主线程执行,有几种方法?Android中线程按功能分的话,可以分为两个,一个是主线程(UI线程),其他的都是子线程
主线程不能执行那些耗时过长的代码或任务(执行耗时过长的代码会出现应用未响应的提示),所以都是使用子线程来执行耗时过长的代码,比如说下载文件等任务
一般情况,子线程中执行过长的代码,都是需要进行更新UI操作。
但是Android中,为了防止安全,是不允许在子线程更新UI的,但是我们可以使用到Android官方给予的API来实现子线程更新UI的操作(本质上,这些API也是切换回了主线程来进行更新UI)
例子:点击一个按钮,过了1s后完成了下载任务,返回了数据,此数据会显示在界面上
具体解释:
点击按钮,之后开启一个子线程来模拟下载过程(线程休眠1s),之后任务执行完毕会返回数据(一个String),使用返回的数据更新UI
新建一个方法,用来模拟下载任务
/**
* 模拟下载
*/
fun download(): String {
Thread.sleep(1000)
return "this is data"
}
下面的使用6种方式和上面的模拟下载任务的方法,来实现上面例子的效果
1.Activity.runOnUiThread()
runOnUiThread是Activity中的方法,只有当前对象是Activity,就可以直接使用,如果当前的对象不是Activity,需要找到Activity对象,才能执行此方法
runOnUiThread方法的参数为一个Runnable接口,我使用的kotlin,所以有很多东西都是省略了
设置按钮的点击事件,点击按钮开启一个线程
btn_start.setOnClickListener {
thread {
val data = download()
runOnUiThread({
//这里进行更新UI操作
tv_show.text = data
})
}
}
Java版
btn_start.setOnClickListener(new OnClickListener(){
new Thread(new Runnable(){
String data = download();
runOnUiThread(new Runnable(){
@Override
public void run() {
tv_show.setText(data);
}
})
}).start();
});
2.View.post()
post方法是View对象的方法,参数也是接收一个runnable接口
这里我选用的view对象是需要进行更新textview的本身,当然也可以选用其他的View对象,只要是在当前Activity的对象都可以
btn_start.setOnClickListener {
thread {
val data = download()
//选择当前Activity的View对象都可以
tv_show.post {
tv_show.text = data
}
}
}
3.View.PostDelayed()
此方法和上面的post方法类似,只是多一个参数,用来实现延迟更新UI的效果
btn_start.setOnClickListener {
thread {
val data = download()
tv_show.postDelayed({
tv_show.text = data
},2000)
}
}
上面的代码实现的效果是点击按钮之后,过了3s后才会看到界面发生改变
4.Handler.post()
new一个Handler对象(全局变量)
private val handler = Handler()
使用post方法更新UI,此post方法和之前的post方法一样,参数都是为Runnable接口
btn_start.setOnClickListener {
thread {
val data = download()
handler.post {
tv_show.text = data
}
}
}
5.AsyncTask(推荐)
说明
AsyncTask是一个抽象类,必须创建一个子类类继承它
这里介绍一下关于AsyncTask的三个泛型参数和几个方法
泛型参数可以为任意类型,为空的话使用Void
抽象方法说明:
简单来说,如果子类继承了AsyncTask,它的抽象方法的参数都会变成泛型对应的类型
例子
下面的代码是取自我的APP,简单地说明一下AsyncTask<String, DownloadingItem, DownloadedItem>
我传入的是3个泛型参数分别为String,DownloadingItem,DownloadedItem,分别对应的params,progress和result泛型
这里我是根据自己的需要而两个类DownloadingItem和DownloadedItem,从下面的代码可以看到,抽象方法的参数变为了我们的泛型的类型
internal inner class DownloadingTask : AsyncTask<String, DownloadingItem, DownloadedItem>() {
override fun onPreExecute() {
//一些初始化操作
}
override fun doInBackground(vararg params: String?): DownloadedItem {
//params是一个参数数组,如果创建DownloadingTask对象只传入了一个参数,直接取下标为0的那个即可(需要转型)
//耗时操作(如下载操作),获得进度数据
//将新的进度数据传递到onProgressUpdate方法,更新UI
publishProgress(messageItem)
//任务执行完毕,返回结果(回调onPostExecute方法)
}
override fun onProgressUpdate(vararg values: DownloadingItem?) {
//这里使用最新的进度来进行相关UI的更新
//values是一个DownloadingItem数组,取末尾那个即为最新的进度数据
}
override fun onPostExecute(result: DownloadedItem?) {
//下载成功提示或者是其他更新UI的操作
}
}
执行:
执行Task的时候需要在主线程(UI线程调用)
DownloadingTask().execute("参数")
批量下载:
//允许在同一时刻有5个任务正在执行,并且最多能够存储50个任务
private val exec = ThreadPoolExecutor(5, 50, 10, TimeUnit.SECONDS, LinkedBlockingQueue<Runnable>())
DownloadingTask().executeOnExecutor(exec, url)
6.Handler机制实现(核心)
其实,Handler机制是子进程更新UI的核心
我们上面的五种实现子进程更新UI的方式,都是基于Handler机制实现的
具体机制本文就不多说了,网上有许多的机制说明,这里就只讲一下实现的步骤
Message中有几个属性,what,arg1,arg2,这三个都是接收一个Int,所以,传递数据不是很友好,这里就不准备实现之前的例子效果了
what表示来源,arg1和arg2用来传递Int数据
1.重写Handler类中的handleMessage方法
一般都是规定好一个Int的常量,来表示what
private val handler =object : Handler(){
override fun handleMessage(msg: Message?) {
if (msg.what == 1) {
//来源为1,则
}
}
}
2.发送Message
val msg = handler.obtainMessage()
//一般都是规定好一个Int的常量,来表示what
msg.what = 1
//传递Int数据
msg.arg1 = 20
handler.sendMessage(msg)
HandlerThread
HandlerThread本身也是Thread,只是在Thread基础上封装上了Handler的载体,并且在run方法中创建了looper对象,这也是为什么在IntentService中能在HandlerThread中直接用handler的原因。而我们知道一个线程是可以有多个handler,所以用HandlerThread更加方便我们不用关心Handler的创建,一般用在多线程中直接处理任务。
四个主要的类handler、message、looper、messagequeue和thread
https://www.jianshu.com/p/43e21be8d849
Looper的工作流程:
ActivityThread.main();//初始化入口
1. Looper.prepareMainLooper(); //初始化
Looper.prepare(false); //设置不可关闭
Looper.sThreadLocal.set(new Looper(quitAllowed)); //跟线程绑定
1.1.Looper.mQueue = new MessageQueue(quitAllowed); //Looper和MessageQueue绑定
1.2.Looper.mThread = Thread.currentThread();
2. Looper.loop();
2.1.myLooper().mQueue.next(); //循环获取MessageQueue中的消息
nativePollOnce(); //阻塞队列
native -> pollInner() //底层阻塞实现
native -> epoll_wait();
2.2.Handler.dispatchMessage(msg);//消息分发
myLooper().mQueue.next()实现原理
通过myLooper().mQueue.next() 循环获取MessageQueue中的消息,如遇到同步屏障 则优先处理异步消息.
同步屏障即为用Message.postSyncBarrier()发送的消息,该消息的target没有绑定Handler。在Hnandler中异步消息优先级高于同步消息。
可通过创建new Handler(true)发送异步消息。ViewRootImpl.scheduleTraversals方法就使用了同步屏障,保证UI绘制优先执行。
Handler.dispatchMessage(msg)实现原理
优先回调msg.callback。
其次回调handler构造函数中的callback。
最后回调handler handleMessage()。
Hander发送消息的流程
1.Handler handler = new Handler();//初始化Handler
1.Handler.mLooper = Looper.myLooper();//获取当前线程Looper。
2.Handler.mQueue = mLooper.mQueue;//获取Looper绑定的MessageQueue对象。
2.handler.post(Runnable);//发送消息
sendMessageDelayed(Message msg, long delayMillis);
sendMessageAtTime(Message msg, long uptimeMillis);
Handler.enqueueMessage();//Message.target 赋值为this。
Handler.mQueue.enqueueMessage();//添加消息到MessageQueue。
MessageQueue.enqueueMessage()方法实现原理
如果消息队列被放弃,则抛出异常。
如果当前插入消息是即时消息,则将这个消息作为新的头部元素,并将此消息的next指向旧的头部元素,并通过needWake唤醒Looper线程。
如果消息为异步消息则通过Message.when长短插入到队列对应位置,不唤醒Looper线程。
Handler的机制:Handler通过执行其绑定线程的消息队列(MessageQueue)中不断被Looper循环取出的消息(Message)来完成线程间的通信。
一个线程可以多个handle,只能有一个looper,怎么保证只有一个looper,用threadLocal
https://blog.csdn.net/cpcpcp123/article/details/104136413
https://www.jianshu.com/p/53e740d6d8f0
https://blog.csdn.net/coder_pig/article/details/106197731
https://www.cnblogs.com/stars-one/p/11666803.html
18、handler延时操作
https://blog.csdn.net/qq_43556200/article/details/110478562
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
dealSpeechAction(intent);
}
}, 500);
19、Android消息机制解析(native层)能说一下吗?为什么java层的loop方法是死循环但却不会消耗性能
https://blog.csdn.net/Rain_9155/article/details/87031308
20、调用sendMessageDelayed(xxx, 5秒)发送了一个Message, 这个5s 是怎么实现的?
从队列取出message的时候,要进行时间控制, 在MessageQueue的Message next()中有这句 nativePollOnce(ptr, nextPollTimeoutMillis)。 那么延伸一下,此时有一个不延时的消息进来了,nativewake了,这个五秒怎么继续计时?假如messageA 延时5秒,messageB不延时, B必然排在A前边,B插入到队列后,会唤醒阻塞,于是就把B从队列取出,执行,然后遍历下一个元素,就是A了,假如B的处理函数耗时3秒, 则重新计算阻塞时间 5s-3s = 2s, 继续调用 nativePollOnce(ptr, 2秒);
https://juejin.cn/post/7020060105773154312#heading-11
内存泄漏:https://mp.weixin.qq.com/s/MwDlFsrH0LbohmrLZyGoEA