前言
作为 Android 开发者,相信对于 Handler 的使用早已烂熟于心。Handler 对于 Android 非常重要,可以说,没有它,Android App 就是一堆“破铜烂铁”,它就像 Android 的血液,穿梭在 App 的各个角落,输送养分。
理解 Handler
的运行原理,可以帮助我们更好地认识 Android
系统的本质。
而且 Handler
作为 Android
非常重要的一个工具,其源码却非常清晰、简洁,非常适合作为【Android源码阅读】 的一个起点。
本文可以了解到
Handler 内部是如何运转的?
它和 NDK 层有什么关联?
Android 系统是如何利用 Handler 来驱动 App 运行的?
解答一些关于 Handler 的经典问题
一、如何使用
相信大家对 Handler
的使用已经很熟悉了,主要分为两种情况:一是在主线程使用,二是在子线程使用。
简单来看看如何使用 Hanlder
,并从中可以得到一些什么疑问和结论。带着疑问来看源码,将会有更多的收获。
主线程中
class HandlerActivity: AppCompatActivity() {
private val mHandler = MyHandler()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 1. 通过自定义的 Handler 发消息
mHandler.sendEmptyMessageDelayed(1, 1000)
// 2. 通过默认的 Handler 发消息
Handler().post {
Log.i("HandlerActivity", "run postDelay")
}
}
// 自定义一个 Handler
class MyHandler: Handler() {
override fun handleMessage(msg: Message) {
Log.i("HandlerActivity", "主线程:handleMessage: ${msg.what}")
}
}
}
可以看到,两种不同的发送消息方式,都可以实现消息的处理,一个是在 Handler
中处理,一个是在 Runnable
处理。
可以得出一个结论:
Handler
可以既自己处理消息,也可以将消息发到Runnable
中处理。
同时存在一个疑问:
如果两个同时存在,那么哪一个会接收到消息呢?
子线程中
- 实现线程切换
- 在主线程中创建好 Handler
class HandlerActivity: AppCompatActivity() {
private var mHandler = MyHandler()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Thread {
mHandler.sendEmptyMessageDelayed(1, 1000)
}.start()
}
class MyHandler: Handler() {
override fun handleMessage(msg: Message) {
Log.i("HandlerActivity", "主线程:handleMessage: ${msg.what}")
}
}
}
- 在子线程中创建 Handler
class HandlerActivity: AppCompatActivity() {
private var mHandler: Handler? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Thread {
mHandler = MyHandler(Looper.getMainLooper())
mHandler!!.sendEmptyMessageDelayed(1, 1000)
}.start()
}
class MyHandler(looper: Looper): Handler(looper) {
override fun handleMessage(msg: Message) {
Log.i("HandlerActivity", "主线程:handleMessage: ${msg.what}")
}
}
}
- 创建属于子线程的 Handler
class HandlerActivity: AppCompatActivity() {
private var mHandler: Handler? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Thread {
// 准备 Looper
Looper.prepare()
mHandler = MyHandler()
mHandler!!.sendEmptyMessageDelayed(1, 1000)
// 开启 loop 循环
Looper.loop()
}.start()
}
class MyHandler(): Handler() {
override fun handleMessage(msg: Message) {
Log.i("HandlerActivity", "子线程:handleMessage: ${msg.what}")
}
}
}
以上3种情况中,前2种虽然 Handler
初始化的位置不一样,但是效果却是一样的,第2种方式虽然是在子线程中初始化 Handler
,但是它接受了 Looper.getMainLooper()
作为参数,表明它的消息回调处理跟主线程同一个 Looper
。
而第3种情况就不一样了,首先是在子线程中准备了一个 Looper
,在最后开启了 loop 循环
。并且,Handler
的消息处理最终是在子线程中回调处理的。
以上分析,可以得出一个结论:
Handler
的消息最终在哪个线程中进行处理,与Looper
有着直接的关联。
同时也有一个疑问:
为什么在子线程中,要传递主线程的
Main Looper
,或者自己准备一个Looper
,才能正常使用Handler
,而主线程却不需要?
带着以上的结论和疑问,开启 Handler
探秘之旅。
二、如何运作
在日常开发中,虽然最常使用的是 Handler
和 Message
这两个类,实际上,整个消息处理机制中,核心部分却不是这两个,而是另外两个:轮询器 Looper
和 消息队列 MessageQueue
。
首先来看下这几个类的依赖关系:
先看左边 Java
部分,整个消息机制使用到的类其实环环相扣的,
Handle
作为一个对外暴露的工具,其内部包含了一个Looper
;Looper
作为消息循环的核心,其内部包含了一个消息队列MessageQueue
,用于记录所有待处理的消息;MessageQueue
则作为一个消息队列,则包含了一系列链接在一起的Message
;Message
则是消息体,内部又包含了一个目标处理器target
,这个target
正是最终处理它的Handler
。
为什么还有右边的 NDK
层的部分?
实际上,Java
层的四个类构成了整个消息循环的主体,而循环的等待阻塞则是通过 NDK
层,借助 Linux
的 epoll
实现阻塞等待功能。
什么是 epoll ?
来自百度百科
epoll 是 Linux 内核为处理大批量文件描述符而作了改进的 poll,是 Linux 下多路复用IO接口 select/poll 的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统 CPU 利用率。
epoll
相关的知识已经超出 Handler
核心内容,本文不会深入探讨,请自行 Google吧~
简单理解:epoll 是 Linux 中用来监听 IO 事件的工具。一个进入等待的句柄,一旦监听到事件发生,就会被唤醒,继续往下执行。
下面按照这个思路,逐一对它们进行分析。
消息处理者:Handler
Handler
是发起消息的入口,看看它是如何被构造出来的。
默认构造
// Handler.java
public Handler() {
this(null, false);
}
public Handler(@Nullable Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class 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 sThreadLocal = new ThreadLocal();
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
看到 ThreadLocal
马上就可以知道,Looper
是和线程相关的,同时也说明了一个线程中只会有一个 Looper
。
从注释中还能得到一个信息,如果要获取到一个非空的 Looper
,那么必须在这个线程中调用一次 Looper.prepare()
。
到这里,论证上面的一个结论了:
Handler
的消息最终在哪个线程中进行处理,与Looper
有着直接的关联从上面的分析知道,
Looper
确实是和线程直接关联的。
传入 Looper 构造
// Handler.java
public Handler(@NonNull Looper looper) {
this(looper, null, false);
}
public Handler(@NonNull Looper looper, @Nullable Callback callback) {
this(looper, callback, false);
}
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
这种方式,则比较简单了,只需将传递进来的 Looper
保存起来即可,但是要注意的是, Looper
不能为 null
。
发送消息
// Handler.java
public final boolean sendEmptyMessage(int what) {
return sendEmptyMessageDelayed(what, 0);
}
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
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.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
无论哪个发送消息的方法,最终的都是调用 enqueueMessage
方法,将 Message
放入 MessageQueue
队列中。
那么,消息放入队列后,是如何在到达时间后,被调用执行的呢?这就要来看 Looper
了。
轮询器:Looper
首先来看 Looper
是如何初始化的。
前面 Handler
初始化的时候,有一段注释说,Looper.myLooper()
要获取到 Looper
,必须要先调用 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
,暂且不管,后面在细看。
开启消息循环
想要开启消息循环,需要通过 Looper.loop()
。
下面来看这个方法(这里只贴出核心代码,去除一些异常判断代码):
// Looper.java
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) {
// No message indicates that the message queue is quitting.
return;
}
try {
msg.target.dispatchMessage(msg);
//省略一些代码...
} catch (Exception exception) {
//省略一些代码...
throw exception;
} finally {
//省略一些代码...
}
//省略一些代码...
msg.recycleUnchecked();
}
}
虽然整个方法比较长,但是主干代码其实非常简单:拿到消息队列 queue
以后,进入死循环。
循环中就做一件事:从消息队列中获取到一个可以执行的 Message
,接着调用这个消息的 target
(即 Handler
)的 dispatchMessage
方法,消息就分发出去了。
如何从队列中拿到一个可执行的消息,就要靠 MessageQueue
了。
消息队列:MessageQueue
前面讲过,构造 Looper
的时候,会创建消息队列,即 MessageQueue
。
// MessageQueue.java
private native static long nativeInit();
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
这里就和 NDK
发生关联了,在构造方法中,通过本地方法 nativeInit
创建了一个对象 mPtr
,这个对象就是 NDK
层的 NativeMessageQueue
。这里暂且不说,第三节再介绍。
消息压入
在了解如何获取可执行消息之前,我们需要先知道消息是如何被压入队列的。
说明: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个步骤(见以上代码标注)。
-
mMessages
是队列的第一消息,获取到它 - 判断消息队列是不是空的,是则将当前的消息放到队列头部;如果当前消息不需要延时,或当前消息的执行时间比头部消息早,也是放到队列头部。
- 如果不是以上情况,说明当前队列不为空,并且队列的头部消息执行时间比当前消息早,需要将它插入到队列的中间位置。
如何判断这个位置呢?依然是通过消息被执行的时间。
通过遍历整个队列,当队列中的某个消息的执行时间比当前消息晚时,将消息插到这个消息的前面。
可以看到,消息队列是一个根据消息【执行时间先后】连接起来的单向链表。
想要获取可执行的消息,只需要遍历这个列表,对比当前时间与消息的执行时间,就知道消息是否需要执行了。
获取可执行消息
在 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 个步骤(见以上代码标注):
- 进入 for 循环来遍历消息队列,调用
NDK
方法nativePollOnce
挂起线程,第一次进入时,等待时间为0
,继续往下执行; - 判断队列是否插入了同步屏障,是则只执行异步消息(同步屏障暂且放一边,后面详细介绍),如果没有同步屏障,则获取到的是队列头部消息;
- 对比当前时间与消息执行时间,如果时间没到,计算需要等待的时间,重新进入等待;
- 如果消息执行时间已到,重新拼接链表,并返回该消息,此时,
Looper
将会得到一个消息,将它分发给Handler
处理。 - 最后是一个空闲监听处理,作用是当队列中没有需要执行的消息时,说明线程进入空闲状态,这时候可以去执行一些其他的任务。
唤醒线程
当消息队列为空的时候,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
来唤醒挂起的线程。
两种情况会唤醒线程:
- 【队列为空 || 消息无需延时 || 或消息执行时间比队列头部消息早】 && 【线程处于挂起状态时(mBlocked = true)】
- 【线程挂起(mBlocked = true)&& 消息循环处于同步屏障状态】,这时如果插入的是一个异步消息,则需要唤醒
以上,基本就是整个消息机制的流程,简洁、清晰。
简单总结一下:
- 准备 Looper:Looper.looper()
- 创建消息队列 MessageQueue:在构造 Looper 的时候被创建
- 创建 Handler: 用户自定义 Handler
- 开启 Loop 循环:Looper.loop()。循环遍历消息队列,判断是否到达执行时间
- 用户发送消息:通过 Handler 的 sendMessageDelay 等方法,将消息压入队列,等待被 Looper 遍历和执行
消息体:Message
消息体的结构
public final class Message implements Parcelable {
public int what;
public int arg1;
public int arg2;
public Object obj;
Handler target;
Runnable callback;
Message next;
// 省略其他...
}
以上仅贴出 Message
几个重点的成员,前面几个相信不必再说,主要来看看 target
和 callback
,这两个就是最终处理消息的地方。
先来看下这两个是在哪里赋值的,回到 Handler
发送消息的地方:
// 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);
}
在压入消息队列之前,target
被赋值为 this
也就是发送这个消息的 Handler
。
再看下 callback
的赋值:
// Handler.java
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
在使用 post
方法的时候,Handler
同样是构造了一个消息体,并把 Runnable
赋值给了 callback
。
第一节提出了一个疑问:当通过
post
发送消息的时候,target
和post
都被赋值了,那么最后处理消息的是哪一个呢?
上文消息循环的分析知道,当 Looper
获取到一个可执行的消息时,会调用 Handler
的 dispatchMessage
方法:
// Handler.java
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
/**
* Subclasses must implement this to receive messages.
*/
public void handleMessage(@NonNull Message msg) {
}
可以看到,消息首先会分发给 callback
,也就是 Runnable
处理;
如果 callback == null
并且 Handler
自己的 mCallback != null
就分发给 mCalllback
处理;
最后才是给到自己的 handleMessage
处理,用户继承 Handler
并重写这个方法就可以处理消息事件了。
那么这个 mCallback
是什么呢?它是 Handler
的一个接口:
// Handler.java
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
的一个构造方法:
// Handler.java
public Handler(@Nullable Callback callback) {
this(callback, false);
}
public Handler(@Nullable Callback callback, boolean async) {
// 省略无关代码...
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
也就是说,在使用 Handler
的时候,并不一定要继承 Handler
来处理消息,也可以通过实现 Callback
这个接口,来处理消息。
消息池
网上常有文章提到,在生成消息的时候,最好是用 Message.obtain()
来获取一个消息,这是为什么呢?先来看看这个方法:
// Message.java
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();
}
可以看到,这是一个消息池
,如果消息池不为空,就会从池中获取一个消息,达到复用的效果。
消息是如何被回收到池子里面的呢?
在 Looper
消息循环的最后有一句代码:
// Looper.java
public static void loop() {
// 省略无关代码...
for (;;) {
// 省略无关代码...
try {
msg.target.dispatchMessage(msg);
} catch (Exception exception) {
// 省略无关代码...
} finally {
// 省略无关代码...
}
// 省略无关代码...
msg.recycleUnchecked();
}
}
消息处理完毕后,对消息进行回收。
// Message.java
private static final int MAX_POOL_SIZE = 50;
void recycleUnchecked() {
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = UID_NONE;
workSourceUid = UID_NONE;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
原来,消息池也是一个消息链表队列,除去了消息所有的信息以后,就会把消息加入队列头部。
同时可以看到,消息池的最大缓存数量为 50
。
消息池,可以避免大量的消息被创建,节省内存。所以,尽量通过
Message.obtain()
来创建消息。
三、NDK 层的消息工具
前面就介绍过,Java
层实现了整个消息循环的核心功能,而消息循环线程的等待挂起则是通过 NDK
的 epoll
实现的。
与 NDK
层交互的是 MessageQueue
,看下其中的本地方法:
// MessageQueue.java
private native static long nativeInit();
private native static void nativeDestroy(long ptr);
private native void nativePollOnce(long ptr, int timeoutMillis);
private native static void nativeWake(long ptr);
private native static boolean nativeIsPolling(long ptr);
private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
重点看下初始化
、挂起
、唤醒
三个方法。
private native static long nativeInit();
private native void nativePollOnce(long ptr, int timeoutMillis);
private native static void nativeWake(long ptr);
消息队列:NativeMessageQueue
以上本地方法的实现,是在 Android 源码
的 AOSP/platform_frameworks_base-android10-c2f2-release/core/jni/android_os_MessageQueue.cpp
中。
//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(nativeMessageQueue);
}
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
jlong ptr, jint timeoutMillis) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast(ptr);
nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast(ptr);
nativeMessageQueue->wake();
}
在 NDK
层,创建了一个 NativeMessageQueue
,你肯定会有疑问,这个消息队列和 Java
层的消息队列有什么关系呢?
可以说,这两个消息队列其实没有太大的关系,
Java
层 的MessageQueue
其实只是 “借用” 了NativeMessageQueue
的线程挂起功能而已。
// MessageQueue.java
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
Java
层的 MessageQueue
初始化后,保存了 NDK
层的 NativeMessageQueue
指针引用,后续的线程挂起和唤醒,都是通过这个指针完成的。
初始化
// NativeMessageQueue.cpp
NativeMessageQueue::NativeMessageQueue() :
mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
mLooper = Looper::getForThread();
if (mLooper == NULL) {
mLooper = new Looper(false);
Looper::setForThread(mLooper);
}
}
这里和 Java
层倒过来了,NativeMessageQueue
借助 Looper
实现消息循环。
️ 这里的 Looper
不是 Java
层的,而是 NDK
层的 Looper.cpp
。
挂起和唤醒
// NativeMessageQueue.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;
}
}
void NativeMessageQueue::wake() {
// 唤醒
mLooper->wake();
}
关于 epoll
的部分,都封装在 Looper
中,下面就来看看具体的实现。
轮询器:Looper
先来了解下 epoll
,前面介绍过,epoll
是 Linux
平台用来监听 IO 事件的工具。
其使用非常简单,三步曲:
#include
// 创建句柄
int epoll_create(int size);
// 添加/删除/修改 监听事件
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// 进入等待
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
下面就来看看 Android
系统是如何实现这三步曲的。
Looper.cpp
位于 : AOSP/platform_system_core-android10-c2f2-release/libutils/Looper.cpp
构造
// Looper.cpp
Looper::Looper(bool allowNonCallbacks)
: mAllowNonCallbacks(allowNonCallbacks),
mSendingMessage(false),
mPolling(false),
mEpollRebuildRequired(false),
mNextRequestSeq(0),
mResponseIndex(0),
mNextMessageUptime(LLONG_MAX) {
mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
LOG_ALWAYS_FATAL_IF(mWakeEventFd.get() < 0, "Could not make wake event fd: %s", strerror(errno));
AutoMutex _l(mLock);
rebuildEpollLocked();
}
void Looper::rebuildEpollLocked() {
if (mEpollFd >= 0) {
mEpollFd.reset();
}
//【1】创建 epoll 句柄
mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC));
//【2】新建唤醒监听事件
struct epoll_event eventItem;
memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
// 设置监听内容可读事件
eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeEventFd.get();
//【3】将唤醒监听事件注册到句柄中
int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &eventItem);
// 注册其他监听事件,Handle 时为空
for (size_t i = 0; i < mRequests.size(); i++) {
const Request& request = mRequests.valueAt(i);
struct epoll_event eventItem;
request.initEventItem(&eventItem);
int epollResult = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, request.fd, &eventItem);
if (epollResult < 0) {
ALOGE("Error adding epoll events for fd %d while rebuilding epoll set: %s",
request.fd, strerror(errno));
}
}
}
在构造方法中调用了 rebuildEpollLocked()
,这个方法中创建了 epoll
的句柄,并将唤醒监听事件注册进去,详见以上代码注释。
线程挂起
Java
层调用 NativeMessageQueue
的 pollOnce
时,最终调用的是 Looper
的 pollOnce
。
// Looper.cpp
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
while (mResponseIndex < mResponses.size()) {
const Response& response = mResponses.itemAt(mResponseIndex++);
int ident = response.request.ident;
if (ident >= 0) {
int fd = response.request.fd;
int events = response.events;
void* data = response.request.data;
if (outFd != nullptr) *outFd = fd;
if (outEvents != nullptr) *outEvents = events;
if (outData != nullptr) *outData = data;
return ident;
}
}
if (result != 0) {
if (outFd != nullptr) *outFd = 0;
if (outEvents != nullptr) *outEvents = 0;
if (outData != nullptr) *outData = nullptr;
return result;
}
result = pollInner(timeoutMillis);
}
}
进入 for 循环以后,如果只是 Handler 使用的情况下,mResponses
是空的,所以可以忽略。最后会进入 pollInner
方法。
由于 NDK
层的 Looper
也是类似于 Java
层 Handler
的消息机制功能,需要实现类似的功能,这里剔除掉一些 pollInner
中的代码,只看 挂起/唤醒
相关的功能,会更直观:
int Looper::pollInner(int timeoutMillis) {
// 省略一些代码...
int result = POLL_WAKE;
mResponses.clear();
mResponseIndex = 0;
mPolling = true;
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
// 【1】进入等待挂起
int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
mPolling = false;
mLock.lock();
if (mEpollRebuildRequired) {
mEpollRebuildRequired = false;
rebuildEpollLocked();
goto Done;
}
if (eventCount < 0) {
if (errno == EINTR) {
goto Done;
}
result = POLL_ERROR;
goto Done;
}
if (eventCount == 0) {
result = POLL_TIMEOUT;
goto Done;
}
//【2】遍历所有监听到的事件
for (int i = 0; i < eventCount; i++) {
int fd = eventItems[i].data.fd;
uint32_t epollEvents = eventItems[i].events;
//【3】如果是唤醒事件
if (fd == mWakeEventFd.get()) {
if (epollEvents & EPOLLIN) {
//【4】清空唤醒事件写入的内容
awoken();
} else {
ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);
}
} else {
// 处理其他事件,Handler没有
// 省略一些代码...
}
}
Done: ;
//省略一些代码...
// Release lock.
mLock.unlock();
//省略一些代码...
return result;
}
首先会调用 epoll_wait
进入等待,其中
timeoutMillis
如果为 0
,则立即返回,继续执行;如果为 -1
,进入无限等待,直到外部唤醒;如果大于 0
,则等待指定时间没有被唤醒的话,自动超时唤醒,继续往下执行。
当 timeoutMillis == 0
或者超时唤醒时,会跳转到 Done
,结束调用,返回。这时 Java
层的 MessageQueue
的 next
方法就会继续执行,开始检查是否有可执行的消息。
当 timeoutMillis == -1
进入无限等待时,如果被外部唤醒,需要判断事件是否为指定的事件。如果是 mWakeEventFd
事件,则需要将 事件清空
,然后继续执行,结束调用,返回。
详细请见以上代码标注。
线程唤醒
epoll
的功能是监听事件,如果要唤醒它,其实就是发起一个事件,可以监听的事件有以下几种:
- EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
- EPOLLOUT:表示对应的文件描述符可以写;
- EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
- EPOLLERR:表示对应的文件描述符发生错误;
- EPOLLHUP:表示对应的文件描述符被挂断;
- EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
- EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。
在 rebuildEpollLocked()
中监听的是 EPOLLIN
事件:
void Looper::rebuildEpollLocked() {
// 省略无关代码...
mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC));
struct epoll_event eventItem;
memset(& eventItem, 0, sizeof(epoll_event));
// 监听可读事件
eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeEventFd.get();
int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &eventItem);
// 省略无关代码...
}
因此,只需要往 mWakeEventFd
中随便写入点内容,就可以唤醒了。
来看看 Looper
的 wake
方法:
// Looper.cpp
void Looper::wake() {
uint64_t inc = 1;
// 写入一个 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));
}
}
}
清除写入的内容也很简单,读取出来即可:
// Looper.cpp
void Looper::awoken() {
uint64_t counter;
// 读取写入的内容
TEMP_FAILURE_RETRY(read(mWakeEventFd.get(), &counter, sizeof(uint64_t)));
}
以上,就是 Handler
从 Java
层到 NDK
层的整个实现过程。
接下里,来看一些 Handler
中比较有意思的东西。
四、Handler 同步消息屏障
什么是同步消息屏障
前文中,多次提到同步消息屏障的问题,到底什么是同步消息屏障呢?
屏障,可以阻挡一些消息,什么消息呢?同步消息!
也就是说,同步屏障
是用来阻挡同步消息执行的。
在日常使用中,很少去关心 Handler
的消息是同步还是异步,这是因为默认的消息都是 同步消息
。来看看消息的同步和异步是怎么设置的。
通过 Handler 设置
// Handler.java
public Handler() {
this(null, false);
}
public Handler(@Nullable Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class 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
的标记,也就是说,开发者是不被允许启动同步消息屏障的。
同步消息屏障的作用
那了解同步消息屏障有啥作用啊?“只可远观,不可亵玩” 呀!别急,往下看吧~
对于前端来说,UI
可以说是系统中最重要的东西,耽误了谁都不能耽误 UI
做事。
如果你看过 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
的同步消息屏障,才能完整地理解Android
系统View
的绘制流程。
五、关于 Hanlder 的经典问题
关于 Handler
经典问题有很多,了解了整个的源码之后,基本都可以得到解答,下面就来看几个经常碰到的问题。
为什么说 Android 是基于消息驱动的
在打开 Apk
的时候,系统会为 Apk
创建一个进程,而我们知道,Linux
进程至少包含一个线程,在 Apk
中,这个线程就是主线程,也就是 UI
线程。
Apk
主线的入口就在 ActivityThread.java
中的 main()
方法。
// ActivityThread.java
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
AndroidOs.install();
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("");
//【1】准备主线程 Looper
Looper.prepareMainLooper();
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);
//【2】获取 Handler
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
//【3】开启消息循环
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
这根本就是启动 Handler
最基本的方法,来看看 getHandler()
方法:
简单地返回了 H
// ActivityThread.java
final H mH = new H();
final Handler getHandler() {
return mH;
}
简单看下 H
的部分代码:
// ActivityThread.java
class H extends Handler {
public static final int BIND_APPLICATION = 110;
@UnsupportedAppUsage
public static final int EXIT_APPLICATION = 111;
@UnsupportedAppUsage
public static final int RECEIVER = 113;
@UnsupportedAppUsage
public static final int CREATE_SERVICE = 114;
@UnsupportedAppUsage
public static final int SERVICE_ARGS = 115;
@UnsupportedAppUsage
public static final int STOP_SERVICE = 116;
// 省略部分代码...
public static final int EXECUTE_TRANSACTION = 159;
public static final int RELAUNCH_ACTIVITY = 160;
// 省略部分代码...
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case BIND_APPLICATION:
AppBindData data = (AppBindData)msg.obj;
handleBindApplication(data);
break;
case EXIT_APPLICATION:
if (mInitialApplication != null) {
mInitialApplication.onTerminate();
}
Looper.myLooper().quit();
break;
case CREATE_SERVICE:
handleCreateService((CreateServiceData)msg.obj);
break;
// 省略部分代码...
// 处理 Activity 生命周期相关的事件
// 如 onCreate/onResume/onPause等
case EXECUTE_TRANSACTION:
final ClientTransaction transaction = (ClientTransaction) msg.obj;
mTransactionExecutor.execute(transaction);
// 省略部分代码...
break;
case RELAUNCH_ACTIVITY:
handleRelaunchActivityLocally((IBinder) msg.obj);
break;
case PURGE_RESOURCES:
schedulePurgeIdler();
break;
}
// 省略部分代码...
}
}
可以看到,几乎从 App
启动,到 Activity
相关的生命周期,Services
相关的生命周期,到 App
退出等等,都是通过 Handler
来驱动的。
可以看出,Android 就是基于 Handler
消息驱动的。
同时也解答了第一节中的疑问:为什么主线程不需要调用
Looper.prepare()
来准备一个Looper
就可以发消息?因为 App 启动的时候,系统已经创建好了
Looper
。
Loop.looper() 死循环为什么不会阻塞主线程
经过上面的分析,App
的主线程中通过 Loop.looper()
开启了消息“死循环”,那为什么 App
依然可以正常运转,没有阻塞到主线程呢?
其实,这个问题就是个伪命题,Loop.looper()
根本就不是死的,而是在事件的驱动下动态运转的,只要有消息来了,线程就会 “活过来” 。
恰恰相反,Loop.looper()
保证了 App
可以活着,因为如果没有 “死循环” ,ActivityThread.main()
在执行完以后,就主线程退出了,再也接收不到消息了,反而是 “死了” !
Handler 如何导致内存泄漏
通常 Handler
导致的内存泄漏,都是引起 Activity
的内存泄漏。
可能日常是这样使用 Handler
的:
class HandlerActivity: AppCompatActivity() {
private val mHandler = MyHandler()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mHandler.sendEmptyMessageDelayed(1, 1000)
}
// 自定义一个 Handler
class MyHandler: Handler() {
override fun handleMessage(msg: Message) {
Log.i("HandlerActivity", "handleMessage: ${msg.what}")
}
}
}
MyHandler
是 HandlerActivity
的内部类,会持有 HandlerActivity
的引用。
在进入页面以后,发送了一个延时 1s 的消息,如果 HandlerActivity
在 1s 内退出了,由于 Handler
会被 Message
持有,保存在其 target
变量中,而 Message
又会被保存在消息队列中,这一系列关联,导致 HandlerActivity
在退出的时候,依然会被持有,因此不能被 GC
回收,这就是内存泄漏!
那么当这个 1s 延时的消息被执行完以后,HandlerActivity
会被回收吗?答案是会的!
因为消息被执行以后,Message
在放入缓存池之前,target
已经被清除,所以持有链就断开了,最终 HandlerActivity
将会被回收。
但这样并不意味着我们就可以不管内存泄漏的问题,内存泄漏始终是内存泄漏,如果是占用内存很大的页面没有被及时回收,有可能会导致 OOM
。
有两个办法可以解决这个问题:
- 将
MyHandler
改为静态类,这样它将不再持有外部类的引用。可以将HandlerActivity
作为弱引用放到MyHandler
中使用,页面退出的时候可以被及时回收。 - 页面退出的时候,在
onDestroy
中,调用Handler
的removeMessages
方法,将所有的消息 remove 掉,这样也能消除持有链。