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
,一旦获取到新的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()
而阻塞,在监听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()
),它在等待新事件进行处理,所谓“不能阻塞主线程”实际是指不能阻塞主线程的事件处理。体会一下区别,很有意思。