Android知识总结
同步屏障
大家经过上面的学习应该知道,线程的消息都是放到同一个MessageQueue里面,取消息的时候是互斥取消息,而且只能从头部取消息,而添加消息是按照消息的执行的先后顺序进行的排序,那么问题来了,同一个时间范围内的消息,如果它是需要立刻执行的,那我们怎么办,按照常规的办法,我们需要等到队列轮询到我自己的时候才能执行哦,那岂不是黄花菜都凉了。所以,我们需要给紧急需要执行的消息一个绿色通道,这个绿色通道就是同步屏障
的概念。
一、 target 为何物
Message 类:
@UnsupportedAppUsage
/*package*/ Handler target;
从这里可以知道,Message 是持有 Handler 的, 所谓的 target 即为 Handler 对象。
我们可以通过 Handle 发送消息的时候(如调用Handler#sendMessage()
等 ),最终都是会调用 Handler#enqueueMessage()
让消息入队,最终找到target 。
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);
}
当我们发送一个消息的时候,msg.target 就会被赋值为this, 而 this 即为我们的 Handler 对象。因此,通过这种方式传进来的消息的 target
肯定也就不为 null,并且 mAsynchronous
默认为 false,也就是说我们一般发送的消息都为同步消息
二、 异步消息
设置异步消息有两种方式:
一种是直接设置消息为异步的
Message msg = mMyHandler.obtainMessage();
msg.setAsynchronous(true);
mMyHandler.sendMessage(msg);
还有一个需要用到 Handler 的一个构造方法
@UnsupportedAppUsage
public Handler(boolean async) {
this(null, async);
}
使用
Handler mMyHandler = new Handler(true);
Message msg = mHandler.obtainMessage();
mMyHandler.sendMessage(msg);
但需要注意的是,通过上面两种方式来发送的消息还不是异步消息,因为它们最终还是会进入 enqueueMessage(),仍然会给 target 赋值 ,导致 target 不为null。
三、同步屏障
发送异步消息的关键就是要消息开启一个同步屏障。屏障的意思即为阻碍,顾名思义,同步屏障就是阻碍同步消息,只让异步消息通过。
MessageQueue#postSyncBarrier()
@UnsupportedAppUsage
@TestApi
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++;
//从消息池中获取Message
final Message msg = Message.obtain();
msg.markInUse();
//初始化Message对象的时候,并没有给target赋值,因此 target==null
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
//如果开启同步屏障的时间(假设记为T)T不为0,且当前的同步消息里有时间小于T,则prev也不为null
prev = p;
p = p.next;
}
}
//根据prev是不是为null,将 msg 按照时间顺序插入到 消息队列(链表)的合适位置
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
可以看到,Message 对象初始化的时候并没有给 target 赋值,因此,target == null的 来源就找到了。上面消息的插入也做了相应的注释。这样,一条target == null 的消息就进入了消息队列。
如果对消息机制有所了解的话,应该知道消息的最终处理是在消息轮询器Looper#loop()中,而loop()循环中会调用MessageQueue#next()从消息队列中进行取消息。
@UnsupportedAppUsage
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
// 1.如果nextPollTimeoutMillis=-1,一直阻塞不会超时。
// 2.如果nextPollTimeoutMillis=0,不会阻塞,立即返回。
// 3.如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时)
// 如果期间有程序唤醒会立即返回。
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// 如果target==null,那么它就是屏障,需要循环遍历,一直往后找到第一个异步的消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());//确定是异步消息
}
if (msg != null) {
//如果有消息需要处理,先判断时间有没有到,如果没到的话设置一下阻塞时间
//场景如常用的postDelay
if (now < msg.when) {
//计算出离执行时间还有多久赋值给nextPollTimeoutMillis,
//表示nativePollOnce方法要等待nextPollTimeoutMillis时长后返回
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 获取到消息
mBlocked = false;
//链表操作,获取msg并且删除该节点
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 {
// 没有消息,nextPollTimeoutMillis复位
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
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;
try {
keep = idler.queueIdle(); //具体执行
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
//根据queueIdle()方法返回值决定是否移除该IdleHandler
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
从上面可以看出,当消息队列开启同步屏障的时候(即标识为 msg.target == null ),消息机制在处理消息的时
候,优先处理异步消息。这样,同步屏障就起到了一种过滤和优先级的作用。
下面用示意图简单说明:
如上图所示,在消息队列中有同步消息和异步消息(黄色部分)以及一道墙----同步屏障(红色部分)。有了同步屏障的存在,msg_2 和 msg_M 这两个异步消息可以被优先处理,而后面的 msg_3 等同步消息则不会被处理。那么这些同步消息什么时候可以被处理呢?那就需要先移除这个同步屏障,即调用removeSyncBarrier()
四、同步屏障使用场景
似乎在日常的应用开发中,很少会用到同步屏障。那么,同步屏障在系统源码中有哪些使用场景呢?Android 系统中的 UI 更新相关的消息即为异步消息,需要优先处理。
比如,在 View 更新时,draw、requestLayout、invalidate 等很多地方都调用了
ViewRootImpl#scheduleTraversals()
如下:
@UnsupportedAppUsage
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//开启同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//发送异步消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
postCallback() 最终走到了 Choreographer#postCallbackDelayedInternal()
:
@UnsupportedAppUsage
@TestApi
public void postCallback(int callbackType, Runnable action, Object token) {
postCallbackDelayed(callbackType, action, token, 0);
}
@UnsupportedAppUsage
@TestApi
public void postCallbackDelayed(int callbackType,
Runnable action, Object token, long delayMillis) {
if (action == null) {
throw new IllegalArgumentException("action must not be null");
}
if (callbackType < 0 || callbackType > CALLBACK_LAST) {
throw new IllegalArgumentException("callbackType is invalid");
}
postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + delayMillis);
}
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true); //异步消息
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
这里就开启了同步屏障,并发送异步消息,由于 UI 更新相关的消息是优先级最高的,这样系统就会优先处理这些异步消息
最后,当要移除同步屏障的时候需要调用 ViewRootImpl#unscheduleTraversals()
void unscheduleTraversals() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
//移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
mChoreographer.removeCallbacks(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
}
五、IdleHandler
很多人在Android项目中都会遇到希望一些操作延迟一点处理,一般会使用Handler.postDelayed(Runnable r, long delayMillis)来实现,但是又不知道该延迟多少时间比较合适,因为手机性能不同,有的性能较差可能需要延迟较多,有的性能较好可以允许较少的延迟时间。
之前在项目中对启动过程进行优化,用到了IdleHandler,它可以在主线程空闲时执行任务,而不影响其他任务的执行。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
Handler handler = new Handler(Looper.myLooper());
//添加 IdleHandler
handler.getLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
//一些延迟一点处理的操做可以在此处理,
//当其他Handler消息分发完毕才会执行此处的消息
return false;
}
});
//移除 IdleHandler
handler.getLooper().getQueue().removeIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
return false;
}
});
}
可以将上述代码添加到Activity onCreate中,在queueIdle()方法中实现延迟执行任务,在主线程空闲,也就是activity创建完成之后,它会执行queueIdle()方法中的代码。
queueIdle()
返回true
表示可以反复执行该方法,即执行后还可以再次执行;返回false
表示执行完该方法后会移除该IdleHandler,即只执行一次。
注意:在主线程中使用时queueIdle中不能执行太耗时的任务。
六、小结
同步屏障的设置可以方便地处理那些优先级较高的异步消息。当我们调用Handler.getLooper().getQueue().postSyncBarrier() 并设置消息的 setAsynchronous(true) 时,target 即为 null ,也就开启了同步屏障。当在消息轮询器 Looper 在 loop() 中循环处理消息时,如若开启了同步屏障,会优先处理其中的异步消息,而阻碍同步消息。