1. Handler相关问题(一)
- Handler主要是用来处理Android线程间通信的,可以说Handler贯穿了整个Android系统,所以Handler的重要性不言而喻了,下面带着以下几个问题来分析下Handler机制(基于
Android11 SDK30
)- Handler消息机制原理
- 一个线程有几个Looper,如何保证?
- Looper.loop方法是死循环为什么不会造成卡死(ANR)
- 子线程中使用Handler需要注意的问题
- 子线程中维护的Looper,消息队列无消息时的处理方案
- 为什么建议使用Message.obtain()来创建Message
- Handler内存泄露场景、原因分析、如何避免
2. Handler消息循环
- Handler的消息循环主要是由Handler、Looper、MessageQueue以及Message四个类来协同完成的
2.1 Looper初始化
- ActivityThread(主线程)的main方法中会对Looper进行初始化并开启消息轮询
public static void main(String[] args) {
...
// 主线程的Looper在这里进行初始化的
Looper.prepareMainLooper();
...
// 主线程中的Looper开启循环
Looper.loop();
// 正是由于Looper.loop阻塞住才不会退出并抛下面的异常
throw new RuntimeException("Main thread loop unexpectedly exited");
}
- 看一下Looper.prepareMainLooper方法
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. See also: {@link #prepare()}
*
* @deprecated The main looper for your application is created by the Android environment,
* so you should never need to call this function yourself.
*/
@Deprecated
public static void prepareMainLooper() {
// 调用prepare方法初始化looper
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
// 这里获取主线程的looper
sMainLooper = myLooper();
}
}
// 使用Looper构造器创建Looper对象并存在主线程的ThreadLocal中,
// ThreadLocal可以先简单理解为每一个线程单独拥有的资源,key为线程名,用value存放looper
// quitAllowed参数最终赋值给MessageQueue的成员变量,表示主线程的Looper、MessageQueue不允许quit
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));
}
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static @Nullable Looper myLooper() {
// 获取线程中存放的Looper对象
return sThreadLocal.get();
}
- MessageQueue是在Looper构造器中进行初始化的
// Looper构造器中会初始化MessageQueue,并将quitAllowed传递给MessageQueue
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
- 从以上就可以看出,Looper与MessageQueue是一对一的关系,而ThreadLocal通过与线程的一对一关系就保证了线程与Looper/MessageQueue的一对一关系
2.2 Looper.loop()
- 接下来分析Looper.loop消息轮询的机制,它是上面ActivityThread类的main方法中倒数第二行调用的
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
final Looper me = myLooper();
if (me == null) {
// 这里就是常见的子线程中使用Looper不调用Looper.prepare会抛异常的原因
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
...
// 开启无限循环不断获取MessageQueue中的message
for (;;) {
// 通过queue.next不断获取Message中的message,这个方法可能会阻塞在nativePollOnce中
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
// 调用了queue.quit会进入这里(子线程才行,主线程不允许quit)
return;
}
...
try {
// target是Message的成员变量,是Handler类型的对象(Handler target)
// 通常是在handler.sendMessage方法中将handler赋值给msg.target
msg.target.dispatchMessage(msg);
...
} catch (Exception exception) {
...
} finally {
...
}
...
// 消息处理完成之后会调用recycleUnchecked将消息进行一系列的回收,下面会讲
msg.recycleUnchecked();
}
}
2.3 queue.next()
- 由上可见Looper.loop实际上是利用queue.next来获取消息的
- 注:代码先剔除异步消息以及IdleHandler相关的代码
Message next() {
...
// 消息需要等待的时长,0为立即执行,-1为无消息无限阻塞,其他正值表示等待时长
int nextPollTimeoutMillis = 0;
for (;;) {
// 一个Native的阻塞方法,阻塞时长由nextPollTimeoutMillis决定
// 或者调用另一个Native方法nativeWake,它会唤醒nativePollOnce放开阻塞
nativePollOnce(ptr, nextPollTimeoutMillis);
// 这里使用synchronized锁住queue对象来保证线程安全
synchronized (this) {
final long now = SystemClock.uptimeMillis();
// mMessages是queue的成员变量,在enqueueMessage中赋值(sendMessage)
// 可以理解为Message链表的头指针
Message msg = mMessages;
...
if (msg != null) {
if (now < msg.when) {
// 延时消息,时间未到,根据msg.when - now获取要等待的时长
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 获取到了一个立即执行的消息
mBlocked = false; // 是否阻塞的标志,在enqueueMessage中会使用
...
// 指针右移
mMessages = msg.next;
// 切断要处理消息的链条
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// 无消息时置nextPollTimeoutMillis = -1会一致阻塞,这也是子线程使用Handler需要注
// 意的点;子线程Handler处理完后不调用quit,子线程便会被nativePollOnce阻塞住无法退出
nextPollTimeoutMillis = -1;
}
// 调用quit后会执行
if (mQuitting) {
dispose();
return null;
}
...
}
}
}
- 由此可见有需要马上处理的消息时会取出该消息立即返回给looper.loop中去处理,延迟消息会根据msg.when计算出相应的延迟时长nextPollTimeoutMillis从而阻塞在Native方法nativePollOnce中
- 消息循环大致分析完了,接下来查看消息的发送过程,发送消息主要有sendMessage和post
2.4 handler.sendMessage()
// 发送消息
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
- 实际调用的是sendMessageDelayed发送延时消息,只不过延时时长为0
/**
* 发送延时消息
* @param msg Message
* @param delayMillis 延时时长
* @return 是否发送成功
*/
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
// 延时时长小于 0 时直接置0
delayMillis = 0;
}
// 实际上调用的是sendMessageAtTime,参数为当前时间 + 延时时长
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
- 实际上又是调用的sendMessageAtTime
// 发送消息,uptimeMillis表示执行执行时间点
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
// 获取handler绑定的messageQueue,具体是在Handler的构造器中绑定的,待会儿分析
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
// 最终执行的是enqueueMessage
return enqueueMessage(queue, msg, uptimeMillis);
}
- 接着看enqueueMessage,它主要是将msg与handler绑定,并最终调用了queue.enqueueMessage
// enqueueMessage中主要做了
// 1、将msg与handler绑定
// 2、实际调用了queue.enqueueMessage
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);
}
2.5 handler.post
- 另一种发送消息的方式是利用handler.post(Runnable r)
// 使用post方式发送消息需要传递Runnable对象
// 虽然跟Thread线程的Runnable是同一个接口,但这里跟线程并没有半毛钱的关系
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
- 实质上依然是调用的sendMessageDelayed跟上面一样就不再重复分析,只是这里并没有传递Message对象,而是使用getPostMessage方法获取了一个Message对象
// 本质上是利用obtain方法创建的对象,并将Runnable类型的r赋值给Message的成员变量callback
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
2.5 queue.enqueueMessage
// message入messageQueue队列
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) {
// 当前入队的msg为FrontMessage(通过sendMessageAtFrontOfQueue发送时when=0)
// 立即执行或比queue中的头还早,就将当前msg置为头节点
msg.next = p;
mMessages = msg;
needWake = mBlocked; // 是否是阻塞状态,mBlocked是在next方法中赋的值
} else {
... // 异步消息暂不考虑
Message prev;
// 遍历消息根据when大小进行消息排序入队
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
...
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
// 当需要唤醒时调用nativeWake方法唤醒
// nativeWake会唤醒next方法中的nativePollOnce执行
nativeWake(mPtr);
}
}
return true;
}
- 上面分析了消息的发送入队列以及消息的轮询过程,接着分析拿到消息进行回调处理的过程
- 在2.2中looper.loop中取出消息最终调用的是msg.target.dispatchMessage(msg);
- msg.target实际上就是指与msg绑定的handler,在2.4 handler.enqueueMessage方法可以追溯到
2.6 handler.dispatchMessage
- 这是一个消息的处理分发的方法
public void dispatchMessage(@NonNull Message msg) {
// 当msg绑定有callback时会优先执行
// callback是message的一个成员变量,类型为Runnable
// 虽然跟Thread线程的Runnable是同一个接口,但这里跟线程并没有半毛钱的关系
// 你只需要知道callback的绑定是在handler.post方式发送消息中去绑定的
if (msg.callback != null) {
handleCallback(msg);
} else {
// 这个mCallBack是一个Handler的内部接口,只有一个handleMessage的抽象方法
// 是在Handler的构造器中去赋值的,具体见3
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
// 当callback的实现类中重写的handleMessage返回true时,代码直接return
// 不再执行后一个handleMessage
return;
}
}
// 这个handleMessage跟上面的不一样,她只是一个没返回值的普通方法
// 通常写一个handler的静态内部类,重写实现这个方法就可以了
handleMessage(msg);
}
}
3. Handler构造器
@Deprecated
public Handler() {
this(null, false);
}
@Deprecated
public Handler(@Nullable Callback callback) {
this(callback, false);
}
public Handler(@NonNull Looper looper) {
this(looper, null, false);
}
public Handler(@NonNull Looper looper, @Nullable Callback callback) {
this(looper, callback, false);
}
@UnsupportedAppUsage
public Handler(boolean async) {
this(null, async);
}
public Handler(@Nullable Callback callback, boolean async) {
mLooper = Looper.myLooper();
if (mLooper == null) {
// 这就是在子线程中使用Handler但没调用Looper.prepare创建Looper对象而抛异常的原因
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
// mCallBack就是在这里赋值的,最终会在dispatchMessage中生效
mCallback = callback;
mAsynchronous = async;
}
@UnsupportedAppUsage
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
4. Message的结构与创建
- message主要包含几个成员变量,包括常用到的存储数据的
what
、arg1
、arg2
、obj
- message本身包含了Handler类型的成员变量
target
,以及一个Runnable类型的callback
- Message本身还是一个单链表结构,
next
表示下一个节点,spool
表示缓存对象的头节点,使用享元模式来缓存之前用到的msg,达到重复利用避免频繁GC;缓存的对象最多为50
个 - Message的创建方式主要有直接new无参构造,但更建议是
Message.obtain()
4.1 Message.obtain()
// 使用obtain获取Message可以有效地避免创建新对象
public static Message obtain() {
synchronized (sPoolSync) {
// 当缓存对象池中存在sPool缓存对象,会使用该对象
if (sPool != null) {
// 缓存对象池是以链表的形式存在的,spool表示链表头
Message m = sPool;
// 拿出spool引用的对象后,将下一个节点置为新的头节点spool
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--; // 缓存池缓存的Message数量-1
return m;
}
}
// 只有缓存对象池中没有对象才会new新对象
return new Message();
}
- 而spool则是在recycleUnchecked方法中赋值的,这个方法是在2.2 Looper.loop中消息处理完毕后进行调用的
4.2 message.recycleUnchecked
// 主要做Message对象的回收,在Looper.loop中消息处理完毕后进行调用
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) {
// 当缓存对象池未满(< 50)时将当前需要回收的message添加到缓存池中,而不是指空
// 使得下次使用时可以直接使用无需重新创建,也避免了频繁回收,典型的享元模式
if (sPoolSize < MAX_POOL_SIZE) {
// 将新回收的msg添加到链表的头部,并将spool指向新的头节点
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
5. 总结
- 针对开头提出的问题进行逐一解答
5.1 Handler消息机制原理
- Handler主要用来进行Android线程间的通信的,常用来利用子线程与主线程间的通信来解决Android无法在子线程更新UI的问题
- Handler消息机制主要是由Looper、MessageQueue、Handler以及Message四个对象协同完成的
- 消息发送:主要是通过handler.sendMessage/handler.post方法发送的,而sendMessage/post本质上是调用的sendMessageAtTime,将携带了执行绝对时间when的Message最终通过messageQueue.enqueueMessage方法执行Message入队,messageQueue维护了一个按时间先后顺序的Message优先级队列,故在执行Message入队时会根据执行时间将Message插入到合适的位置.
- 消息获取:当主线程ActivityThread类的main方法执行时会执行Looper.prepareMainLooper进行主线程中Looper的初始化,Looper构造器中也会对MessageQueue进行初始化操作;main方法中的倒数第二行还会执行Looper.loop开启消息的轮询,loop方法会通过queue.next获取MessageQueue中存放的消息,消息需要立即执行时取出消息,否则阻塞在nativePollOnce方法中,取出消息后调用msg.target.dispatchMessage,将message交给发对应handler中的dispatchMessage方法进行处理
- 消息处理:handler中的dispatchMessage中会先判断message是否绑定有Runable类型的callback,有就回调run方法,没有的话再看是否绑定有CallBack类型的参数,有便回调CallBack接口的实现方法handleMessage,实现方法只有返回false时,才会继续调用本身的handler本身的handleMessage普通方法。
5.2 一个线程有几个Looper,如何保证?
- 一个线程只有一个Looper,首先Looper的构造器是私有的,你无法通过new的方式创建,Looper只提供了一个prepare方法构造对象(prepareMainLooper实质上也是调用的prepare),而prepare方法是将Looper对象保存在ThreadLocal中,利用ThreadLocal来保证线程与Looper间一对一的关系
- ThreadLocal内部主要是维护了一个ThreadLocalMap的key-value型内部类,调用set时将key设置为当前线程的ThreadLocal对象(ThreadLocalMap是Thread类的一个成员变量一对一的关系),value为Object类型的值
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赋值给Thread类的threadLocals成员变量达到绑定的目的
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
// ThreadLocalMap为Thread类的的一个成员变量
ThreadLocal.ThreadLocalMap threadLocals = null;
5.3 主线程中Looper.loop方法是死循环为什么不会造成卡死(ANR)
- ActivityThread的main方法中正是由于Looper.loop死循环才不会导致程序抛异常退出
- 主线程卡死ANR并不代表主线程阻塞而是主线程的消息循环出现异常,需要马上响应处理的消息未及时处理才会导致卡死ANR
- 当没有需要马上处理的消息时,Looper.loop方法中的queue.next会进入一个阻塞的Native方法nativePollOnce,此时主线程会释放CPU资源进入休眠直到下一个需要处理的消息的到达,所以也并不会消耗很多的CPU资源
- nativePollOnce不阻塞的原因是epoll机制,他是linux里面的,在native层会有一个读取端和一个写入端,当有消息发送过来的时候会去唤醒读取端,然后进行消息发送与处理,没消息的时候是处于休眠状态。
5.4 子线程中使用Handler需要注意的问题
- 主线程本身是创建了Looper并开启了消息循环Looper.loop的,但子线程并没有,所以必须在子线程中手动的调用
Looper.prepare()
来创建子线程中的Looper,以及需要调用Looper.loop()
开启子线程中的消息循环 - 子线程任务执行完毕之后记得调用
looper.quit()
方法,否则该子线程将由于looper.loop阻塞线程的原因导致子线程即使执行完毕也不会退出 - 不建议自己在子线程中这样干,容易出错也比较麻烦,可以直接使用官方封装好的
HandlerThread
5.5 子线程中维护的Looper,消息队列无消息时的处理方案
- 子线程中消息队列无消息时,会一直阻塞在nativePollOnce中而无法终止线程;
- 如没有消息,可调用Looper.quit(内部调用messageQueue.quit将消息回收,并执行nativeWake方法唤醒nativePollOnce退出循环)释放线程。
- 主线程调用quit会抛异常"Main thread not allowed to quit"
// Looper.quit
public void quit() {
mQueue.quit(false);
}
// messageQueue.quit
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
// 退出时,未到时间的消息直接回收,需要立即处理的消息等待处理完成再回收
removeAllFutureMessagesLocked();
} else {
// 退出时,所有消息直接回收
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
// 可以唤醒nativePollOnce往下运行的另一个Native方法
nativeWake(mPtr);
}
}
5.6 为什么建议使用Message.obtain()来创建Message
- Message维护了一个单链表结构的缓存对象池(缓存最大数为50),用于缓存之前创建但现在已使用完毕的Message对象,而不是直接置空回收,避免了Message对象的频繁创建与回收。
- spool指向的是Message缓存对象池链表的头节点,每次调用obtain会先判断spool是否为空,即是否已存在缓存对象,为空则使用new的方式构造新对象,不为空就直接复用该缓存头节点sPool,避免重复创建Message对象 (可查看上面的4.1和4.2)
5.7 Handler内存泄露场景、原因分析、如何避免
- 场景1:当发送的是延迟消息,在延迟消息未处理之前关闭了Activity
- 此时Handler作为Activity的非静态内部类持有了Activity的引用,而Handler有完整的到主线程的应用链
Activity
<—Handler
<—Message
(target)<—MessageQueue
<—Looper
<—ThreadLocal
<—ActivityThread
(主线程),由于主线程不会被回收就会导致Activity不会被回收从而导致内存泄漏
- 此时Handler作为Activity的非静态内部类持有了Activity的引用,而Handler有完整的到主线程的应用链
- 场景2:子线程中使用了Handler时,没有调用quit,子线程无法结束或子线程未执行完毕
- 子线程作为非静态内部类持有了外部类Activity对象的引用,而运行中的子线程不会被回收就会导致Activity不会被回收从而导致内存泄漏
- 避免方式1:使用
静态内部类
或弱引用
- 单独使用静态内部类就使得Handler无法更新Activity中的非静态视图对象,从而无法更新UI,所以一般会配合弱引用一起使用
MyHandler(WeakReference(this)).sendEmptyMessageDelayed(0, 20000)
//kotlin中内部类默认为静态内部类
class MyHandler(var mActivity: WeakReference<HandlerActivity>):Handler(){
override fun handleMessage(msg: Message?){
super.handleMessage(msg)
mActivity.get()?.changeBtn()
}
}
-
避免方式2:在Activity的onDestroy中移除未来得及处理的延迟消息,调用
handler.removeCallbacksAndMessages(null)
取消handler对message的监听(本质上是message.recycleUnchecked,将msg.target置null,见4.2)防止内存泄漏