Android 中的 Thread 和 Handler
立泉其实很长一段时间我并不知道Handler是如何工作的,接触 RxJava 之前切换线程只是handler.post(() -> {}),但随着项目更迭我需要知道关键组件的执行原理,不然无法为代码质量负责。
习惯在 OneNote 中记录学习和工作笔记,这些文本容量已经累积到 57MB,是时候停下来整理一遍更精细的填充技术栈。这篇文章是一个开始,能描述清楚一个组件才意味着真正理解它。
Main Thread
Android 的 Thread 即是 Java 线程,系统会为启动的 App 创建一个 Linux Process 和一个 Execution Thread,默认此 App 的所有组件都会运行在这个进程的单一执行线程中,包括 UI 触控事件的分发处理,所以此线程又被称为 UI Thread 或 Main Thread。
Called 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"
// 抛出的 Runnable 会在 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实现,而且它其实能将任意线程中的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和它的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,没有的话就创建一个新并设置为该线程私有,留心的话,其实Handler构造时从线程获取的Looper就是在这里创建的。Looper构造时会在内部创建一个MessageQueue,即是Handler.post(Runnable)用到的那个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,一旦获得就取出其在post()时保存的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()而阻塞,处于监听MessageQueue等待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()),它在等待新事件,所谓“不能阻塞主线程”实际是指不能阻塞主线程的事件处理。体会一下区别,很有意思。