Android HandlerThread、Looper、MessageQueue 源码分析
简介
在 Android
开发中,大家应该对 HandlerThread
有一定了解。顾名思义,HandlerThread
是 Thread
的一个子类。与普通的 Thread
不同,Thread 通常一次只能执行一个后台任务,如果需要执行多个任务,就必须创建多个线程,这样容易导致资源管理复杂,甚至可能出现内存泄漏等问题。而 HandlerThread
有一个显著的特点,它能够串行地执行多个任务。每个任务通过消息的形式排队执行,线程池中的任务按顺序依次处理,无需手动管理线程的生命周期或调度过程。我们只需通过 Handler 将任务发送到 HandlerThread
中,它会自动按顺序执行,极大简化了开发过程。使用 HandlerThread
的最大优势是:它是单线程的,因此不需要担心线程安全问题。实际上,Android 源码中也有许多地方使用了 HandlerThread
,它在设计思想上值得我们学习和借鉴。接下来,我们将通过源码进一步了解它的实现原理。
源码分析
HandlerThread
内部持有一个Looper
,Looper
中持有一个消息队列MessageQueue
Looper
在了解HandlerThread
前,我们有必要先认识一下Looper
,Looper 是一个负责管理消息队列(MessageQueue
)并循环处理其中消息的类。简单来说,Looper 通过不断地从消息队列中取出消息并处理它们,来实现线程的消息处理机制。每个线程都有自己的消息队列和 Looper,通过 Looper,线程能够不断地处理任务直到任务队列为空。接下来我们会涉及到它以下几个核心接口。
prepare()
是一个静态方法,用来构建一个Looper
对象,quitAllowed
表示这个Looper是否支持退出,默认是支持,像Android 主线程的Looper
是不支持退出的
public static void prepare() {
prepare(true);
}
loopOnce
静态方法,顾名思义,循环一次,它的作用是从当前消息队列MessageQueue
中取一条符合条件的消息进行分发操作
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
Message msg = me.mQueue.next(); // 取一条消息
if (msg == null) {
//正常没消息,队列会堵塞,不会返回null,所以这里是null肯定是已经要结束了,这里返回false 退出looper
return false;
}
//分发消息代码逻辑省略
}
loop()
静态方法,启动循环,在循环中不断调用loopOnce
来处理消息
//关键代码
public static void loop() {
final Looper me = myLooper();
//...
for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) {
//返回false 意味着循环结束
return;
}
}
}
quit()
和quitSafely()
退出looper,前者是立即退出,后者是处理完当前队列中所有消息后退出,最终是调用消息队列MessageQueue
对应的退出方法
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
HandlerThread
- 既然它是一个线程,那可以先从
run
方法入手:
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
可以看出,线程跑起来后,先初始化了一个Looper
,然后启动死循环,HandlerThread
在这里充当的作用是在子线程中开启死循环接受和分发消息
这里有个地方比较有意思,在mLooper = Looper.myLooper();
后面,它调用了notifyAll()
,它起到了什么作用呢?
getLooper()
,我们应该知道要把任务提交给HandlerThread
执行,需要借助Handler
,但是Handler
的构造参数是需要传入一个Looper
对象,所以,这里对外公开了获取Looper的接口
public Looper getLooper() {
if (!isAlive()) {
return null;
}
boolean wasInterrupted = false;
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
wasInterrupted = true;
}
}
}
/*
* We may need to restore the thread's interrupted flag, because it may
* have been cleared above since we eat InterruptedExceptions
*/
if (wasInterrupted) {
Thread.currentThread().interrupt();
}
return mLooper;
}
这里是直接返回了在run
中初始化的mLooper
,但是呢,在非当前线程获取mLooper
对象,就会引发线程安全问题,可能mLooper
还没被初始化就调用了getLooper()
,这样就有可能返回一个空的数据了,所以官方在这里做了while
循环,并且使用了wait()
堵塞,等待上面run
初始化完成后再notifyAll()
这里
getThreadHandler()
就是公开给外面向当前HandlerThread
插入消息的接口,内部维护着一个Handler
对象
@NonNull
public Handler getThreadHandler() {
if (mHandler == null) {
mHandler = new Handler(getLooper());
}
return mHandler;
}
quit()
、quitSafely()
同样,HandlerThread
也有退出的方法,其实现也是调用looper
对应的函数退出
MessageQueue
由于关于消息分发逻辑在其他地方讲过,这里只要分析退出队列的逻辑。
它实现了退出和安全退出的方法,这两个操作有什么区别呢,请看源码
next()
looper每调用一次loopOnce
,内部就会调用messageQueue获取一条消息
Message next() {
//只放关键代码
for (; ; ) {
//堵塞,等待消息或者时机合适或主动唤醒
nativePollOnce(ptr, nextPollTimeoutMillis);
//...
//符合分发条件的 返回这条消息给looper
if (msg != null) {
//...
return msg;
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
//...
//退出状态 则释放队列,结束循环
if (mQuitting) {
dispose();
return null;
}
}
}
quit()
实际逻辑由下面两个removeAllFutureMessagesLocked和removeAllMessagesLocked函数执行
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();
}
//把队列的消息标记完成后,唤醒上面next的堵塞位置nativePollOnce,执行剩下的退出逻辑
nativeWake(mPtr);
}
}
//移除所有消息
private void removeAllMessagesLocked() {
//mMessages 在这里是队列的头
Message p = mMessages;
while (p != null) {//从头开始,把所有消息标记为回收状态
Message n = p.next;
p.recycleUnchecked();
p = n;
}
mMessages = null;//队头消息置空,next()执行时会判断这个,为空直接退出队列
}
//移除所有未来消息,退出这一瞬间之前提交的消息保留继续执行
private void removeAllFutureMessagesLocked() {
final long now = SystemClock.uptimeMillis();
Message p = mMessages;
if (p != null) {
if (p.when > now) {//队头的时间比当前新,说明全是后面新加的,全部回收掉
removeAllMessagesLocked();
} else {//以下为从消息队列中找到退出那一瞬间时间一样的分割点,把分割点前的消息与队列断开链接
Message n;
for (;;) {
n = p.next;
if (n == null) {
return;
}
if (n.when > now) {
break;
}
p = n;
}
p.next = null;//断开链接,这里把队列从分割点切断
do {
p = n;
n = p.next;
p.recycleUnchecked();//把所有未来消息标记为回收状态
} while (n != null);
}
}
}
从上面可以看出,安全退出时,队列会把所有未来消息移除掉,并且不再接受新的消息,队列中剩下的消息会继续被looper取,一直到取完为止,然后结束队列退出循环,而普通退出就会把队列中所有消息移除,然后紧接着结束队列退出循环
quit和quitSafely 应用场景有哪些呢
;//把所有未来消息标记为回收状态
} while (n != null);
}
}
}
从上面可以看出,安全退出时,队列会把所有未来消息移除掉,并且不再接受新的消息,队列中剩下的消息会继续被looper取,一直到取完为止,然后结束队列退出循环,而普通退出就会把队列中所有消息移除,然后紧接着结束队列退出循环
### quit和quitSafely 应用场景有哪些呢
举一个简单的例子,在Android 使用Room操作数据库,由于操作数据库需要在子线程,所以,我们可以构造一个`HandlerThread`,专门处理操作数据库的任务,如果操作过程非常耗时,然后又要关闭数据库,