前言
上篇文章通过分析Handler
源码,相信对Handler
机制已经有了更进一步的了解。本篇文章将会通过源码来分析两个很少被提起的概念:消息同步屏障和IdelHandler
。
消息同步屏障
概念
我们平时在使用Handler
时一般发送的有两种消息:同步消息、异步消息。从“消息同步屏障”字面意思我们可以看出,它主要影响的是同步消息。发送同步消息屏障的方法在MessageQueue
中,我们通过源码来看一下“消息同步屏障”究竟是什么。
/**
* Posts a synchronization barrier to the Looper's message queue.
*
* Message processing occurs as usual until the message queue encounters the
* synchronization barrier that has been posted. When the barrier is encountered,
* later synchronous messages in the queue are stalled (prevented from being executed)
* until the barrier is released by calling {@link #removeSyncBarrier} and specifying
* the token that identifies the synchronization barrier.
*....
*/
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
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;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
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;
}
}
/**
* 移除消息同步屏障.
*/
public void removeSyncBarrier(int token) {
// Remove a sync barrier token from the queue.
// If the queue is no longer stalled by a barrier then wake it.
synchronized (this) {
Message prev = null;
Message p = mMessages;
//找到token对应的同步屏障消息
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
if (p == null) {
throw new IllegalStateException("The specified message queue synchronization "
+ " barrier token has not been posted or has already been removed.");
}
final boolean needWake;
//移除该消息
if (prev != null) {
prev.next = p.next;
needWake = false;
} else {
mMessages = p.next;
needWake = mMessages == null || mMessages.target != null;
}
//将这条消息在回收到message池中
p.recycleUnchecked();
// If the loop is quitting then it is already awake.
// We can assume mPtr != 0 when mQuitting is false.
if (needWake && !mQuitting) {
nativeWake(mPtr); //唤醒消息队列
}
}
}
那么让我来翻译一下这两段注释(这里只是部分注释,有兴趣的小伙伴自行查看):
这个方法将会发送一个同步的屏障到Looper的MessageQueue中。
消息队列在遇到同步屏障消息之前都会正常运行。但是当遇到消息同步屏障时,队列中后面的同步消息将会被暂停(禁止被执行)直到执行
removeSyncBarrier() 方法和确定消息同步屏障的token时,它将会被释放释放掉。
关于消息同步屏障的作用,在注释中已经说的很明白了。我们从上述源码中也可以看到:消息同步屏障其实就是在初始化 Message
对象时,将其target
属性设置为 null,也就是说消息同步屏障是没有对应的Handler
进行接收的。
源码分析
我们都知道,Looper
会通过调用MessageQueue
对象的 next()
方法来轮询消息队列发送消息,那么让我们看一下这个方法。
Message next() { //省略了很多不必要代码
......
for (;;) {
......
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
//如果当前消息是同步屏障消息,会进行循环直到找到不是同步消息的那个。
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
}
......
}
}
从上面的源码中我们可以看出:消息同步屏障影响的是在它之后插入的同步消息,在轮询消息队列时跳过同步消息只处理异步消息。但是此时发送的同步消息还是存到了消息队列中,当消息队列被唤醒后,还是会发送这些同步消息。
用法
private void Handler() {
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.i("MainActivity", msg.obj.toString());
break;
}
}
};
Looper.loop();
}
}).start();
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.send_syn_message:
Log.i("MainActivity", "点击了发送同步消息的按钮");
sendSynMesage();
break;
case R.id.send_asy_message:
Log.i("MainActivity", "点击了发送异步消息的按钮");
sendSayMessage();
break;
case R.id.send_syn_barrier_message:
Log.i("MainActivity", "点击了发送同步屏障消息的按钮");
sendSynBarrierMessage();
break;
case R.id.remove_syn_barrier_message:
Log.i("MainActivity", "点击了移除同步屏障消息的按钮");
removeSynBarrierMessage();
break;
}
}
private void sendSynMesage() {
Message message = Message.obtain();
message.what = 1;
message.obj = "发送同步消息";
handler.sendMessageDelayed(message, 500);
}
private void sendSayMessage() {
Message message = Message.obtain();
message.what = 1;
message.obj = "发送异步步消息";
message.setAsynchronous(true);
handler.sendMessageDelayed(message, 500);
}
//插入同步屏障消息
@RequiresApi(api = Build.VERSION_CODES.M)
private void sendSynBarrierMessage() {
try {
MessageQueue queue = handler.getLooper().getQueue();
Method method = MessageQueue.class.getDeclaredMethod("postSyncBarrier");
method.setAccessible(true);
token = (int) method.invoke(queue);
Log.i("MainActivity", "发送同步屏障消息");
} catch (Exception e) {
e.printStackTrace();
}
}
//移除同步屏障消息
@RequiresApi(api = Build.VERSION_CODES.M)
private void removeSynBarrierMessage() {
try {
MessageQueue queue = handler.getLooper().getQueue();
Method method = MessageQueue.class.getDeclaredMethod("removeSyncBarrier", int.class);
method.setAccessible(true);
method.invoke(queue, token);
Log.i("MainActivity", "移除同步屏障消息");
} catch (Exception e) {
e.printStackTrace();
}
}
界面很简单,四个按钮,分别对应发送的事件。我们看一下点击之后的效果。
注意看图中框出来的两个部分,点击发送同步屏障消息之前,同步消息和异步消息都能正常处理;在点击发送同步屏障之后,异步消息正常处理,同步屏障不被处理;在移除同步屏障之后,之前发送几次同步消息,都会被处理。总结
当我们设置了同步屏障消息后,Handler
将会跳过同步消息,只对异步消息进行处理。我们也可以理解成,同步屏障为Handler
消息机制增添了一种简单的优先级机制,异步消息的优先级比同步消息高。
IdelHandler
概念
其实在接触IdelHandler
之前我也不知道它具体是什么概念,但是我们可以通过源码中的注释看一下其定义和相关说明,这就要求小伙伴们有一定的英文能力。废话少说直接看源码。
/**
* 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();
}
又轮到我来翻(zhuang)译(b)了
当一个线程将要阻塞以等待更多消息时的接口回调
当MessageQueue中的消息被分发完毕并且当前等待更多消息时被调用。当queueIdele方法返回值为true时,IdelHandler将被激活;
当返回返回false时,IdelHandler将会被移除。当消息队列中仍然存在延时消息时,这个方法也有可能被调用,但是这些消息将在当前之间之后被发送。
总之,可以浓缩为依据话:当消息队列处于阻塞状态时将会调用IdelHandler的queueIdel方法。
源码分析
从上面可以看出,消息队列阻塞时会调用IdelHandler
的queue()
方法,我们来看一下MessageQueue.next()
方法。
Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//1、判断当前消pendingIdleHandlerCount是否小于零,因为其初始值为-1
//2、判断当前消息队列中消息是否为空
//3、将获取IdleHandler数量
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
//如果IdleHandler数量小于零,将会阻塞消息队列
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
//获取IdleHandler数量
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
//遍历pendingIdleHandlerCount集合
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // 将IdelHandler进行置空
boolean keep = false;
try {
keep = idler.queueIdle(); //调用queueIdle方法
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) { //如果queueIdel方法返回的是false,将IdleHandler进行移除
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
//将IdelHandler数量置零
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
}
从源码中我们可以看出,在消息分发时的执行过程与IdelHandler
的注解说明一致。
除了执行流程我们看一下IdleHandler
的添加和移除过程。
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}
public void removeIdleHandler(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.remove(handler);
}
}
源码中的用法
说到用法,IdleHandler
在平时的开发中很少使用(或许是本人在日常开发中没有用过所产生的错觉,惭愧惭愧),让我们我们看一下系统源码中哪些地方用到了,
ActivityThread
类,这个类是程序的入口,看一下其中是怎么使用的,有兴趣的小伙伴可以自己查阅源码。
void scheduleGcIdler() {
if (!mGcIdlerScheduled) {
mGcIdlerScheduled = true; //执行GC操作
Looper.myQueue().addIdleHandler(mGcIdler);
}
mH.removeMessages(H.GC_WHEN_IDLE);
}
void unscheduleGcIdler() {
if (mGcIdlerScheduled) {
mGcIdlerScheduled = false;
Looper.myQueue().removeIdleHandler(mGcIdler);
}
mH.removeMessages(H.GC_WHEN_IDLE);
}
总结
IdleHandler
会在消息队列为空进入阻塞时调用,这样我们就可以利用IdleHandler
进行延时操作。
1、IdelHandler
可以用于在Activity
启动时延时执行一些不耗时的操作,从而起到优化加载速度。
2、源码中的使用,比如如上所示的ActivityThread
中,强制开启GC操作。
3、Glide图片加载框架等,小伙伴们请自行查阅。
通过两篇文章分析了关于Handler
机制,不知道各位看官感觉如何,如有任何不妥之处,还请大家及时提出。这里给大家准备了关于Handler机制面试题,希望对大家能有所帮助。
最后预祝大家国庆快乐。