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 目标的创立有关,查看 Looper.prepare()
代码:
果然,在 prepare()
办法中创立了 Looper 目标,并将其设置到 sThreadLocal 中。
该办法为单例模式,每个线程只能有一个 Looper 目标,否则会抛出反常。
并且在创立 Looper 的时分,创立了 MessageQueue 目标:
mQueue 是运用 final 关键字进行润饰的,赋值后不能改变。
一个线程对应着一个 Looper,一个 Looper 对应着一个 MessageQueue。Looper 是完成音讯循环的中心,使(主)线程能够不断地接收、分发和处理音讯。
所以必须在创立 Handler 目标前,确保该线程有一个 Looper。
1.2 主线程为什么不需要主动调用 Looper.prepare()
那为什么主线程中没有调用 Looper.prepare()
却没有报错呢?
这是因为在程序启动的时分,体系现已帮咱们主动调用了 Looper.prepare()
办法。查看 ActivityThread 中 main()
办法:
// ActivityThread。java
Looper.prepareMainLooper();
在 prepareMainLooper()
办法中,其实也是调用了 prepare()
办法。由此可见,每个线程都对应着一个 Looper。
1.3 Looper 目标为什么要存放到 sThreadLocal 中
这就要说到 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 的源码并不杂乱:
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
private @Nullable Handler mHandler;
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();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
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;
}
}
}
if (wasInterrupted) {
Thread.currentThread().interrupt();
}
return mLooper;
}
@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() 办法,线程天然不会被挂起。