这是我参与8月更文挑战的第11天,活动详情查看:8月更文挑战
现在有了RxJava,Kotin,EnentBus等,开发者对于使用Handler的机会越来越来少。那我们是不是可以就不去了解Handler机制了?不行。为什么?因为面试会问!
言归正传,我刚入行的时候进公司看到的项目,都是Okhttp再加Handler的,虽然现在开发者很少直接使用Handler,但是据我所知,Rxjava,Kotlin的协程他们内部切换主线程内部都和Handler机制有关,我们用到的view.post,runOnUiThread内部都是Handler机制,还有我们分析Activity启动流程的时候发现Handler起了至关重要的作用。所以了解Handler机制很有必要。
概述
Handler有一下一句注释
用人话说就是1.发消息2.切线程
我们知道说道Android消息机制是通过Handler,Looper,MessageQueue,Meassge这四个主要类完成的。
在Looper上面有官方对于Handler的使用样例
可以看到Looper的官方注释也给了使用样例:
- Looper.prepare();
- 创建Handler
- Looper.loop();
当然还有2种使用方式:
- post(Runnable)
- senMessage()
Looper
在看Looper的prepare和loop这两个方法之前,先看一下他的构造方法。
# android.os.Looper
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class
final MessageQueue mQueue;
final Thread mThread;
private boolean mInLoop;
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Looper有几个比较关键的成员变量在代码中贴了上来,可以先留意一下。 构成方法接受一个参数,看意思是:是否允许退出,然后根据这个参数创建了一个MessageQueue给变量 mQueue,并且获取当前的线程,赋值给了mThread。
Looper内部有一个消息队列MessageQueue,还有一个当前运行在哪个线程的变量
- MessageQueue是一个消息队列,内部存储着Measgae,根据quitAllowed的值,MessageQueue会有两种类型的,可退出和不可退出,后面会细讲。
Looper.prepare
# android.os.Looper
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
hrow new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
Looper内部用ThreadLocal来存储Looper。也就是说,线程内可以拥有自己独有的Looper对象。也就是说,prepare内部通过ThreadLocal机制,巧妙的把线程和Looper对象进行了冠联。
不熟悉的ThreadLocal的小伙伴可以先自行百度一下,后期我也会写一篇关于ThreadLocal的文章
这边会先从sThreadLocal里做一个取的操作,如果有值则会抛异常Only one Looper may be created per thread。
也就是说
线程的Looper.prepare只能调用一次,多次调用会报错。
Looper.loop
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
public static void loop() {
final Looper me = myLooper();
......
final MessageQueue queue = me.mQueue;
......
for (;;) {
Message msg = queue.next(); // might block
注意一下上面的注意might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
......
try {
msg.target.dispatchMessage(msg);
......
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
......
}
......
}
}
可以看到myLooper()方法是对sThreadLocal的一个取的操作。
先是获取当前线程的Looper对象,然后取出消息队列MessageQueue。然后通过开启一个死循环,循环内不断的从MessageQueue内取出消息,如果消息为null则退出循环。如果消息不为null,则调用消息内部的taget的dispatchMessage方法。 到这里出现几个问题:
- queue.next()什么时候为null?
- msg.target.dispatchMessage(msg)又做了什么?
- MessageQueue是什么
- Message是什么
这些问题后面会一一得到解答,暂时先看官方样例的第二步,创建Handler
Handler
Handler构造方法
# android.os.Handler
final Looper mLooper;
final MessageQueue mQueue;
......
@Deprecated
public Handler() {
this(null, false);
}
......
public Handler(@Nullable Callback callback, boolean async) {
1
mLooper = Looper.myLooper();
2
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
3
mQueue = mLooper.mQueue;
......
}
- 通过Looper.myLooper()给当前Handler的mLooper赋值
- 如果Looper.myLooper()返回null则会抛出我们很熟悉的异常:不能在没有执行Looper.prepare()的线程里创建Handler。
- 给取出mLooper下的mQueue赋值给当前Handler的mQueue
Looper.myLooper()为null的情况肯定是没有往sThreadLocal中添加值,也就是没有执行Looper.prepare()方法。
在创建Handler之前,该线程必须先执行Looper.prepare()方法.
post(Runnable)
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;
}
post方法内部会把runnable类型的参数通过getPostMessage方法包装成Message类型,然后在调研, 我们知道post还可以延迟操作。也就是
public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
其实最后都是调用sendMessageDelayed方法
sendMessage
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
我们发现不管是post,postDelayed,sendMessage最后都是执行发送延迟消息的方sendMessageDelayed,只不过第二个参数是0。
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
sendMessageDelayed方法接受一个Message参数和一个表示时间的参数,为0则表示立刻执行,有值则在当前系统时间加上需要延迟的时候,传给sendMessageAtTime。
Android内能见到的延迟操作,绝大部分都是通过这种方式实现的,随意没啥神秘的。
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
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(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
注意!注意!注意!
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
sendMessageAtTime只是将当前线程下的Handler持有的消息列表传给了enqueueMessage方法。 enqueueMessage方法第一行就将当前Handler赋值给了Message的target变量。
也就是说每一个Message内部都会通过target持有发送这个消息的Handler引用。在回想起之前Looper从消息队列里取出Message之后会调用target.dispatchMessage(msg)方法。其实这就是触发消息的处理了。也是最重要的一行代码只是现在还不看不出来。对于Message持有Handler引用也好理解,就好像我们微信发出去的消息,每个消息都有对应这发送者是谁。
代码接下来应该来到queue.enqueueMessage,也就是去看看MessageQueue。但是看它之前,先简单看看Message是怎样的组成,毕竟MessageQueue存的就是这个东西。
Message
# android.os.Message
public final class Message implements Parcelable {
public int what;
public Object obj;
Runnable callback;
public long when;
Handler target;
}
Message可以看成我们平常写的一个数据接口,可以进行系列化。当然他还有其他的属性,我们先看看比较重要和熟悉的几个。
- what : 我们在Handler中处理消息的区分依据
- obj : 我们在消息中传递的数据,根据what区分后进行强转
- callback : 需要处理的回调,上文看到我们通过post发送的callback就是被赋值在这
- when : 消息的处理时间
- target : 发生这个Message的Handler对象,最后通过他来完成消息的消费
MessageQueue
简单的了解了Message之后,接着看MessageQueue的enqueueMessage方法
# android.os.MessageQueue
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
......
synchronized (this) {
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
对队列进行排序
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
方法比较长,主要做了两件事:
- 校验
- 如果Message的target为null,则抛异常,因为这是个幽灵消息,没有发送者也就没有执行者
- msg.isInUse()表示消息正在使用,抛异常。可以创一个Message,连续两次调用send,则会触发
- 如果MessageQueue正在退出,则抛异常
- 对队列进行排序。把传进来的诗句赋值给Message的when,也就是执行时间。然后对正规队列根据when,做一个排序。
MessageQueue是一个以时间为条件的,有序列表
简单梳理流程已知流程:
- 在线程中先执行Looper.prepare()方法,目的是为了让当前线程持有一个Looper对象。
- 创建Hander对象
- 执行Looper.loop方法。目的是为了开启消息的循环,取出消息后,执行msg.target.dispatchMessage(msg)方法,也就是触发了发送消息的Handler执行dispatchMessage方法。
那现在就是看Handler的dispatchMessage做了什么了。
其实从另一个角度,我们发现最后还是需要分析dispatchMessage。
在创建Handler的时候,我们需要重写handleMessage来坐接收到消息的处理。
可以看到Handler的handleMessage是个空实现,上面的注释也是说。子类必须需要重写该方法完成对message的接收。 我们会发现,触发Handler的handleMessage方法的地方就是dispatchMessage。
dispatchMessage
图中红框1处,如果message的callback不为null,则执行handleCallback。这种情况一般都是通过post的方式
private static void handleCallback(Message message) {
message.callback.run();
}
其实就是执行 Runnable 的 run 方法,实际上就是执行一个接口回调,与线程无关。
图中红框3处,是当mCallback不为null的时候触发。mCallback不为null只有当我们构造Hanlder的時候传Cllback。
public Handler(@Nullable Callback callback) {
this(callback, false);
}
这种使用情况非常少,可以选择忽略。
图中红框3处,是我们把Handler当成消息机制时,就会执行我们重写的handleMessage。
小结
- Handler 负责的是消息的发送和事件的处理
- 不管是 post(Runnable)还是sendMesage,最后触发的都是sendMessageDelayed,只不过Handler会将post的Runnable赋值给Message的callback,还是包装成了一个Message
- Message内部的target就是发送这个消息的Handler,所以才能在创建Hnadler的线程处理事件。这点特别重要
很早以前我有一种误解,安卓的消息机制是由 Message,MessageQueue,Looper,Handler互相调用一起组成的,以为Looper是从MessageQueue取消息交给Handler来处理的。后面自己看了源码才发现我错了。
- Message,MessageQueue只是两个数据结构。Looper的作用是不停的从MessageQueue里取出消息,但是并非是交给了Handler,而是触发了Message的target执行dispatchMessage。
- handler对于有Runnable消息处理相当于执行一个回调
最后再重复唠叨一句,Handler之所以能在别的线程发消息,却还能在创建Handler的线程执行的原因。就是因为发送的Message里携带了发送他的Handler引用,所以不管这个消息最后去了哪里。执行消息target.dispatchMessage的时候,执行的就是他所属Handler的dispatchMessage,然后再执行到Handler的handleMessage方法。我们又对handleMessage进行了重写,所以最后回到了创建Handler的线程里。
本文对Android消息机制做了个大概的分析,关于Handler还留下了很多面试会问到的疑问。将在下一篇对面试点进行点对点解析。