版权声明:本文章原创于 RamboPan ,未经允许,请勿转载。
虽然 Handler 分析的比较多,可还是想记录下自己分析的思路。
基于 :JKD 1.8 SDK 26
我们设计一个程序时,往往会执行很多小块的代码,而很多小块的代码还经常被调用,
那么根据程序设计的原理,最好抽出公共部分,增加代码的复用性。
这也是 Handler 使用时既有 sendMessage() 也有 postRunnable() 的原因。
sendMessage() 常用来传递某些变量,或者类似一个触发的消息。
postRunnable() 常用来直接执行某个方法。
那么许多代码块都是非立即执行的,那么需要一个好的容器来管理这些任务就是很重要的。
先从 Handler 常用方法使用说起。首先,我们 new 一个新的 Handler。
Handler handler = new Handler();
public Handler() {
this(null, async);
}
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;
}
从常见的无参构造函数进来,顺着重载可以看到两个参数的构造函数。
按着代码的顺序来看,上面省略部分是如果有内存泄露潜在风险,则判断是否是静态的,如果不是会有提示。
下面的 mLooper = Looper.myLooper() 与 mQueue = mLooper.mQueue() 实际都是从 Looper 中取得的,那这个留着等分析 Looper 的时候再来分析。
最后两行代码就是把传入的 Callback 类型与布尔值存下来。
Handler 还有一个三参的构造函数,只是 Looper 获取的方式有所差异。
那我们肯定就要用 Handler 发送消息。那就是 sendMessage() 与 post() 。
//如果直接发送消息,则默认是无延时
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
//根据当前时间加上需要延时的时间,作为此条消息执行的时间
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
//检查时间是否不合理
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
//使用 Handler (Looper 内的消息队列) 内部的消息队列进行消息插入,
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);
}
//在入队前,把该 Handler 存在 Message 对象当中。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
//此处判断是否是异步 Handler ,默认是同步 Handler
//如果是异步,则每条插入的消息都会在此设为异步
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
//需要执行 runnable 时
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
//获取一条 Message 并将 runnable 存在 Message 中
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
Message{
……
Runnable callback;
……
}
按我们一般的习惯,新建了 Handler ,那么肯定要复写它的 handleMessage() 方法。
那怎么知道复写哪个方法,或者消息的处理流程。我们来 Handler 代码中找找有没有分发消息的方法。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
//先判断 message 是否自己带有 runnable ,如果带有,则执行
private static void handleCallback(Message message) {
message.callback.run();
}
//判断 Handler 内部是否有的 callback 接口
//在构造 handler 时可以使用两参的构造自己创建一个
public interface Callback {
public boolean handleMessage(Message msg);
}
//只要没有提前被 return ,就会走此处代码
public void handleMessage(Message msg) {
}
从这个方法名来看,是负责分发消息的。三个方法也分别说明了。
按照这么说,那我们可以通过后两种方式修改来处理消息。
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
//todo
}
};
//如果用这种方式,要注意与 handleMessage 的调用。
Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
//todo
return false;
}
});
Handler 基本说完了,剩下来说说 Looper 。
第一次认识的时候是, Handler 需要传递一个 Looper 变量。
正如代码【1】 中
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
//Handler 中的 Looper 是通过这方法获取的
//这又是从 ThreadLocal 中获取的,那 ThreadLocal 中又是怎么出现的,暂时好像没有思路
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
那既然没有思路,我们还是先从构造函数开始寻找线索。
//新建一个消息队列 并且标记是否可以退出
//记录下当前的线程
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
//当我们调用 prepare 时,默认是可以退出的消息队列
public static void prepare() {
prepare(true);
}
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
//主线程调用时,是不可退出的消息队列
//从注释中可以看出,这事安卓系统调用的。
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
从 prepareMainLooper() 来看,我们猜想下,会不会主线程对应的 Looper 就是 mainLooper ?
我们找到 Looper 中关于返回 Looper 的方法, myLooper() ,getMainLooper() 。
我们在 Activity onCreate() 代码中加入这么一小段,测试下,是否正确。
……
Looper myLooper = Looper.myLooper();
Looper mainLooper = Looper.getMainLooper();
Log.w("TAG->MainActivity","onCreate: myLooper : "+myLooper+" ; mainLooper : "+mainLooper);
new Thread(new Runnable(){
@Override
public void run(){
Looper newThreadLooper=Looper.myLooper();
Log.w("TAG->MainActivity","onCreate: newThreadLooper : "+newThreadLooper);
}
}).start();
……
输出结果:
TAG->MainActivity: onCreate: myLooper : Looper (main, tid 1) {1ad55b7b} ; mainLooper : Looper (main, tid 1) {1ad55b7b}TAG->MainActivity: onCreate: newThreadLooper : null
果然,主线程对应的 Looper 就是 mainLooper ,并且我们在一个新线程中使用 Looper.myLooper() 返回的是一个 null 。
也说明了新的线程并没有调用什么方法生成对应的 Looper 。从代码【1】中,Handler 的两参构造函数,那个抛出的异常,也说明了:如果该线程对应的 Looper 为 null ,无法创建 Handler。
这样我们如果要在新的线程中使用 Handler ,那么可以考虑两种方式:
比如新线程也使用 Looper.prepare() 或者 Looper.getMainLooper() 使用主线程的 Looper 。
那我们先写写这两种 Handler 创建。
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
Handler newLooperHandler = new Handler();
Looper.loop();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
Handler useMainLooperHandler = new Handler(Looper.getMainLooper());
}
}).start();
运行起来,肯定是没问题的。关于 Looper.loop() 方法,在 Looper.prepare() 注释中说明了,如果需要把消息轮询起来,那么需要调用此方法。
如果没有调用的话,使用该 Handler 无法处理消息,不信可以试试哈。
那我们来分析下 Looper.loop() 究竟干嘛了。
public static void loop() {
……
//轮询的是该 Looper 中的消息队列。
final MessageQueue queue = me.mQueue;
……
for (;;) {
//不断的取消息,直到没有更多的消息
//注意下面这句,可能阻塞。
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
……
try {
msg.target.dispatchMessage(msg);
} finally {
……
}
……
msg.recycleUnchecked();
}
}
简化了下代码,从 MessageQueue 中不断取出消息,然后把取到的消息,使用 Message 中的 Handler(target) 对象,调用其分发消息方法,最后把该消息重置后循环。
我们可以观察到调用 Looper.loop() 之后,一直会在 for 循环中寻找消息,如果没有消息的话,就会终止。
从上面的注释可以了解到 Message.next() 可能会阻塞,除非确定没有消息的时候,表明消息队列已经停止工作,Looper.loop() 将要停止轮询消息了。
从 Handle 与 Looper 的一些方法看出,涉及到消息的插入,取出都会有 MessageQueue 有关,有部分方法就是封装的 MessageQueue 的方法,那么接下来看看 MessageQueue。
还是从构造函数开始吧。
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
构造函数很简单,就是判断是否这个队列允许退出,在之前代码【5】中,了解到普通消息队列是传入的 true ,而主线程消息队列是传入的 false。
mPtr 暂时不知道是什么,不过可以看到是 long 类型的,看来是存一个很大的数。
之前有几个方法是通过 Handler 或者 Looper 来间接调用 MessageQueue 的方法,现在来看看。
Handler.enqueueMessage() —> 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;
}
插入队列的方式,就是一个单链表的插入,画了个简图。有三种情况。
//如果当前队列没有消息,或者该消息需要马上执行,或者该消息,小于最前端消息
if (p == null || when == 0 || when < p.when) {
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.
//此时会先判断是否被 barrier 阻塞,该消息是否是异步消息
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);
}
从之前代码【8】中 Looper.loop() 也可以看到取消息的时候是有可能阻塞的,因为一旦返回空消息,loop() 方法就结束了。
所以在 MessageQueue 中阻塞也说明了这个问题,如果暂时没有消息或者是需要执行的消息,就进行阻塞,等到需要唤醒的时候再唤醒。
说到这,需要说一个新的东西,就是 barrier ,也关于到为什么构造 Handler 时需要传入是否为异步参数。
搜索 barrier 可以看到这么一个方法。
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
//累加一个值作为 barrier 的标记放在 msg.arg1 中
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
//根据 barrier 的时间对比消息队列中的时间,找合适的位置进行插入。
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
//重新将该点前后节点的引用进行修改
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
该方法前面还有很长一段注释,简单的说就是。
有时我们按这个时间的执行顺序,把一些任务排列好了,但是可能碰到一种情况,就是可能因为某些条件不满足,此时需要等待条件满足后,再按照顺序执行完。
但是也可能此时会遇到这个队列中需要某些任务不受这个条件的约束,到了该事件,就需要执行这个任务。当然我们可以新建两个队列,一个受条件约束,一个不受,不过这样会多一份的消耗。
于是想出了这么一个方法。需要条件等待时,就插入一个 barrier,阻塞队列,部分消息就一直等待,这部分消息我们称为同步消息;另一部分消息不受条件约束时,也就是不受 barrier 影响,称为异步消息。
这两种消息都放在同一个队列中,如果没有 barrier 时,两种消息都差不多,没区别。
当有 barrier 时,寻找消息时,就暂时不考虑同步消息,查找是否有满足执行条件的异步消息,没有就等待。
明白了这么一个大的前提,我们来看看这段代码,类似于插入消息。在不同的位置进行阻塞,和前面插入消息类似,就不再分析了。
不过需要注意一点的是,barrier 的 target 对象,是没有保存任何 Handler ,所以在消息队列时,会通过判断 message.target == null 来判断该 message 是正常的消息,还是 barrier。
因为定了执行时间的消息,可能会因为 barrier 导致到时间没有执行,而每次取消息时,也会查找队列中所有以前的消息,然后都执行完。才会进入等待。
说到了插入消息,那么肯定要说取消息了。
Looper.loop() —> MessageQueue.next()
Message next() {
……
for (;;) {
……
synchronized (this) {
//找到当前的时间
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
//获取队首指针指向消息
Message msg = mMessages;
//如果第一个消息是 barrier (上面分析出 target == null 时为 barrier)
if (msg != null && msg.target == null) {
//一直向后寻找,直到碰到一个异步消息,跳出 while 循环
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
//如果是找同步消息,则直接判断队首消息,
//判断该该消息时间是否小于当前时间
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
//如果当前时间小于消息执行时间,则继续等待,
//等待时间为 该消息继续等待时间 与 int最大值 中大的一个。
//可以推测,如果该消息等待时间足够长,那么在 Integer.MAX_VALUE 时间后,
//肯定会唤醒一次,然后再继续判断
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
//获取了一条可执行的消息,此时阻塞需要改为 false
mBlocked = false;
//该节点前后重新建立连接
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
//此处是将该消息的下一个节点引用置空
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
//如果已经在退出的话,就返回空消息.
//这个之前在 Looper.loop() 的注释中也说明了,如果返回 null 证明队列已经在退出了。
//如果 Looper.loop() 不返回消息,就是阻塞在 MessageQueue 中。
if (mQuitting) {
dispose();
return null;
}
……
}
……
}
}
因为中间有些其他逻辑的影响,我简化了下,其他代码一会再分析下,大概的分析都已经用注释写出了。
我觉得很巧妙的是,第一个 if 语句对异步消息的查找,如果第一个不为 barrier 的话,直接跳过异步消息查找,走下一段代码,直接就从第一条消息开始判断。
第二处就是 Looper.loop() 是一直循环的,没有消息的时候会阻塞,类似线程挂起,等到有消息时再唤醒,Message.next() 返回 null 表明消息队列退出,而 Looper.loop() 也是因此而停止。
第三处是推测,nextPollTimeoutMillis 在有消息的时候,是一个大的和时间有关的正值。而在没有消息的时候。nextPollTimeoutMillis = -1 ,应该是一直等待的意思。(当然仅是推测)
Message next() {
……
//初始设置为 0
int nextPollTimeoutMillis = 0;
for (; ; ) {
//当不为 0 时进行一些操作
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
……
synchronized (this) {
……
if (msg != null) {
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
……
}
} else {
nextPollTimeoutMillis = -1;
}
}
}
……
}
这是简化后关于 nextPollTimeoutMillis 有关的逻辑,初始设为 0 ,然后有消息时,会修改 nextPollTimeoutMillis 的值。
如果没有消息时,为 -1。并且把这个类似时间的值作为参数使用 nativePollOnce() 方法,仿佛是和底层一些相关的操作。
剩下的方法看起来像是和 IdeHandler 有关的。前几次看这个 next() 方法被干扰了,现在再看逐渐清晰。
当然,还是先看看 IdleHandler 的结构代码。
/**
* Callback interface for discovering when a thread is going to block
* waiting for more messages.
*/
public static interface IdleHandler {
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
*/
boolean queueIdle();
}
虽然看着多,但就两行代码。从代码中可以看出这是个简单的接口,有一个带返回值的实现。
从注释中可以了解到,当线程阻塞空闲时会被调用。
queueIdle() 返回值: 如果还需要执行,则返回 true ;如果以后不再执行,则返回 false。
而 IdleHandler 通过 MessageQueue.addIdleHandler() 方法添加,类似 Handler.post(Runnable) 方式。
既然有点明白了 IdleHandler ,那么就可以来看 next() 方法中其余部分了。
Message next() {
……
//第一次默认为 -1 ,需要去获取外部 idleHandler 数量。
int pendingIdleHandlerCount = -1;
for (;;) {
synchronized (this) {
//如果之前轮询,没有找到合适的消息
//不会返回消息,才有机会执行接下来的代码。
……
//第一次是因为 pendingIdleHandlerCount = -1 ,取一次 idleHandler 数量
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
//这次判断大小是确认是否有需要处理的 idlHandler
//如果没有的话,则将 mBlocked 改为阻塞状态
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}
//如果有需要执行的 idleHandler ,那么就继续下面这段代码
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
//把数组列表中的 idleHandler 复制一份到数组中
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
//挨着顺序从数组中取出对应的 idleHandler
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
//执行 idleHandler.queueIdle() 方法。
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
//根据 idler.queueIdle() 返回结果判断该消息是否从数组列表中移除。
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
//重置数量,保证每次 next() 时,只会执行一次 IdleHandler 方法。
pendingIdleHandlerCount = 0;
}
}
代码中也简单说明了逻辑,那我们肯定需要跑点代码看看来验证下。
onCreate() 中加入如下代码。
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
Log.d("TAG->Main", "handleMessage" );
}
};
handler.sendEmptyMessageDelayed(1, 2000);
//第一个试着返回 true.
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
Log.d("TAG->MainActivity", "queueIdle true " );
return true;
}
});
//第一个试着返回 false.
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
Log.d("TAG->MainActivity", "queueIdle false " );
return false;
}
});
打印结果:
TAG->MainActivity: queueIdle trueTAG->MainActivity: queueIdle false
TAG->MainActivity: queueIdle true
TAG->MainActivity: queueIdle true
TAG->MainActivity: queueIdle true
TAG->MainActivity: queueIdle true
TAG->MainActivity: queueIdle true
TAG->MainActivity: queueIdle true
果然返回 false 的 IdleHandler 只执行了一次,后面不再执行。不过 true 的 IdleHandler 怎么执行这么多?
忍不住摸下了屏幕,有点点发现,不管我触摸屏幕,还是锁屏。都会增加新的 log 。是不是也侧面说明,这些也是使用的 MessageQueue,
那我们现在可以来理下这个 MessageQueue().next() 的逻辑了,大致两种情况。
空队列时,第一条消息插入,先判断是否需要马上执行,如果不需要马上执行,走 IdleHandler 下方代码。然后在此阻塞,等到有合适的消息时,返回该消息,此次 next() 方法执行完成。
空队列时,第一条消息插入,并且该消息需要马上执行,那么按代码的逻辑,肯定是先返回该消息,此次 next() 结束。然后 Looper.Loop() 中发起下一次 MessageQueue.next() 方法,此时没有合适的消息,然后再走 IdleHandler 代码,并且阻塞在这,等到下次有消息时再结束 next() 方法。
至于关于阻塞的代码,我猜测应该与 nativePollOnce() 、 Binder.flushPendingCommands() 有关,在阻塞的时候,native 层进行暂停,然后在一定时间后或者得到另一个信号时,暂停解除,接着走 java 层代码。
Message next() {
……
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
}
……
}
但是现在水平有限,就暂时分析到这吧,如果有不足之处或有错误,欢迎指出,一起进步。