在多线程通信中,Handler接收消息端的线程是独立的,无论Handler的引用在哪个线程发送消息,Handler在哪里实例化就在哪里接收消息。Handler拥有独立存在于线程内部且私有使用的Looper类来实现发送消息。
总体来讲,当Handler对象在其他线程发送消息时,可通过Handler的引用对象将消息添加到MessageQueue 并找到其线程的Looper处理接收消息,Looper接收消息后再分发给Handler的接收消息方法。
接下来看看Handler家族的四大成员:
一、Handler----负责handler 消息发送,提供发送和接收消息的接口。
二、Looper----实现消息接收端存在且独立于线程内部,负责从消息队列中取消息分发给Handler接收消息端。
Looper采用Java 提供的 ThreadLocal 来实现线程独立。ThreadLocal 支持泛型,在实例化对象时需重写initialValue 用于引用变量的初始化,默认为 null。主要方法:
1.1、T get() 返回此线程局部变量的当前线程副本中的值(引用变量)。若变量没有当前线程的值,则先将其初始化为initialvalue()方法调用返回的值。
1.2 void remove() 删除本地线程的引用变量
1.3 set(T value)为本地线程引用变量赋值。也可通过 initialValue 来实现初始化赋值
Looper特点:
1、职责单一,负责从消息队列中取消息,并分发给对应handler
2、线程独立,且每个线程只能存在一个Looper
3、会根据存活情况,创建和退出属于他自己的MessageQueue
创建和退出 Looper:
源码分析:
1、UI线程 Looper 实例
private static Looper sMainLooper;
2、Worker 线程 Looper 实例,用 ThreadLocal 保存的对象都是线程独立的
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
3、与当前Looper对应的消息队列,被 final 关键字修饰的,只能被赋值一次
final MessageQueue mQueue;
4、当前Looper所在的线程,被 final 关键字修饰的,只能被赋值一次
final Thread mThread;
5、对外初始化方法:在普通线程中初始化 Looper 调用此方法。可调用该方法为自己的 worker 线程创建 Looper,但需注意只能初始化一次。
public static void prepare(){ prepare(true); }
6、对外初始化方法:UI 线程中初始化 Looper 调用此方法。prepareMainLooper在应用被创建就会调用。
public static void prepareMainLooper() {
// 因为是主线程,初始化一个不允许退出的 Looper
prepare(false);
synchronized (Looper.class) {
// 如果 sMainLooper 不等于空说明已经创建过主线程 Looper 了,无需重复创建
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has
already been prepared.");
}
sMainLooper = myLooper();
}
}
7、私有初始化构造方法:@param quitAllowed 是否允许退出 Looper
private static void prepare(boolean quitAllowed) {
// 每个线程只能有一个 Looper
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per
thread");
}
// 保存实例
sThreadLocal.set(new Looper(quitAllowed));
}
8、私有构造方法:@param quitAllowed 是否允许退出 Looper。在构造方法中创建了对应的 MessageQueue 实例,然后得到当前线程
private Looper(boolean quitAllowed) {
// 初始化 MessageQueue
mQueue = new MessageQueue(quitAllowed);
// 得到当前线程实例
mThread = Thread.currentThread();
}
9、调用 Looper.prepare()方法初始化完成后,可调用 myLooper()和 myQueue()方法得到当前线程对应的实例。
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
public static @NonNull MessageQueue myQueue() {
return myLooper().mQueue;
}
10、退出Looper:安全和不安全
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
10、运行Loopr处理消息
当 Looper.prepare()方法初始化完成,再调用Looper.loop()方法就可运行Looper
public static void loop() { // 得到当前线程下的 Looper final Looper me = myLooper(); // 如果还没初始化过抛异常 if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } // 得到当前线程下与 Looper 对应的消息队列 final MessageQueue queue = me.mQueue; // 得到当前线程的唯一标识(uid+pid),作用是下面每次循环都判断一下线程有没有被切换 // 第一次调用 Binder.clearCallingIdentity()为了保存当前的调用者身份标识,将其保存到变量ident中;第二次调用 Binder.clearCallingIdentity()在操作完成后,恢复之前保存的调用者身份标识,以确保后续的操作可以继续在正确的身份标识下执行。 Binder.clearCallingIdentity();//用于清除调用者的身份标识(identity) final long ident = Binder.clearCallingIdentity(); // 进入死循环不断取出消息 for (;;) { // 从队列中取出一个消息,这可能会阻塞线程 Message msg = queue.next(); // 如果消息是空的,说明队列已经退出了,直接结束循环,结束方法 if (msg == null) { return; } // 打印日志 final Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + " : " + msg.what); } // 性能分析相关的东西 final long traceTag = me.mTraceTag; if (traceTag != 0 && Trace.isTagEnabled(traceTag)) { Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); } try { //尝试将消息分发给宿主(Handler),dispatchMessage 为宿主 Handler 的接收消息方法 msg.target.dispatchMessage(msg); } finally { // 性能分析相关的东西 if (traceTag != 0) { Trace.traceEnd(traceTag); } } //打印日志 if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } //得到当前线程的唯一标识 final long newIdent = Binder.clearCallingIdentity(); //如果本次循环所在的线程与最开始不一样,打印日志记录 if (ident != newIdent) { Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to "+ msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what); } //消息分发完毕,回收消息到缓存池 msg.recycleUnchecked(); } }
总之,Looper 核心是调用 Looper.loop()不断的从消息队列中取出消息并分发给对应的 Handler。