4.3Handler深度解析
目录介绍
- 01.Handler构造方法
- 02.sendMessage(msg)发消息
- 03.postDelayed延迟消息
- 04.handleMessage处理消息
- 05.createAsync如何理解
- 06.mainIfNull如何理解
- 07.如何移除消息
- 08.handler可以是多个吗
- 09.发送消息的Delay可靠吗
01.Handler构造方法
首先看看构造方法,如果构造函数没有传入 Looper
参数,则会默认使用当前线程关联的 Looper
对象,mQueue
需要依赖于从 Looper
对象中获取。
如果 Looper
对象为 null ,则会直接抛出异常,且从异常信息 Can't create handler inside thread that has not called Looper.prepare()
中可以看到,在向 Handler
发送消息前,需要先调用 Looper.prepare()
public Handler(Callback callback, boolean async) {
···
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
02.handler.sendMessage(msg)
看 handler.sendMessage(msg)
方法
关于下面得源码,是步步追踪,看enqueueMessage这个方法,原来msg.target就是Handler对象本身;
而这里的queue对象就是我们的Handler内部维护的Looper对象关联的MessageQueue对象。博客
handler.sendMessage(message);
//追踪到这一步
public final boolean sendMessage(Message msg){
return sendMessageDelayed(msg, 0);
}
//最终调用到 sendMessageAtTime 方法
可以看到 sendMessageAtTime()
方法中需要一个已初始化的MessageQueue
类型的全局变量mQueue
,否则程序无法继续走下去。
而 mQueue
变量是在构造函数中进行初始化的,且 mQueue
是成员常量,这说明 Handler
与 MessageQueue
是一一对应的关系,不可更改
public boolean sendMessageAtTime(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);
}
然后接着往下看enqueueMessage
方法
当中,需要注意 msg.target = this
这句代码,target 对象指向了发送消息的主体,即 Handler 对象本身。
即由 Handler 对象发给 MessageQueue 的消息最后还是要交由 Handler 对象本身来处理。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//target 对象指向的也是发送消息的主体,即 Handler 对象
//即由 Handler 对象发给 MessageQueue 的消息最后还是要交由 Handler 对象本身来处理
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
看MessageQueue对象的enqueueMessage方法
看到这里MessageQueue并没有使用列表将所有的Message保存起来,而是使用Message.next保存下一个Message,从而按照时间将所有的Message排序。
因为存在多个线程往同一个 Loop 线程的 MessageQueue 中插入消息的可能,所以 enqueueMessage()
内部需要进行同步。
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
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;
}
03.postDelayed延迟消息
使用 postDelayed 后消息队列会发生什么变化?这个消息是立即插入队列,还是延迟插入队列?
postDelayed的Message并不是先等待一定时间再放入到MessageQueue中,而是直接进入并阻塞当前线程,然后将其delay的时间和队头的进行比较,按照触发时间进行排序,如果触发时间更近则放入队头,保证队头的时间最小、队尾的时间最大。
此时,如果队头的Message正是被delay的,则将当前线程堵塞一段时间,直到等待足够时间再唤醒执行该Message,否则唤醒后直接执行。
问题,这个阻塞是如何实现的?
04.handleMessage处理消息
looper从消息队列里拿到消息后,然后交给handler去处理。看下 Handler 对象处理消息的方法
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
如果 msg.callback
不为 null ,则调用 callback 对象的 run()
方法,该 callback 实际上就是一个 Runnable 对象,对应的是 Handler 对象的 post()
方法
private static void handleCallback(Message message) {
message.callback.run();
}
public final boolean post(Runnable r){
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
如果 mCallback
不为 null ,则通过该回调接口来处理消息,如果在初始化 Handler 对象时没有通过构造函数传入 Callback
回调接口,则交由 handleMessage(Message)
方法来处理消息,我们一般也是通过重写 Handler 的 handleMessage(Message)
方法来处理消息
05.createAsync如何理解
首先看一下源码
public static Handler createAsync(@NonNull Looper looper) {
if (looper == null) throw new NullPointerException("looper must not be null");
return new Handler(looper, null, true);
}
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
然后看一下mAsynchronous变量是在哪里用到的?
相当于,如果把mAsynchronous设置成true,则会给Message对象赋值。那这个主要是做什么用的呢?异步消息,需要优先处理。后面在详细说这个
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
06.mainIfNull如何理解
@NonNull
public static Handler getMain() {
if (MAIN_THREAD_HANDLER == null) {
MAIN_THREAD_HANDLER = new Handler(Looper.getMainLooper());
}
return MAIN_THREAD_HANDLER;
}
/** @hide */
@NonNull
public static Handler mainIfNull(@Nullable Handler handler) {
return handler == null ? getMain() : handler;
}
07.如何移除消息
Handler移除消息有多个api,如下所示:
handler.removeCallbacks(runnable);
handler.removeCallbacks(runnable,"token");
handler.removeCallbacksAndMessages(null);
handler.removeMessages(0);
handler.removeMessages(1,"");
08.handler可以是多个吗
09.发送消息的Delay可靠吗
不靠谱的,引起不靠谱的原因有如下
- 发送的消息太多,Looper负载越高,任务越容易积压,进而导致卡顿
- 消息队列有一些消息处理非常耗时,导致后面的消息延时处理
- 大于Handler Looper的周期时基本可靠(例如主线程>50ms)
- 对于时间精确度要求较高,不要用handler的delay作为即时的依据
如何优化保证可靠性
- 消息精简,从数量上处理
- 队列优化,重复消息过滤。这个如何做?
- 互斥消息取消
- 复用消息。其实Message已经用到了享元模式做到了消息创建复用!
消息空闲IdleHandler
MessageQueue.IdleHandler ideHandler =new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
return false;
}
};
Looper.myQueue().addIdleHandler(ideHandler);
使用独享的Looper(HandlerThread)
HandlerThread handlerThread = new HandlerThread("A-Thread");
handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper());