Handler 常用来进行线程间通信,比如子线程有更新 UI 的需求时,通过 Handler 发送一条消息,然后在主线程中取出这条消息,并更新 UI。我们结合源码看下该过程是如何完成的。
一、概述
过程中有如下角色:
- Handler:①发送 Message 到 MessageQueue 中;②处理从 MessageQueue 中取出的消息。
- Message:消息,有三种类型:同步消息、异步消息和同步屏障。由 Handler 发送和处理,储存在 MessageQueue 当中。
- MessageQueue:消息队列,存储 Message 的,对不同类型的 Message 有不同的处理方式。当队列中没有消息可取时,会挂起当前线程,在有新消息入队后,再唤醒线程。
- Looper:循环器,通过 loop() 循环遍历 MessageQueue 并从中取出一个 Message 交给 Handler 处理。
- Thread:线程,是图中没有体现出的角色,但是与前面的 4 个角色有密不可分的关系,比如线程与 Looper 是一一对应绑定的关系。具体内容后面介绍源码时会讲。
二、各个角色的创建
先结合源码看看各个角色是如何创建的,源码基于 API 30。
1.创建 Handler
在 API 30 中,Handler 一共有 7 个构造方法,2 个被标记了 @Deprecated,3 个 @hide(其中 2 个也是 @UnsupportedAppUsage),剩下 2 个正常的供我们调用来创建 Handler 对象。2 个 @Deprecated 的方法是在老版本中常用的:
@Deprecated
public Handler() {
this(null, false);
}
@Deprecated
public Handler(@Nullable Callback callback) {
this(callback, false);
}
它们被 @Deprecated 是因为需要在创建 Handler 对象时指定 Looper,在方法参数中加上 Looper 后就变成了当前版本可用的构造方法:
public Handler(@NonNull Looper looper) {
this(looper, null, false);
}
public Handler(@NonNull Looper looper, @Nullable Callback callback) {
this(looper, callback, false);
}
这两个构造方法内部调用的是三参构造方法:
@UnsupportedAppUsage
final Looper mLooper; // 该 Handler 指定的 Looper
final MessageQueue mQueue; // mLooper 创建并维护的消息队列
@UnsupportedAppUsage
final Callback mCallback; // Handler 内部处理消息的接口
final boolean mAsynchronous; // 是否为异步
@UnsupportedAppUsage
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
注意构造方法内部初始化的 4 个成员都是 final 的,意味着初始化后它们不能再更改,特别是这个 Looper。从这里能看出,Handler 只能与一个 Looper 绑定且不可更改。
那为什么一定要在构造方法的参数中指定 Looper 呢?源码注释给出了答案:
在 Handler 构造期间隐式选择 Looper 会导致操作无声地丢失(如果 Handler 不期待新任务并退出)、崩溃(如果有时在没有活动 Looper 的线程上创建 Handler)或竞争条件,Handler 关联的并不是预期线程。相反,使用 Executor 或显式指定 Looper,使用 Looper#getMainLooper,View#getHandler 或类似方法会避免以上情况。如果为了兼容性,需要隐式的线程本地行为,请使用 new Handler(Looper.myLooper()) 向读者说明。
翻译的有些生硬,但大致意思就是隐式 Looper 会导致 bug,严重的还会导致应用崩溃,为了避免这些问题才需要在 Handler 的构造方法中显式指定 Looper。这里说的崩溃,最容器想到的就是在子线程中创建 Handler 时会抛出 RuntimeException:Can’t create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()。
子线程创建 Handler
刚入门的时候,在子线程中创建 Handler 可能就这么写了:
private void test() {
new Thread(new Runnable() {
@Override
public void run() {
Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
}
};
}
}).start();
}
然后系统也就毫不留情的弹出个 FC 的窗口,一看 Log 正是上面说的那个 RuntimeException,意思是不能在没有调用 Looper.prepare() 的线程中创建 Handler。
当然像上面那样用匿名内部类的形式创建 Handler 也是不对的,会造成内存泄漏,应该创建一个静态内部类继承 Handler,文章最后会说 Handler 内存泄漏的相关内容。
Looper.prepare() 是干啥的?为啥主线程中直接 new 就没事儿呢?先回头看我们调用的那个空参构造方法:
@Deprecated
public Handler() {
this(null, false);
}
它在内部又调用了一个双参的构造方法,就是之前说的那个 @hide 的方法:
/**
* @hide
*/
public Handler(@Nullable Callback callback, boolean async) {
// FIND_POTENTIAL_LEAKS 默认为 false,这部分是通过检查 Handler class 的
// 声明方式提醒我们可能存在内存泄漏的
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
// 匿名类或成员类或局部类,并且不是 static 的,就可能发生内存泄漏
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());
}
}
// 获取当前 Handler 的 Looper,如果为空就抛 RuntimeException
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;
}
这里我们找到了抛出异常的位置,如果通过 Looper.myLooper() 拿到的 mLooper 是 null 就会抛异常。而 Looper.myLooper() 其实就是去获取与该 Handler 所在线程绑定的 Looper:
/**
* 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() {
return sThreadLocal.get();
}
方法注释说的很清楚,返回与当前线程关联的 Looper 对象,如果线程没有与 Looper 关联就返回 null。显而易见,抛出异常的原因是 Handler 所在线程没有与 Looper 关联。
而主线程中直接 new Handler() 没抛这个异常,是因为系统已经帮我们做了主线程与 Looper 绑定的工作。在 ActivityThread 的 main() 中:
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
static volatile Handler sMainThreadHandler; // set once in main()
public static void main(String[] args) {
......
// 1.创建主线程 Looper
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
// 2.创建主线程的 Handler,实际类型是 ActivityThread 内部类、Handler 的子类 H。
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
// 3.开启消息循环
Looper.loop();
}
Android 的消息驱动机制其实是用 Handler 实现的,系统中的很多操作如 BIND_APPLICATION、EXIT_APPLICATION、CREATE_SERVICE 等几十种操作,其实都是以 Handler 消息进行处理的(具体见 ActivityThread 中 Handler 子类 H)。所以不要以为 Handler 的主要作用就是进行线程间通信,它对于整个 Android 系统的意义都是很大的。
Looper#prepareMainLooper() 会创建并保证一个全局唯一的主线程 Looper:
@UnsupportedAppUsage
private static Looper sMainLooper; // guarded by Looper.class
/**
* 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,会创建一个新的 Looper
prepare(false);
synchronized (Looper.class) {
// 如果 sMainLooper 已经存在,又调用 prepareMainLooper() 就抛异常
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
// 否则给 sMainLooper 赋值为与当前线程绑定的 Looper
sMainLooper = myLooper();
}
}
prepareMainLooper() 被 @Deprecated 的原因是应用的主线程 Looper 会由系统创建,所以应用开发者就不应该再自己调用这个方法创建主线程 Looper 了。
prepare() 是真正创建 Looper 的方法,会在当前线程(调用该方法的线程)没绑定 Looper 的情况下,为之创建一个 Looper 并绑定,但如果线程已经绑定了 Looper,那么就会抛出异常:
// sThreadLocal.get() will return null unless you've called prepare().
@UnsupportedAppUsage
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
// quitAllowed 表示是否允许退出 Looper.loop() 的死循环,后面会详细讲
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));
}
由此可见,一个线程只能与一个 Looper 绑定。
sThreadLocal 是全局唯一的,set() 就是将 Looper 作为当前线程的 value 存进 ThreadLocal 内部的 ThreadLocalMap 中:
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 拿到当前线程中保存的 ThreadLocal.ThreadLocalMap 对象
ThreadLocalMap map = getMap(t);
// 如果 map 存在就保存 value 值,否则就用 value 值为 t 创建一个 ThreadLocalMap
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
这样 Looper 就与一个线程绑定了。
那么子线程创建 Handler 其实就效仿 prepareMainLooper() 三步走就可以了:
- 调用 Looper.prepare() 创建一个新的 Looper 并与当前线程绑定(一个线程中就调用一次,避免重复绑定抛出异常)。
- 创建 Handler 对象。
- 调用 Looper.loop() 开启消息循环,使得消息可以从消息队列中被取出交给 Handler 处理。
示例:
private void test() {
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare(); // 新建 Looper 绑定线程
// 先忽略内存泄漏的问题,最后会说
Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
}
};
Looper.loop(); // 开启消息循环
}
}).start();
}
当然上面这种方式只是理论上的,实际项目中需要创建子线程 Handler 时,通常都会使用 HandlerThread。
2.创建 Message
获取 Message 对象要用 Message.obtain() 而不是直接 new Message(),简单说下原因。
Message 类中建立了一个全局唯一的消息池,obtain() 就是先从这个消息池中取 Message 对象,如果消息池中没有缓存的 Message 才会 new Message() 返回给调用者:
// sometimes we store linked lists of these things
@UnsupportedAppUsage
Message next; // 指向链表中下一条 Message 的指针
/** @hide */
public static final Object sPoolSync = new Object(); // 同步锁对象
// 消息池是一个链表,sPool 是该链表的头节点
private static Message sPool;
// 消息池大小
private static int sPoolSize = 0;
// 消息池的最大尺寸
private static final int MAX_POOL_SIZE = 50;
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
public static Message obtain() {
synchronized (sPoolSync) {
// sPool != null 说明消息池中还有消息
if (sPool != null) {
// 将链表头部的 Message 对象取下来返回给调用者
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
// 如果消息池中没有缓存的 Message,就 new 一个返回
return new Message();
}
以上是从消息池中取出 Message 进行复用,下面再看一下 Message 的回收:
/** If set message is in use.
* This flag is set when the message is enqueued and remains set while it
* is delivered and afterwards when it is recycled. The flag is only cleared
* when a new message is created or obtained since that is the only time that
* applications are allowed to modify the contents of the message.
*
* It is an error to attempt to enqueue or recycle a message that is already in use.
*/
/*package*/ static final int FLAG_IN_USE = 1 << 0;
@UnsupportedAppUsage
void recycleUnchecked() {
// 清空 Message 的所有字段,flags 标记为 FLAG_IN_USE,表示正在使用。
// 不应该将一个正在使用中的 Message 进行入队或者回收操作
// 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;
// 如果消息池容量能容纳新的 Message,就在链表头部加入 Message
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
设置消息池的意义在于它可以防止内存抖动进而防止 OOM,这种内存复用的思想其实也是使用了享元设计模式。这种模式在 Android 中有多处应用,比如 RecyclerView 对 ViewHolder 的回收复用。
3.创建 Looper
Looper 的创建其实前面已经提到过,创建 Handler 对象之前,必须保证 Handler 所在线程有一个与之绑定的 Looper,主线程 Looper 已经由系统在 ActivityThread 的 main() 中通过调用 Looper.prepareMainLooper() 创建出来了,而子线程 Looper 需要调用 Looper.prepare() 创建。通过前面贴出的源码可以看到 Looper.prepareMainLooper() 内部其实就是调用 Looper.prepare() 创建 Looper 的:
@Deprecated
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
private static void prepare(boolean quitAllowed) {
// 在通过 ThreadLocal set() Looper 之前,先检查之前是否已经为当前线程绑定
// 过 Looper,如果是则要抛出异常,以保证一个线程只能绑定一次 Looper,不能更改
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
prepare() 内部调用了 Looper 的构造方法创建了 Looper 对象。Looper 只有一个 private 的构造方法:
@UnsupportedAppUsage
final MessageQueue mQueue;
final Thread mThread;
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Looper 在构造方法内创建了一个 MessageQueue 对象 mQueue ,并且记录了当前线程 mThread。mQueue 与 mThread 都是 final 的,意味着初始化之后不可更改,也就是说 Looper 只能与一个线程绑定,并且只维护一个 MessageQueue。
结合前面章节的红字,我们可以对 Handler、Thread、Looper 和 MessageQueue 之间的关系做一个总结了:
- Thread 与 Looper 是一一对应的关系,一个 Thread 只能绑定一个 Looper,反之亦然。
- 一个 Handler 就只有一个 Looper;反过来一个线程上可以创建多个 Handler 对象,那么一个 Looper 也可以服务于多个 Handler。
- 一个 Looper 内部只有一个 MessageQueue。
将以上关系做成图是这样的:
4.创建 MessageQueue
MessageQueue 只有一个 Default 权限的构造方法:
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
Default 权限意味着只有同一个包下,即 android.os 包下的其他非子类可以调用该构造方法,目前源码中也只有 Looper 调用了该构造方法。也就是说,想获取到 MessageQueue 对象只能通过 Looper#getQueue()。
参数中的 quitAllowed 表示消息队列是否允许退出循环。如果 Looper 与主线程绑定了,那么它的消息队列是不允许退出循环的,如果尝试调用 quit() 退出,就会收到一个 IllegalStateException:Main thread not allowed to quit。
三、Handler 发送消息
准备工作就绪,下面就该由 Handler 发送消息到消息队列中了,常用的方法有 sendMessage() 和 post():
public final boolean sendMessage(@NonNull Message msg) {
// 第二个参数为延迟时间,传0
return sendMessageDelayed(msg, 0);
}
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
我们先分别来看下这两个方法的执行过程。
1.sendMessage
sendMessage() 内部会调用 sendMessageDelayed():
#Handler.java:
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
// msg 的分发目标为当前的 Handler 对象
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
// Handler 是否为异步,如是,就把所有 msg 设置为异步消息
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// 调用 MessageQueue 的 enqueueMessage() 将 msg 入队
return queue.enqueueMessage(msg, uptimeMillis);
}
看调用链最后的 enqueueMessage(),Message 的 target 字段是 Handler:
@UnsupportedAppUsage
/*package*/ Handler target;
在将 Message 入队之前,让它的 target 指向当前的 Handler 对象,目的是为了记住该 Message 是属于哪个 Handler 对象的,这样在从 MessageQueue 出队时,才能分发给正确的 Handler。
而 mAsynchronous 在前面介绍 Handler 的构造方法时出现过,如果用构造方法把 mAsynchronous 设置为 true,那么经由这个 Handler 发送的所有消息,在发送前都会被设置成异步消息。
异步消息的详细介绍在【三.3.异步消息与同步屏障】一节中。
2.post
再来看 post() 这边,它也会先调用 sendMessageDelayed(),只不过它将参数中的 Runnable 对象通过 getPostMessage() 包装进 Message 当中
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
这里又要去看 Message 中的另一个字段 callback 了:
@UnsupportedAppUsage
/*package*/ Runnable callback;
实际上就是将一个 Runnable 对象封装进 Message 中,后续在 Handler 执行消息的时候,会优先检查 Message 的 callback 是否为空,如果不为空就执行这个 Runnable。
3.异步消息与同步屏障
按照代码的执行流程,本应该看 MessageQueue 是如何将消息入队的。但是由于在入队操作时涉及到了异步消息和同步屏障两个概念,只好在这里作为前置知识先进行说明。
异步消息
消息队列中的消息有三种类型:普通消息(同步消息)、异步消息和同步屏障。
前面 Handler 通过 enqueueMessage() 发送消息时会根据 mAsynchronous 决定是否将自己发送的消息设为异步消息:
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
// 1.msg 的分发目标为当前的 Handler 对象
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
// 2.Handler 是否为异步,如是,就把所有 msg 设置为异步消息
if (mAsynchronous) {
msg.setAsynchronous(true);
}
// 3.调用 MessageQueue 的 enqueueMessage() 将 msg 入队
return queue.enqueueMessage(msg, uptimeMillis);
}
mAsynchronous 仅在 Handler 的两个构造方法中可以赋值,而这两个方法是 @hide 的,也就是说我们无法通过正常手段 new 出一个发送异步消息的 Handler,平时使用的 Handler 发送的都是同步消息。
由于 Message 的 setAsynchronous() 是 public 方法,所以如果我们想发送一条异步消息,可以像源码第 2 步中那样先将消息设为异步消息再发送。
同步消息和异步消息的主要区别在于它们的执行方式。如果开启了同步屏障,就只会取异步消息出队,直接忽略同步消息。没开同步屏障,就按照 Message.when 由小到大排列的顺序逐个取出消息(同步和异步都按照排序取出)。
同步屏障
同步屏障表面上看起来就是一条 Message.target = null 的消息,需要结合消息出队的代码才能看出它的作用:
@UnsupportedAppUsage
Message next() {
for (;;) {
synchronized (this) {
Message prevMsg = null;
// msg 指向消息队列头部
Message msg = mMessages;
// 队列不为空并且队头 msg 是一个同步屏障
if (msg != null && msg.target == null) {
// 遍历队列,直到找到一条异步消息或到了队尾
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
// 通用流程,如果同步屏障没开启,就找同步消息出队并返回,
// 如果同步屏障开启并找到了异步消息,就将异步消息出队......
}
}
}
在消息出队的 for 循环中,首先就看队列头节点是不是同步屏障,如果不是就走后面一般的消息出队流程,否则就只找屏障后的异步消息,执行时间越靠前就越早出队,完全无视同步消息。其实就是屏蔽、过滤掉了同步消息,优先执行异步消息。
联想一下前面,Handler 在发送消息调用 enqueueMessage() 时,会通过 Message.target = this 把自己作为消息的分发目标,那么因为同步屏障的 Message.target = null,显然其不是通过 Handler 发送进消息队列的。这就涉及到了同步屏障设计的初衷:为了保证需要紧急执行的消息能够及时执行,而不是跟着同步消息排队,按照时间顺序执行。例如系统每 16ms 就要刷新一次 UI,用的就是同步屏障 + 异步消息实现的。
在刷新 UI 的过程中,会调用到 ViewRootImpl 的 scheduleTraversals():
@UnsupportedAppUsage
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 发送同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 发送更新 UI 的异步消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
调用 MessageQueue 的 postSyncBarrier():
@UnsupportedAppUsage
@TestApi
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
// 入队一个同步屏障。不需要唤醒队列,因为屏障的目的就是阻碍消息队列的循环。
synchronized (this) {
// removeSyncBarrier() 时会用到
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
// 没有给 msg 的 target 字段赋值,所以 msg.target = null
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
// p 指向链表头部,遍历后最终指向 msg 要插入的位置
Message p = mMessages;
if (when != 0) {
// 根据执行时间 when 找到 msg 应该插入的位置
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
// 在链表中间插入 msg
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
// 在链表头部插入 msg
msg.next = p;
mMessages = msg;
}
return token;
}
}
也就是说,将同步屏障入队是 MessageQueue 才能执行的操作。
同步屏障发送完成后,紧接着调用 Choreographer 的 postCallback() 发送更新 UI 的异步消息:
/**
* Callback type: Traversal callback. Handles layout and draw. Runs
* after all other asynchronous messages have been handled.
* @hide
*/
public static final int CALLBACK_TRAVERSAL = 3;
@UnsupportedAppUsage
@TestApi
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
@UnsupportedAppUsage
@TestApi
public void postCallbackDelayed(int callbackType,
Runnable action, Object token, long delayMillis) {
if (action == null) {
throw new IllegalArgumentException("action must not be null");
}
if (callbackType < 0 || callbackType > CALLBACK_LAST) {
throw new IllegalArgumentException("callbackType is invalid");
}
postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
// 通过 Handler 发送异步消息,callbackType 传入的是 CALLBACK_TRAVERSAL,
// 即处理 layout 和 draw
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
刷新完成还是在 ViewRootImpl 中,调用 unscheduleTraversals() 移除同步屏障、移除更新 UI 的消息:
#ViewRootImpl.java:
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
// 从 Handler 中移除消息
mChoreographer.removeCallbacks(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
4.消息入队
介绍完同步屏障再来看消息入队的具体流程。Handler 中众多发送消息的方法,最终都调用到 MessageQueue 的 enqueueMessage() 执行入队操作:
@UnsupportedAppUsage
Message mMessages; // 消息链表的头节点
private boolean mQuitting; // 是否处于结束 Looper 的消息循环
boolean enqueueMessage(Message msg, long when) {
// msg 没有分发目标,直接抛异常
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
// 以当前 MessageQueue 对象为同步锁
synchronized (this) {
// 标记正在使用的 msg 不能入队
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
// mQuitting 为 true 时表示结束 Looper 对队列的循环,这时候 msg 也不能入队
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;
// 如果队列中没有消息,或者 msg 的执行时间为 0,或者 msg 的执行时间
// 比消息链表头节点的执行时间要早,那么应该将 msg 插入到链表头
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.
// 在队列中间插入消息时,是否需要唤醒就取决于三点了:
// 1.当前队列已经阻塞;2.队列头节点是同步屏障;3.待插入消息是异步的
// 以上三点全部成立时,才需要唤醒
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// 死循环遍历消息队列
for (;;) {
prev = p;
p = p.next; // p 指向下一条消息
// 如果 p 为空(遍历到队尾了)或者 msg 执行时间
// 比 p 要早,那么 msg 应该插入到 p 之前
if (p == null || when < p.when) {
break;
}
// 需要唤醒并且 p 是异步消息
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
// 插入 msg
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
// 调用 native 方法唤醒
nativeWake(mPtr);
}
}
return true;
}
这段代码其实主要做了三件事:
- Message 合法性校验,当 target = null、isInUse() 为 true、mQuitting 为 true 时 Message 不能入队。
- 根据 Message 执行时间由前到后的顺序,找到合适位置插入 Message(因此这里使用插入排序的优先级队列),同时更新 needWake 标记。
- 如果 needWake 为 true 表示需要唤醒线程,那么就调用 native 方法 nativeWake() 唤醒。
说一下为什么要调用 nativeWake() 主动唤醒线程,因为在消息入队时,线程可能正处于阻塞状态,比如队列中没有消息可以取出,或者是队列中有消息,但当前还没到队头消息的执行时间,那么 MessageQueue 是会调用 native 方法 nativePollOnce() 让当前线程休眠一定时间的。所以在上面的代码中对于是否需要唤醒也有两种判断条件:
- 在队列头部插入消息,是否唤醒取决于 mBlocked,即当前新线程是否已经阻塞。因为在阻塞时,如果在队头插入一条消息,意味着之前设置的阻塞时间已经不适用,需要唤醒重新检查队列。
- 在队列中间插入消息,隐含了队列不为空的条件。由于 MessageQueue 按排列顺序取消息出队时,只出队头,即最早执行的消息,所以在队列中插入同步消息并没改变出队顺序,因此不需要唤醒线程,所以需要插入的是异步消息了。光插入异步消息还不够,还需要让队头是同步屏障这样队列才会只取异步消息出队。所以需要三个条件同时满足才需要唤醒线程。
四、消息出队
前面说在子线程中创建 Handler 时除了要先调用 Looper.prepare() 将线程与 Looper 绑定外,还需要调用 Looper.loop() 保证 Looper 能正常工作,它的工作其实就是通过一个死循环不断的从消息队列中取出消息交给对应的 Handler 去执行:
/**
* 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) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
for (;;) {
// 1.通过 MessageQueue 的 next() 取出一条消息,该方法可能会阻塞
Message msg = queue.next(); // might block
if (msg == null) {
// 如果没拿到消息,说明 MessageQueue 正在退出消息循环
// 这是唯一跳出死循环的出口(不算省略的 try-catch)
return;
}
try {
// 2.调用目标 Handler 的 dispatchMessage() 处理消息
msg.target.dispatchMessage(msg);
......
}
// 3.回收消息进消息池
msg.recycleUnchecked();
}
}
其中第 3 步回收消息到消息池在介绍 Message 创建方法时有提到过,主要看前两步。
1.MessageQueue#next()
// 实现了 IdleHandler 接口的对象集合
@UnsupportedAppUsage
private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
// 对应于 mIdleHandlers 的数组,最大容量为 4
private IdleHandler[] mPendingIdleHandlers;
@UnsupportedAppUsage
Message next() {
// mPtr 持有 native 层的 NativeMessageQueue 对象,当退出消息循环后会释放掉该
// 对象被置为 0。因为不支持应用在退出消息循环后尝试重新开启 Looper 的操作,所
// 以这里判断到 mPtr 为 0 时就直接返回 null。
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0; // 表示让线程阻塞的时长
// 一直到方法结束,都是这个死循环
for (;;) {
// 1.根据 nextPollTimeoutMillis 的值阻塞当前线程
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// 2.寻找同步屏障后的第一条异步消息,详细解析前面已经提到过
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());
}
// 3.队列中有消息,如果到了执行时间就出队,否则计算出等待时间
if (msg != null) {
// msg != null 包含两种情况,一是有同步屏障,并且拿到了一条异步消息,
// 二是没开同步屏障,在队列中有消息的情况下,从队列头让同步消息出队。
// 无论哪种情况,msg 都会出队。
if (now < msg.when) {
// 没到 msg 的执行时间,就计算出当前到执行时间的时间差,并由
// 死循环头部的 nativePollOnce() 进行阻塞
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// msg 到了执行时间,出队并返回,阻塞标记位 mBlocked 设为 false
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// 队列中没有消息,就无限期的阻塞
nextPollTimeoutMillis = -1;
}
// 4.如果正在退出消息循环,就释放 native 的 NativeMessageQueue 对象。
if (mQuitting) {
dispose();
return null;
}
// 5.创建闲时任务数组 mPendingIdleHandlers
// 第一次阻塞时,获取系统中实现了 IdleHandler 接口并通过 addIdleHandler() 添加到
// mIdleHandlers 集合中的对象数量。IdleHandler 仅在队列为空或者队列中的第一条消息
// (可能是屏障)未到执行时间的情况下运行。
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
// 没有可以运行的 IdleHandler,将阻塞标记置为 true 后跳出本次循环
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}
// 如果 mPendingIdleHandlers 数组还没创建,就创建一个容量最大为 4 的
// IdleHandler 类型的数组,然后将 mIdleHandlers 转成 mPendingIdleHandlers
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
} // 同步代码块结束
// 6.运行闲时任务,这段代码仅在第一次阻塞的时候运行
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;
// 运行 IdleHandler 时,可能有消息要到执行时间了,所以把 nextPollTimeoutMillis
// 设置为 0,不进行等待,立即去队列中找是否有要出队的消息
nextPollTimeoutMillis = 0;
}
}
出队过程注释中已经写的很详细,就不多赘述了。只不过结合入队和出队的过程来看,MessageQueue 是一个由单链表实现的优先级队列。enqueueMessage() 中通过插入排序对 Message 按照执行时间的先后顺序进行了排序,先执行的在队前。之所以说是一个队列,是因为在 next() 中,只取头部的 Message 出队。
下面详细说明线程阻塞的过程、如何退出消息循环以及闲时任务。
阻塞线程
死循环上来就调用 native 方法 nativePollOnce() 阻塞线程:
@UnsupportedAppUsage
private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
阻塞的时长由 timeoutMillis 决定,有三种情况:
- timeoutMillis = -1,会无限期阻塞,直到有人调用 nativeWake() 主动唤醒。
- timeoutMillis = 0,不进行阻塞。
- timeoutMillis > 0,最长阻塞 timeoutMillis 毫秒后自动唤醒。
在 next() 中,nextPollTimeoutMillis 初始化为 0,所以 for 的第一次循环不会被 nativePollOnce() 阻塞。
MessageQueue 中的阻塞与唤醒没有使用 wait()/notify(),而是使用了底层 epoll 机制的 nativePollOnce()/nativeWake()。
然后在第 3 步中,如果要出队的 Message 还没到执行时间,就计算出当前时间与执行时间的时间差,赋值给 nextPollTimeoutMillis,下一次 for 循环开始时就会调用 nativePollOnce() 阻塞。而如果队列中没有消息了,就把 nextPollTimeoutMillis 置为 -1 进行无限期阻塞。
for 循环的最后一句 nextPollTimeoutMillis = 0,是因为在队列为空或者当前时间还没到消息的执行时间时,会执行一次闲时任务。如果是在等待消息执行时间期间运行了闲时任务,那么运行之后可能已经到了执行时间,所以需要立即去队列中查找是否有到了执行时间的消息,因此让 nextPollTimeoutMillis = 0,在下次循环开启时不阻塞。
阻塞线程也是 Looper.loop() 的死循环不会造成 ANR 的原因之一。还有一个原因是,ANR 检测的是任务执行是否超时,这个任务一般都是通过 Handler 发消息入队,再由 Looper 取出交给 Handler 执行的,只有当任务本身执行超过规定时间才会触发 ANR,而 Looper.loop() 是通过死循环取出消息,并不属于 ANR 的检测范畴。
退出消息循环
接下来看第 4 步,mQuitting 为 true 时调用 dispose() 释放 native 的消息队列并返回 null:
// Disposes of the underlying message queue.
// Must only be called on the looper thread or the finalizer.
private void dispose() {
if (mPtr != 0) {
nativeDestroy(mPtr);
mPtr = 0;
}
}
private native static void nativeDestroy(long ptr);
调用 nativeDestroy() 释放 native 的 NativeMessageQueue 对象:
android_os_MessageQueue.cpp:
static void android_os_MessageQueue_nativeDestroy(JNIEnv* env, jclass clazz, jlong ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->decStrong(env);
}
并且把 mPtr 置为 0,mPtr 全局只有两个地方被赋值,除了此处,就是在 MessageQueue 的构造方法中:
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
private native static long nativeInit();
nativeInit() 就是创建了一个 NativeMessageQueue 对象并返回:
android_os_MessageQueue.cpp:
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
if (!nativeMessageQueue) {
jniThrowRuntimeException(env, "Unable to allocate native queue");
return 0;
}
nativeMessageQueue->incStrong(env);
return reinterpret_cast<jlong>(nativeMessageQueue);
}
所以正常情况下 mPtr 不会为 0,如果为 0 了就说明已经退出消息循环并且释放了 NativeMessageQueue。这也就与 next() 开始的 ptr = 0 时直接返回 null 对上了,意思是退出消息循环后,不能再重新开启循环。
当退出循环,返回给 Looper.loop() 一个 null 之后,loop() 也会从死循环中 return 结束死循环:
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) {
// 从 MessageQueue 的 next() 得到 null 就说明消息队列退出了,
// 这边就结束循环。
return;
}
......
}
}
退出循环的标记位 mQuitting 只能通过 quit() 设置:
void quit(boolean safe) {
// 主线程不允许退出消息循环。mQuitAllowed 在 MessageQueue 的构造方法中指定。
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.
// 唤醒 mPtr 对应的 NativeMessageQueue 对象,我们可以假设 mPtr != 0
// 因为 mQuitting 在执行本方法之前是 false
nativeWake(mPtr);
}
}
如果一个子线程持有的消息队列不再使用了,就可以调用 quit() 退出消息循环。
闲时任务
第 5 步,pendingIdleHandlerCount 在上面被初始化为 -1,然后在队列为空或者当前没到消息执行时间时,可以尝试执行闲时任务。闲时任务 IdleHandler 是 MessageQueue 内定义的静态接口:
// 回调接口,当一个线程将要阻塞,以等待更多消息时被触发
public static interface IdleHandler {
// 在两种情况下被调用:
// 1.消息队列中已经没有消息。
// 2.消息队列中仍有消息,但是当前时间点未到队列头部消息的执行时间。
// 返回值为 true 表示需要保持 IdleHandler 存活,false 表示需要移除。
boolean queueIdle();
}
queueIdle() 的执行条件正好与第 5 步中的 if(pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) 条件吻合。而 mIdleHandlers 是保存所有实现了 IdleHandler 的接口集合,实现类会调用 addIdleHandler() 添加到 mIdleHandlers 中:
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
将 mIdleHandlers 转换成对应的数组 mPendingIdleHandlers 后,就开始第 6 步,执行这些闲时任务,每执行完一个就将 mPendingIdleHandlers[i] 置为 null 释放掉保存的 IdleHandler,因为闲时任务只执行一次,没必要让 mPendingIdleHandlers 数组一直保存不用的对象。
第 6 步的 for 循环执行完,将 pendingIdleHandlerCount 置为 0,这样下一次循环时,会进到 if (pendingIdleHandlerCount <= 0) 这个条件,让 mBlocked = true 并且 continue 跳出本次循环,保证后面的代码不会再执行。
至此 next() 流程分析完毕,总结一下:
- 队列中有消息的前提下,如果消息队列的第一条消息是同步屏障,那么就只取异步消息出队,不取同步消息;否则就按照队列顺序由前至后取消息出队。
- 如果队列中没有消息了,或者当前时间还没到队头消息的执行时间,就会调用 nativePollOnce() 阻塞线程,阻塞时长根据 nextPollTimeoutMillis 的值来确定。在第一次阻塞之前,会执行且仅执行一次闲时任务。
- 如果在 next() 的执行过程中,mQuitting 标记被置为 true,说明要退出当前的消息循环,释放 native 层的相关变量,并返回 null 给 Looper.loop() 从而结束 loop() 内的死循环。主线程 Looper 不可退出。
MessageQueue 其实采用了生产者-消费者的设计模式,MessageQueue 中没有消息时会阻塞线程,直到有新消息入队才唤醒。但是与标准的生产者-消费者模式相比,MessageQueue 并没有硬性设置消息数量的上限,只是在 Message 中建立了一个消息池来进行回收复用。个人理解应该是像 ActivityThread 这样的系统操作中也依赖 Handler 发送和处理消息,如果有重要操作因为消息数量超过上限而导致线程阻塞,会发生严重问题。
2.Handler#dispatchMessage()
再看 Looper.loop() 中的第 2 步,将从消息队列中取出的消息分配给对应的 Handler:
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
首先从表面上就能看出,处理消息的方法是有优先级的。最高的是 Message 的 callback 字段不为空时,会执行 handleCallback():
private static void handleCallback(Message message) {
message.callback.run();
}
前面在说 Handler#post(Runnable runnable) 时说过,callback 其实就是个 Runnable,这里执行的也正是 post() 参数 runnable 内的 run()。
其次是当 Handler 内的 mCallback 不为空时,会执行 mCallback.handleMessage()。这个 mCallback 是 Handler 内部接口 Callback 的实例:
public interface Callback {
/**
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
boolean handleMessage(@NonNull Message msg);
}
需要通过 Handler 的构造方法指定:
@UnsupportedAppUsage
final Callback mCallback;
@Deprecated
public Handler(@Nullable Callback callback) {
this(callback, false);
}
public Handler(@NonNull Looper looper, @Nullable Callback callback) {
this(looper, callback, false);
}
@UnsupportedAppUsage
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
Callback 接口的 handleMessage() 注释上说,如果返回 true 表示不会再做进一步处理,意思其实是在 dispatchMessage() 的 else 内,如果 mCallback.handleMessage(msg) 为 true 就会直接 return,不会再执行到 Handler#handleMessage()。因此 Handler 的 handleMessage() 优先级是最低的。
所以在创建 Handler 对象时,通常有两种写法:
Handler mHandler1 = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
}
};
Handler mHandler2 = new Handler(Looper.getMainLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
return false;
}
});
到这里 Handler 的工作流程分析完毕。
五、其他问题
1.内存泄漏
泄漏原因
内存泄漏的原因有两个:
- 非静态内部类、匿名内部类会持有外部类的引用 this,而静态内部类则不会。
- Handler 的生命周期可能会比它所在的 Activity/Fragment 要长,当 Activity/Fragment 结束任务要销毁进行内存回收时,Handler 可能还活着并持有其引用,导致其无法被回收而发生内存泄漏(就是 Activity 都销毁了,消息还没执行)。
那为什么 Handler 的生命周期比较长呢?还记得 Handler 在发送消息时做了什么吗?
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
// msg 的分发目标为当前的 Handler 对象
msg.target = this;
......
}
将 Handler 对象赋值给 Message 的 target 字段,也就是说 Message 还持有 Handler 的引用,一直持有到 Message 被执行,最后调用 recycleUnchecked() 被回收到消息池,清空包括 target 字段在内的所有信息后,才不再持有 Handler 对象。
所以当 Handler 是一个非静态内部类,并且其消息执行在 Activity/Fragment 之后,就会发生内存泄漏。
此外还有一种情况会造成内存泄漏,与 Handler 有关但不是主因,看以下代码:
public class MainActivity extends AppCompatActivity {
private Thread mThread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
test();
}
private void test() {
mThread = new Thread(new Runnable() {
@Override
public void run() {
Message message = Message.obtain();
SystemClock.sleep(4000); // 模拟耗时操作
mHandler.sendMessage(message);
}
});
mThread.start();
}
Handler mHandler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
startActivity(new Intent(MainActivity.this, SecondActivity.class));
return true;
}
});
@Override
protected void onDestroy() {
super.onDestroy();
Log.d("Test", "onDestroy");
}
}
进入 MainActivity 后让子线程休眠 4 秒然后发一条消息,Handler 处理消息时开启 SecondActivity 。如果在休眠的 4 秒内双击 back 退出 MainActivity,会触发 onDestroy(),但是到了 4 秒时 SecondActivity 还是被打开了!!!
这个内存泄漏的原因确实还是因为 mHandler 生命周期比 Activity 长且持有其 this,但是 mHandler 生命周期长的原因不是因为 Message.target 持有它的引用,因为 Activity 被销毁时,mHandler 还没发 Message。本质上来说,这属于线程生命周期长于外部类使其无法被回收,造成内存泄漏的主犯是子线程,Handler 只能算从犯。需要在 Activity 销毁时结束在其内部创建的线程。但是针对示例代码,如果偏要从 Handler 的方向解决,可以尝试在 onDestroy() 中将 mHandler 置为 null,但是使用 mHandler 主调之前要判空避免 NPE,并不是一种根本的方法。
解决方法
针对 Handler 造成内存泄漏的两个原因,可以给它声明成一个静态内部类,让它不再持有外部类的引用;或者在外部类销毁之前,移除 Handler 的所有消息,解除 Message.target 对 Handler 的引用。以下是具体操作方法。
静态内部类
除了将 Handler 声明成一个静态内部类之外,最好还要让该静态内部类持有外部类的弱引用,这样 GC 回收的时候能保证外部类被回收:
public class MainActivity extends AppCompatActivity {
private TextView mTextView;
private MyHandler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = findViewById(R.id.text);
mHandler = new MyHandler(Looper.myLooper(), this);
}
private static class MyHandler extends Handler {
// 对外部类用弱引用
private WeakReference<MainActivity> mReference;
public MyHandler(@NonNull Looper looper, MainActivity activity) {
super(looper);
mReference = new WeakReference<>(activity);
}
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case 1:
// 比如要更新 TextView
MainActivity activity = mReference.get();
activity.mTextView.setText("更新TextView");
break;
// .....
}
}
}
}
外部类销毁前移除消息
再就是在外部类销毁时,移除 Handler 消息队列中的所有消息:
@Override
protected void onDestroy() {
super.onDestroy();
// 移除所有 Callback 和 Message
mHandler.removeCallbacksAndMessages(null);
// mHandler.removeMessages(whatValue);
}
或者通过 removeMessages() 根据 Message.what 定向移除消息。
2.内存共享与线程安全
Handler 的作用之一是实现线程间通信,其采用的是内存共享的实现方案。内存共享体现在 Message 上。以常用的子线程通知主线程更新 UI 的过程为例:
- 主线程中创建 Handler,与之绑定的是主线程的 Looper 和 MessageQueue。
- 子线程中获取 Message 对象并通过 Handler 发送到对应的,也就是主线程的 MessageQueue 中,从而实现 Message 从子线程到主线程的传递,就是在这里发生的线程切换。
- 再由 Looper 从 MessageQueue 中取出分配给主线程的 Handler 执行,最终实现子线程创建的 Message 被主线程的 Handler 处理。
Message 对象在内存中,而内存是不分主线程还是子线程的,任何线程都可以访问内存中的对象。对象是可以被多个线程共享的,而方法才说是属于哪个线程的,因此不能说 Message、MessageQueue 是属于哪个线程的。
换一个角度来看,一个 Handler 可能会从不同的线程中发消息到自己的 MessageQueue 中,这样 MessageQueue 就需要解决线程安全问题。它采用了内置的同步锁 synchronized 关键字来解决,你翻看前面贴出的源码,在入队的 enqueueMessage()、出队的 next()、退出消息循环的 quit()、将同步屏障出入队的 postSyncBarrier() 和 removeSyncBarrier() 等等方法内部都用 synchronized (this) 来设置了对同一个 MessageQueue 对象的锁以保证线程安全。
next() 将消息出队时加同步锁,是为了跟入队操作互斥。因为如果取消息时,有新消息在队头插入,就会导致取出的消息不是最新插入的消息。
在学习 Handler 时除了搞清楚流程和源码外,还要学习这种内存管理思想。
3.epoll 机制的阻塞与唤醒
在 MessageQueue 中没有使用我们常见的 wait()/notify() 这套方法来做线程的等待与唤醒,而是使用了 nativePollOnce()/nativeWake(),其中 nativePollOnce() 仅在 next() 无限循环的头部调用过一次,而 nativeWake() 在调用 quit() 退出队列、removeSyncBarrier() 移除同步屏障和 enqueueMessage() 将消息入队时都会被调用。下面我们来看一看这两个方法在 native 层是如何调用的。
两个 native 方法是在 Java 层的 MessageQueue.java 中声明的,其对应的 JNI 层文件为 /frameworks/base/core/jni/android_os_MessageQueue.cpp:
static const JNINativeMethod gMessageQueueMethods[] = {
/* name, signature, funcPtr */
{ "nativeInit", "()J", (void*)android_os_MessageQueue_nativeInit },
{ "nativeDestroy", "(J)V", (void*)android_os_MessageQueue_nativeDestroy },
{ "nativePollOnce", "(JI)V", (void*)android_os_MessageQueue_nativePollOnce },
{ "nativeWake", "(J)V", (void*)android_os_MessageQueue_nativeWake },
{ "nativeIsPolling", "(J)Z", (void*)android_os_MessageQueue_nativeIsPolling },
{ "nativeSetFileDescriptorEvents", "(JII)V",
(void*)android_os_MessageQueue_nativeSetFileDescriptorEvents },
};
能看到两个方法对应的 JNI 方法名,我们先看 android_os_MessageQueue_nativePollOnce():
// android_os_MessageQueue.cpp:
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
jlong ptr, jint timeoutMillis) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}
它调用了 NativeMessageQueue 的 pollOnce():
// android_os_MessageQueue.cpp:
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
mPollEnv = env;
mPollObj = pollObj;
mLooper->pollOnce(timeoutMillis);
mPollObj = NULL;
mPollEnv = NULL;
if (mExceptionObj) {
env->Throw(mExceptionObj);
env->DeleteLocalRef(mExceptionObj);
mExceptionObj = NULL;
}
}
再调用 Looper 的 pollOnce():
// /system/core/libutils/Looper.cpp
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
...
result = pollInner(timeoutMillis);
}
}
int Looper::pollInner(int timeoutMillis) {
...
// 利用 epoll_wait() 等待 timeoutMillis 长时间
int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
...
return result;
}
以上是 nativePollOnce() 的调用过程,再看 nativeWake():
// android_os_MessageQueue.cpp:
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->wake();
}
void NativeMessageQueue::wake() {
mLooper->wake();
}
// /system/core/libutils/Looper.cpp
void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
ALOGD("%p ~ wake", this);
#endif
// 向 mWakeEventFd 写入 1 以唤醒 Looper
uint64_t inc = 1;
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));
if (nWrite != sizeof(uint64_t)) {
if (errno != EAGAIN) {
LOG_ALWAYS_FATAL("Could not write wake signal to fd %d (returned %zd): %s",
mWakeEventFd.get(), nWrite, strerror(errno));
}
}
}