Android中的Thread和Handler
立泉其实,在很长一段时间里我并不了解Handler
究竟是如何工作的。接触RxJava
之前,切换线程只是简单的post(Runnable)
,但随着所做项目的不断迭代,我需要知道一些关键组件的执行原理,不然便无法为代码质量负责。
之前在学习和工作中我都会将笔记写在OneNote
上,这些文本容量已经累积到57MB,我觉得是时候停下来好好把它们分类整理一遍,来更精细地填充我的技术栈。所以这篇文章只是一个开始,如果我能把一个东西写清楚,那么可以说,我才真正理解了它。
Main Thread
Android
中的Thread
也即是Java
线程,当一个App启动时,系统会为它创建一个Linux Process
和一个Execution Thread
。默认情况下,此App的所有组件都会运行在这个进程的单一执行线程中,包括UI上产生的各种触控交互事件的分发,所以此线程又被称为UI Thread
和Main Thread
。
Call from wrong thread
Only the original thread that created a view hierarchy can touch its views
Android
的UI操作是线程不安全的,这意味着多线程操作UI时可能会出现状态不一致的情况,所以Android
强制要求必须在主线程中操作UI,否则就会抛出CalledFromWrongThreadException
异常。
override onCreate(savedInstanceState: Bundle?) {
...
// 主线程中操作UI
tvShow.text = "UI Thread"
// 当View Tree完成后执行
tvShow.post {
Thread {
// 其它线程中操作UI,抛出CalledFromWrongThreadException
tvShow.text = "Other Thread"
}.start()
}
}
ANR
ANR
即Application Not Responding
,应用程序无响应。Android
作为手持设备,是通过UI的触摸与用户交互的,这些触控事件一旦产生,都会在主线程中进行由Activity
到View
的层层分发,交给对应的处理代码并在处理完成后及时刷新UI,这样用户才会感到操作流畅不卡顿。
如果负责分发事件的主线程被阻塞(通常是在主线程中执行耗时操作),则用户点击屏幕后,这些触控事件迟迟不能向下传递,事件处理者就无法获取事件并给出反馈,用户看到的就是点击了屏幕但无任何反应,App就像卡住了一样。通常,如果阻塞时间大于5秒,Android
系统就会弹出ANR
,提醒用户强制关闭程序。
所以在Android
中使用线程必须遵循以下2条规则:
- 必须在主线程中操作UI
- 不能阻塞主线程
什么是Handler
已经知道,Android
需要在工作线程中执行耗时操作,然后切换到主线程刷新UI,这个切换线程的动作就可以使用Handler
实现。实际上,Handler
可以将任意线程中的Message
或Runnable
发送到任意指定的线程中处理。
// 创建处理Runnable的Handler
val handler = Handler()
override fun onCreate(savedInstanceState: Bundle?) {
...
Thread {
// 在工作线程中执行耗时操作
doSth()
// 切换到主线程中刷新UI
handler.post {
Toast.makeText(this, "Work done", Toast.LENGTH_SHORT).show()
}
// view的post的内部实现其实也是Handler
view.post {
Toast.makeText(this, "Post from view", Toast.LENGTH_SHORT).show()
}
}.start()
}
要理解Handler
是如何将Runnable
或Message
发送到另一个线程中的,需要注意Looper
、MessageQueue
和ThreadLocal
,从创建Handler
的那一刻说起:
// 创建Handler时,实际使用的构造器
public Handler(Callback callback, boolean async) {
// 获取Looper
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
// 获取Looper的MessageQueue
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
创建Handler
时,它会去获取一个Looper
,并且获取这个Looper
的MessageQueue
,如果获取不到,就会直接抛出异常。
那么它要获取的是什么Looper
呢,继续看:
// Looper.myLooper()方法实现
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
了解ThreadLocal
就知道,它获取的是此线程私有的Looper
,即这个Looper
是和此线程绑定的。
那么这个Looper
有什么作用呢,看一下Handler.post(Runnable)
的实现:
public final boolean post(Runnable r) {
// 将Runnable封装成Message,继续传递
return sendMessageDelayed(getPostMessage(r), 0);
}
最终调用的是这个:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
// 从Looper中获取的MessageQueue
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
// 将Runnable封装的Message,添加到一个Handler持有的MessageQueue中,即从Looper那里获取的Queue
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
// 将该Handler实例保存到Message中
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
可以看到,切换线程使用的Handler.post(Runnable)
实际上只是把Runnable
封装成Message
,并添加到一个该Handler
从Looper
那里获取的MessageQueue
中了,那么是谁来从Queue
中取出Message
并处理呢?
答案就是Looper
,来看2个Looper
的关键方法:
// Looper.prepare()
private static void prepare(boolean quitAllowed) {
// 检查该线程是否已经创建过Looper,一个线程只允许创建一个Looper
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// 创建该线程私有的Looper
sThreadLocal.set(new Looper(quitAllowed));
}
// Looper的构造器
private Looper(boolean quitAllowed) {
// 创建MessageQueue
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Looper.prepare()
会检查当前线程是否已经创建过Looper
,没有的话就创建一个新的Looper
并设置为该线程私有,留心的话,其实Handler
构造时从线程中获取的Looper
就是在这里创建的。Looper
创建时会同时在内部创建一个MessageQueue
,也就是Handler.post()
时用到的那个Queue
。
// Looper.loop(),这里只取关键逻辑,完整逻辑请看源码
public static void loop() {
// 确保已经执行过Looper.prepare()
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// 获取Looper的MessageQueue
final MessageQueue queue = me.mQueue;
// 进入一个阻塞线程的无限循环
for (;;) {
Message msg = queue.next(); // might block
try {
// 取出Message中保存的Handler实例,即发送该Message的Handler,调用它的相关方法去处理这个Message
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
}
}
Looper.loop()
会进入一个无限的等待循环,不断检查MessageQueue
中是否有新的Message
,一旦获取到新的Message
,就取出该Message
在post()
的时候保存的Handler
实例,调用该Handler
的方法来处理这个Message
,这就是整个Handler.post(Runnable)
的执行轨迹。
那么最关键的线程切换是怎么完成的呢?需要注意Looper.prepare()
执行时所在的线程,它就是Runnable
事件被处理时所在的线程,这个和线程启用Looper
时有关。
前面提到,Handler
创建时必须要求该线程有Looper
,即执行过Looper.prepare()
,一个典型的创建支持Looper
的线程是这样的:
class CusThread : Thread() {
lateinit var handler: Handler
override fun run() {
Looper.prepare()
handler = Handler()
// 阻塞线程,等待处理Handler分发的事件
Looper.loop()
}
}
实际上,这个线程在创建了Handler
之后,就因为执行Looper.loop()
而阻塞,等待执行Handler
分发的事件。注意到,Looper.loop()
是在该线程中执行的,结合之前的源码分析,通过Handler
发送的Runnable
都会在这个线程中执行,而Handler.post(Runnable)
这个行为可以在其它任意线程中进行,这就实现了在其它线程中发送事件到指定的线程中处理,即广义上的切换线程。
可以这样使用上面的CusThread
:
val thread = CusThread()
thread.start()
...
// 在其它线程中发送事件到CusThread线程中处理
Thread {
thread.handler.post {
// 这里的代码就会在CusThread中执行了
}
}.start()
// 在必要时及时退出Looper.loop()引起的线程阻塞,结束该执行线程
thread.handler.looper.quite()
使用HandlerThread
普通的线程必须手动创建Looper
才可以使用Handler
,Android
提供了一个默认创建好Looper
的线程实现HandlerThread
,原理和上面的CusThread
大同小异,只是多了一些细节控制,可以这样使用它:
val handlerThread = HandlerThread("")
handlerThread.start()
// 创建Handler时传入指定的Looper,这样它就不会去获取创建线程的Looper
val handler = Handler(handlerThread.looper)
...
// 在其它线程中发送事件到CusThread线程中处理
Thread {
handler.post {
// 这里的代码就会在HandlerThread中执行了
}
}.start()
// 在必要时及时退出Looper.loop()引起的线程阻塞,结束该执行线程
handler.looper.quite()
总结
Handler
机制简单的说,是这样:Handler
创建时需要一个Looper
,可以是指定的Looper
或默认使用当前线程的Looper
。Handler
获取此Looper
的MessageQueue
,当Handler.post(Runnable)
执行时,Handler
把此Runnable
封装成Message
并携带该Handler
实例,发送到从Looper
那里拿到的MessageQueue
中。Looper
在其创建线程中不断检查MessageQueue
是否有新的Message
,有的话就取出,调用附带的Handler
实例方法去处理,因为Handler
发送事件的线程和Looper
执行事件的线程一般都不一致,这样就实现了切换线程
。
Main Thread的特殊性
可能已经注意到,既然创建Handler
时要求该线程必须有Looper
,否则直接抛出异常,那么Android
的主线程为什么可以直接创建Handler
呢?
其实,可以创建就说明这个主线程默认已经有了Looper
,联想一下,Android
实际上是由事件驱动的,主线程也可以说一直都在“阻塞”(即Looper.loop()
),它在等待新的事件进行处理,所谓的“不能阻塞主线程”实际指的是不能阻塞主线程中的事件处理。体会一下区别,很有意思的。