上一章学习了消息机制中的 ThreadLocal, 本章接着来学习消息机制中的 Looper. 开篇也是先抛出几个问题.
先来一个典型的关于 Looper 的例子。
class LooperThread extends Thread{
public Handler mHandler;
public void run(){
//分析1
Looper.prepare();
mHandler = new Handler(){
public void HhandleMessage(Message msg){
Message msg = Message.obtain();
}
};
//分析 2
Looper.loop();
}
}
进入 Looper.prepare() 方法. 代码在 Looper.java
public static void prepare() {
prepare(true);
}
这也正对应了我们例子中写的, 要先调用 Looper.prepare() , 然后创建一个 Handler, 最后才调用 Looper.loop() 启动循环.
继续回到 public static void prepare(), 在方法内部又调用了一个 private static void prepare(boolean quitAllowed), 注意, 这两个方法名是相同的, 不过一个是 public 无参的, 一个是 private 有参的. 而我们调用的是 public 无参的, 在这个方法内部, Android 系统又调用了 private 有参的, 并且传入的参数为 true, 实际上这个参数一直向下传递到了 MessageQueue 的构造函数中, 表示允许退出消息队列. 也就是说我们在外部调用 Looper.prepare() 后, 系统为我们创建的消息队列是允许退出的。
进入到 private static void Looper.prepare(true)
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
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));
}
又看到了 ThreadLocal, 所以我们需要先学习了第二章, 再来看这个就会明白是什么意思了. 这个 sThreadLocal 中存储的是 Looper 对象。
调用 ThreadLocal.get() 看获取到的值是否为null, 如果为不为 null 说明当前线程已经创建过 Looper 对象了. 那么就会直接抛出异常. [每个线程只能执行一次 Looper], 那么这就是问题1 的答案,
如果获取到值为 null, 说明当前线程还未创建 Looper, 就会创建一个 Looper 对象, 并放到 ThreadLocal 中。
进入到 Looper(boolean quitAllowed)
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
进入到 prepareMainLooper()
public static void prepareMainLooper() {
//设置不允许退出的 Looper
prepare(false);
synchronized (Looper.class) {
//主线程有且只能调用一次 prepareMainLooper()
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
//赋值给 sMainlooper
sMainLooper = myLooper();
}
}
进入到 myLooper()
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
现在完整的分析 1, 已经分析完了, 主要就是为当前线程创建一个Looper 对象放入到当前线程的 ThreadLocal中, 并同时持有 MessageQueue 对象与当前线程对象. 其中创建的消息队列, 是可以退出的. 也知道了我们为什么可以在主线程直接使用 Handler, 就是因为在ActivityThread.main 方法中调用了 Looper.prepareMainLooper() 方法, 系统已经为主线程创建好了 Looper.
Looper 已经创建好了, 那么接下来就是开始循环了. 接下来开始看分析 2 Looper.loop().
进入 Looper.loop() 方法
public static void loop() {
//获取当前线程 ThreadLocal 存储的 Looper
final Looper me = myLooper();
if (me == null) {
//没有 Looper 直接抛出异常
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//获取当前 Looper 持有的消息队列
final MessageQueue queue = me.mQueue;
//确保权限检查基于本地进程, 而不是基于最初调用进程
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
//进入 loop 主循环方法
//一个死循环, 不听的处理消息队列中的消息,消息的获取是通过 MessageQueue 的 next 方法
for (;;) {
//可能会阻塞
Message msg = queue.next();
//注: 这里的 msg = null, 并不是说没消息了, 而是如果消息队列正在关闭的情况下, 会返回 null.
if (msg == null) {
return;
}
...
//用于分发消息,调用 Message 的 target 变量(也就是 Handler)的 dispatchMessage方法处理消息
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
...
...
//将 Message 回收标记后放入消息池.
msg.recycleUnchecked();
}
}
这里面有几个重要的方法, 后面文章会解释到.
5.Looper.quit 与 Looper.quitSafely 问题 3 的答案.
上面只是开启了循环方法, Looper 也提供了两个退出循环的方法, 分别是 quit 与 quitSafely
我们调用的 Looper 的两个退出循环的方法。
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
MessageQueue.quit(boolean safe)
void quit(boolean safe) {
//当 mQuitAllowed 为 false,表示不允许退出,强行调用 quit 会有异常
//mQuitAllowed 是在 Looper 构造函数里面构造 MessageQueue() 以参数传入的.
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
//同步代码块
synchronized (this) {
//防止多次执行退出操作
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {
//移除所有尚未触发的所有消息
removeAllFutureMessagesLocked();
} else {
//移除所有消息
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
quit: 会将消息队列中的所有消息移除 (延迟消息和非延迟消息)
Looper 的 quit() 方法内部的本质是调用 MessageQueue 的quit(boolean safe) 方法. 传入参数是 false.
quitSafely: 会将消息队列所有延迟消息移除, 非延迟消息则派发出去让 Handler 处理.
Looper 的 quitSafely() 方法内部调用的本质也是 MessageQueue 的quit(boolean safe) 方法. 只不过传入参数是 true.
quitSafely 相比于 quit 方法安全支出在于清空消息之前会派发出去所有的非延迟消息.