Handler 源码解析系列文章:
- Handler 源码解析(一)—— Handler 的工作流程
- Handler 源码解析(二)—— 正确创建 Handler 对象
- Handler 源码解析(三)—— Handler 内存泄漏
- ...
1. 创建 Handler 对象的相关问题
1.1 为什么直接在子线程中创建 Handler 对象会抛出异常?
在子线程中直接创建 Handler 对象:
// 本文代码基于Android API 34
new Thread(new Runnable() {
@Override
public void run() {
handler2 = new Handler();
}
}).start();
运行上述程序,会崩溃:
该异常提示你不能在没有调用过 Looper.prepare()
的线程中创建 Handler 对象,如果运行下述代码,运行就不会报错:
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
handler2 = new Handler();
}
}).start();
我们可以在 Handler 的构造函数中找到抛出异常的地方:
// 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;
mIsShared = false;
}
// Looper.java
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
第5~9行,如果通过 sThreadLocal.get()
获取当前线程的 Looper 对象为 null,就会抛出异常。
从上一篇文章我们可以知道,Looper 在 Handler 机制中起到了非常关键的作用,每个线程必须有一个 Looper 对象去维护一个消息循环。
那为什么主线程中没有调用 Looper.prepare()
却没有报错呢?
这是由于在程序启动的时候,系统已经帮我们自动调用了 Looper.prepare()
方法。
主线程是在 AcitivityThread 的 main 方法中通过 Looper.prepareMainLooper() 创建的主线程 Looper 对象,实际上就是通过 Looper.prepare()
创建的单例。
1.2 Looper 对象为什么要存放到 sThreadLocal 中?
这就要说到 ThreadLocal 线程间隔离机制。
上文说过:ThreadLocal 对象用于存储线程私有数据。ThreadLocal 是一个线程本地存储机制,每个线程都有自己的局部变量副本,这些副本独立于其他线程,每个线程可以拥有自己的状态,而不会影响其他线程。这避免了多线程环境下的数据竞争和同步问题,提高了程序的性能和安全性。
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));
}
prepare()
方法中第5行,new 出一个 Looper 对象并调用了 ThreadLocal 的 set 方法:
// ThreadLocal.java
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
// ThreadLocal.java
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// Thread,java
ThreadLocal.ThreadLocalMap threadLocals = null;
通过 set 方法可以发现,Looper 对象实际上是放到了 ThreadLocalMap 中,而第4行的 ThreadLocalMap 对象,是通过当前线程获得的。当前 thread 持有 ThreadLocalMap 对象。
每个 Thread 的内部都会有一个 ThreadLocalMap 对象,用来存储 Looper。所以,在 sThreadLocal.set(new Looper(quitAllowed))
时,其实是将 Looper 对象存储到每个 Thread 内部的 ThreadLocalMap 对象中,从而保证每个线程只能访问自己的 Looper 对象,确保了线程之间的隔离性。
2. 在子线程中正确创建 Handler 对象
2.1 正确创建一个子线程 LooperThread,并开启循环
根据上述分析,核心问题就是,一个线程必须对应一个 Looper,我们可以如下在子线程中创建 Handler 对象:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler(Looper.myLooper()) {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
分别在第5行和第13行加入 Looper.prepare()
和 Looper.loop()
。
2.2 上述方法的局限性
但这种写法仍存在一个问题,如果我们想向这个线程中发送消息,只能用 mHandler 去发送消息。不能创建使用这个线程其他 Handler 对象去发送消息。
很容易想到,将子线程中的 Looper 暴露出去:
class LooperThread extends Thread {
Looper looper;
public LooperThread(@NonNull String name) {
super(name);
}
@Override
public void run() {
super.run();
Looper.prepare();
looper = Looper.myLooper();
Looper.loop();
}
@Override
public synchronized void start() {
super.start();
}
public Looper getLooper(){
return looper;
}
}
然后就可以取到子线程 Looper 对象,并创建 Handler 对象。
LooperThread looperThread = new LooperThread("thread");
looperThread.start();
Handler handler1 = new Handler(looperThread.getLooper());
Handler handler2 = new Handler(looperThread.getLooper());
2.3 并发同步问题
这样就没有问题了吗?
其实还存在并发同步问题。
LooperThread looperThread = new LooperThread("thread");
looperThread.start();
Handler handler1 = new Handler(looperThread.getLooper());
Handler handler2 = new Handler(looperThread.getLooper());
由于 looperThread 是子线程,而第3、4行运行在主线程中,在主线程中运行不一定保证可以拿到子线程中运行的结果,所以 looperThread.getLooper()
取到的Looper 对象很有可能为空。
也就是说,第3、4行的运行,必须等待子线程运行的结果。所以存在着并发同步问题。
怎么解决这个问题,在 looperThread.start()
将主线程 sleep,等待子线程运行完成?这样会大大降低系统性能,不是并发问题的正确解决方式。
3. HandlerThread
其实在 Android 已经提供了这样的一个线程类 HandlerThread,可以完美解决此问题。
通过 HandlerThread 可以创建一个带有消息循环的线程,从而实现异步任务处理。
HandlerThread 的源码并不复杂:
public class HandlerThread extends Thread {
int mPriority;// 线程优先级
int mTid = -1;// 当前线程id
Looper mLooper;// 线程关联的 Looper 对象
private @Nullable Handler mHandler;// 线程绑定的 Handler
public HandlerThread(String name) {// 线程名称
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;// 默认优先级
}
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
protected void onLooperPrepared() {
}
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();// 初始化 Looper 和 MessageQueue
synchronized (this) {
mLooper = Looper.myLooper();// 将 Looper 对象引用赋值给 mLooper,以便外部获取
notifyAll();// 唤醒可能等待getLooper()的线程
}
Process.setThreadPriority(mPriority);
onLooperPrepared();// 触发Looper准备完成回调
Looper.loop();// 启动消息循环
mTid = -1;// 循环结束后重置线程ID
}
// 获取当前线程的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) {// 如果子线程创建 Looper 对象之前,就调用了 getLooper,就循环等待,直至 Looper 初始化完成
try {
wait();// 释放锁,等待run()中的notifyAll()唤醒 // 无限等待,调用 notifyAll 唤醒,退出循环
} catch (InterruptedException e) {
wasInterrupted = true;// 记录中断状态
}
}
}
if (wasInterrupted) {
Thread.currentThread().interrupt();// 恢复中断标志
}
return mLooper;// 获取到 Looper 对象
}
@NonNull
public Handler getThreadHandler() {
if (mHandler == null) {
mHandler = new Handler(getLooper());
}
return mHandler;
}
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}
public int getThreadId() {
return mTid;
}
}
HandlerThread 继承了 Thread,并封装了 Handler。
public void run() {
...
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
...
}
public Looper getLooper() {
...
// 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;
}
}
}
...
return mLooper;
}
在 run 方法和 getLooper 方法中都使用了 synchronized (this)
进行上锁。同一个对象调用这两个方法为互斥访问。
HandlerThread handlerThread = new HandlerThread("thread");
handlerThread.start();
Handler handler1 = new Handler(handlerThread.getLooper());
Handler handler2 = new Handler(handlerThread.getLooper());
所以每当使用 handlerThread 调用 getLooper()
方法时,一定能拿到不为空的 Looper 对象。
如果 getLooper()
方法先拿到了锁,这时还没有运行 Looper.prepare(),则会执行第17行 wait()
方法,进入阻塞状态,并释放锁。稍后 run 方法会拿到了锁,并且给 mLoopeer 进行了赋值,然后调用 notifyAll()
方法进行唤醒了,这样就可以拿到 mLooper 了。
执行 notifyAll() 后,会等到 synchronized 代码块中所有代码都执行完毕才会去执行其它代码。所以 notifyAll() 在 synchronized 代码块中的位置无关紧要,他也可以放到 mLooper = Looper.myLooper() 前执行。
HandlerThread 对象被创建出来之后,不执行 start 方法直接去 getLooper ,线程会一直被挂起吗?
getLooper() 会通过 isAlive() 去判断线程是否在运行中,如果线程还未 start,会直接返回 null,会报空指针异常。不会调用到 wait() 方法,线程自然不会被挂起。