Android源码解析--深入Handler机制
目录
对于一个Android程序员来说,Handler可谓是被谈及最多的一个词也不为过。从Android开发刚兴起的时候它就是面试时不可避讳的话题,发展到现在,它的重要地位也未能被替代,很多开源框架的实现原理也都离不开它,虽然现在面试被问的比较少了,这可能是因为它被认为是Android的一个最基础的知识点了,如果连它都没搞懂,您好意思说是一个Android老手吗?不过话说回来,不少老程序员对于它虽然很熟悉,但是真要问起细节还说不死,这篇文章我们就一起回顾一下这个经典知识。废话不多说,作为一个老程序员,基本的东西还是知道的,就不用说怎么使用那些废话了,直接进入正题。
Handler机制主要由Handler、Looper、MessageQueue、Message四个类来实现的,它们各司其职,首先看看Looper类:
1. Looper
Android中最常用的使用方式是在主线程中创建一个Handler对象,子线程中耗时获取数据后发送消息给Handler,Handler收到消息后在主线程刷新页面。但是如果我们要在子线程中创建Handler则需要多两个步骤,否则会报异常:
new Thread(() -> {
//1:Looper.prepare()
Looper.prepare();
subHandler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 1:
Log.w("handler", "子线程创建Handler收到消息:"+msg.obj);
break;
}
}
};
//2:Looper.loop()
Looper.loop();
}).start();
为什么子线程中的Handler需要这两步而主线程中创建Handler却不需要?Looper是个什么东西?prepare()
、loop()
方法做了什么事情?
1.1 prepare()创建Looper
#### android.os.Looper
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
//将当前线程初始化为循环程序。请确保在调用此方法后调用loop(),并在循环结束时调用quit()
public static void prepare() {
prepare(true);
}
//quitAllowed:True 消息队列可以退出(子线程创建的Handler可调用Looper.quit()退出消息队列轮询)
// false 消息队列不能退出(主线程中创建的Handler不可以退出轮询,请看下面的prepareMainLooper()方法,传的就是false)
private static void prepare(boolean quitAllowed) {
//从sThreadLocal中获取一个Looper对象,如果获取到则抛出一个异常,说明一个线程只能调用一次Looper.prepare()
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//★ 如果是第一次调用prepare(),为当前线程创建一个Looper对象(请看下面Looper的构造方法),放入sThreadLocal中
sThreadLocal.set(new Looper(quitAllowed));
}
//应用程序的主循环器是由Android环境创建的,所以您不必自己调用这个函数。这个方法在下面将的ActivityThread的main方法中被调用
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
final MessageQueue mQueue;
final Thread mThread;
private Looper(boolean quitAllowed) {
//★ Looper构造方法中初始化了MessageQueue消息队列对象
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
从源码可以看出,prepare()
方法其实就是为当前线程初始化了一个Looper
对象(循环器)并放在sThreadLocal
中保存,而Looper
的构造方法里初始化了类型为MessageQueue
(消息队列)的mQueue
变量。sThreadLocal
是ThreadLocal
类的变量,它的作用是将Looper
对象与线程关联起来,并保证当前线程有且仅有一个Looper
对象,至于为什么要用ThreadLocal
放到最后讲一下。在Looper中还有一个prepareMainLooper()
方法,这个方法是系统创建应用程序是由系统调用的,其作用是为主线程创建循环器。
1.2 loop()轮询
public static void loop() {
//获取当前线程的Looper对象=>sThreadLocal.get()
final Looper me = myLooper();
//如果没有获取到,抛异常,提示需要先调用Looper.prepare()
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//获取当前线程Looper对象中的MessageQueue消息队列
final MessageQueue queue = me.mQueue;
//...
//★ for死循环轮询取出消息
for (;;) {
//★ 从队列中取出消息,next()是一个阻塞方法,当队列中没有需要马上处理的消息会阻塞线程,直到获取到一条消息
Message msg = queue.next(); // might block
if (msg == null) {
//如果没有next()的阻塞,队列中消息为空时,轮询就停止了
return;
}
//...
//★ Message的target就是当前消息保存的Handler对象的引用,通过handler.dispatchMessage(msg)将消息分发给Handler
try {
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
//...
} finally {
//...
}
//...
//★ 回收可能正在使用的消息,供Message.obtain()复用
msg.recycleUnchecked();
}
}
Looper的prepare()方法为当前线程创建一个唯一的Looper对象,Looper对象中维护了一个消息队列,loop()方法则开启一个for循环不断轮询取出队列中的消息,然后调用handler的dispatchMessage(msg)方法将消息发送给目标Handler处理。需要注意的是轮询取消息的动作所在的线程是调用loop()方法的线程,如果实在主线程中创建Handler则轮询也是在主线程,如果在子线程中创建的Handler,轮询动作在子线程,子线程的Handler收到消息后如果要刷新UI,需要使用runOnUiThread(new Runnable(){})
切换到主线程。总之记住一条,在哪个线程创建Handler,则在那个线程收到消息。
1.3 主线程的Looper
为什么子线程中的Handler需要这两步而主线程中创建Handler却不需要?这是因为应用程序在启动时(ActivityThread
的main方法)已经为主线程创建了Looper循环器,并开启了消息轮询:
#### android.app.ActivityThread 隐藏API @hide
public static void main(String[] args) {
//...
//★ 为主线程创建一个消息队列不能退出的Looper
Looper.prepareMainLooper();
//...
ActivityThread thread = new ActivityThread();
//
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
//...
//★ 开启主循环器的消息轮询
Looper.loop();
}
1.4 主线程looper死循环为什么不会导致ANR
ActivityThread的main方法中为主线程创建了Looper,并调用loop()开启了死循环轮询消息,为什么主线程出现死循环没有导致ANR?Looper在没有消息需要处理时是休眠状态(后面讲解消息队列时会涉及到怎样休眠的),这时候主线程是可以响应用户交互的。Android是由事件驱动的,我们在屏幕上所有的点击触摸交互都是一个事件,这些事件都会被放入主线程的消息队列中,等待Looper轮询分发事件,这样才有了组件的声明周期、事件处理等回调,其实都是对loop到的消息的处理。也正是因为Looper的死循环,才没有导致主线程退出(线程run方法执行完就退出了)。什么情况下会导致ANR?我们在处理消息事件时(生命周期方法、handleMessage())出现耗时操作,系统发现looper长时间不能正常轮询也不能阻塞休眠时就会发生ANR。
接下来我们看看怎样往消息队列中插入消息以及Handler是怎样收到消息的。
2. Handler
Handler handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
//处理消息
switch (msg.what){
case 1:
Log.w("handler", "主线程创建Handler收到消息:"+msg.obj);
break;
}
}
};
//★ 获取message实例的方式有很多,应该使用哪一种请看下面Message部分的讲解
//new Message对象(但是获取消息的首选方法是调用Message.obtain())
Message msg1 = new Message();
//从全局池返回新的消息实例,避免在许多情况下分配新对象。
Message msg2 = Message.obtain();
//obtainMessage()方法其实也是调用Message.obtain()
Message msg3 = handler.obtainMessage();
//对msg进行设置
msg3.what = 1;
msg3.obj = "这是一条来自Handler的消息";
//发送消息
handler.sendMessage(msg3);
//发送runnable
handler.post(new Runnable() {
@Override
public void run() {
}
});
上面的代码首先获取到一个Message对象,对其进行数据设置,然后handler.sendMessage(msg3)
将消息发送出去,最后在Handler的handleMessage()
中处理消息。这个过程最重要的两步就是发送消息以及消息是怎么被传到handleMessage()
中的。
2.1 发送消息
#### android.os.Handler
//1. Handler构造方法
public Handler(Callback callback, boolean async) {
//拿到调用new Handler()所属线程的Looper轮询器
mLooper = Looper.myLooper();
//如果没有获取到轮询器抛异常,提示需要调用Looper.prepare()初始化轮询器
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
//持有轮询器的消息队列,方便Handler王其中插入和分发消息
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis){
if (delayMillis < 0) {
delayMillis = 0;
}
//消息被处理的时间 = 当前时间+延迟的时间,SystemClock.uptimeMillis()是自开机启动到目前的毫秒数
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
//2. sendMessage(msg)、sendEmptyMessage(what)、sendMessageDelayed(msg, delayMillis)等常用发消息的方法最终都是调用sendMessageAtTime()
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
//拿到Handler中的mQueue,其实就是Looper中维护的消息队列
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
//将消息排队到消息队列中
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//msg的target指向当前Handler对象,方便在轮询器中取出消息后分发给当前handler
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
//★ 调用MessageQueue的enqueueMessage()方法将消息放到消息队列中,该方法跟踪请看MessageQueue讲解
return queue.enqueueMessage(msg, uptimeMillis);
}
//3. 使用post()方法发送的Runnable对象将被包裹成一个Message消息,并将Runnable作为Message的callback回调
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
Handler发送消息最终都是调用到queue.enqueueMessage(msg, uptimeMillis)
,也就是调用MessageQueue的方法将消息插入到队列中,详情请看MessageQueue部分讲解
2.2 分发消息
上面讲Looper.loop()
方法时我们知道轮询器开启了一个for循环调用queue.next()
从消息队列中取出消息,然后调用msg.target.dispatchMessage(msg)
分发消息,下面我们就看看Handler的dispatchMessage(msg)
方法是怎样将消息分发的:
#### android.os.Handler
//承接Looper.loop()方法中调用的dispatchMessage(msg)
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
//post(runnable)发送的消息将调用handleCallback()
handleCallback(msg);
} else {
if (mCallback != null) {
/*
* 创建handler时传递了Callback对象的情况,将调用mCallback.handleMessage(msg)
* 这跟重写Handler的handleMessage(msg)方法差不多,但是它的返回值如果是false表示消息没处理,将继续由handleMessage(msg)处理
* new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
return false;
}
}
*/
if (mCallback.handleMessage(msg)) {
return;
}
}
//调用handleMessage(msg),也就是我们重写的那个方法
handleMessage(msg);
}
}
//post(runnable)发送的消息将执行Runnable.run()
private static void handleCallback(Message message) {
message.callback.run();
}
消息分发就是将loop()取出的消息传递给我们处理消息的方法,需要注意的是dispatchMessage()
方法中并没有做线程切换,也就是说Handler在哪个线程创建的(Looper就属于这个线程,消息轮询也在该线程)则消息最终会被发送到这个线程中,如果是在子线程创建的Handler,收到消息后需要更新UI,需要使用runOnUiThread(new Runnable(){})
切换到主线程。
到此消息的发送、分发、轮询都讲完了,接下来还有一个重要的,那就是消息队列,消息在队列中是怎么排列的?消息是怎样插到队列中的?轮询从队列中取出消息又是怎样一个过程?请看MessageQueue
3. MessageQueue
★ 3.1 数据结构
MessageQueue是一种单向链表数据结构,Message中有一个next属性指向队列中下一条消息,队列排序是根据Message的when字段(消息延迟时间)来排序的,调用handler.sendMessage(msg)
发送的消息,when==0
表示需要立刻处理的消息,调用handler.sendMessageDelayed(msg, 1000)
发送的属于延迟消息,when==距开机时间毫秒数+延迟时间。总之when最小值为0,when越小表示消息延迟时间越小越被早处理,在队列中越靠前,下图模拟一下消息队列:
越靠近队头的消息越早被处理 (x表示开机时间,后面+值表示延迟时间,when值越大越靠后)
-------------------------------------------------------
when 0 x+1000 x+1500 x+2000 x+3000 x+5000
MessageQueue.next()