侧边栏
首页代码

Handler屏障消息

Handler 屏障消息

Handler Message 种类

HandlerMessage种类分为3种:

  • 普通消息
  • 屏障消息
  • 异步消息

同步消息

我们默认用的都是同步消息,即前面讲Handler里的构造函数参数的async参数默认是false,同步消息在MessageQueue里的存和取完全就是按照时间排的,也就是通过msg.when来排的。

异步消息

异步消息就是在创建Handler如果传入的asynctrue或者发送来的Message通过msg.setAsynchronous(true);后的消息就是异步消息,异步消息的功能要配合下面要讲的屏障消息才有效,否则和同步消息是一样的处理。

// Handler.java
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
// 这个mAsynchronous就是在创建Handler的时候传入async参数
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}

Barrier(屏障)消息

屏障消息又称为同步屏障。

屏障(Barrier)是一种特殊的Message,它最大的特征就是targetnull(只有屏障的target可以为null,如果我们自己设置Messagetargetnull的话会报异常),并且arg1属性被用作屏障的标识符来区别不同的屏障。屏障的作用是用于拦截队列中同步消息,放行异步消息。

屏障消息就是在消息队列中插入一个屏障,在屏障之后的所有普通消息都会被挡着,不能被处理。不过异步消息却例外,屏障不会挡住异步消息,因此可以这样认为:屏障消息就是为了确保异步消息的优先级,设置了屏障后,只能处理其后的异步消息,同步消息会被挡住,除非撤销屏障。

那么屏障消息是怎么被添加和删除的呢?我们可以看到在MessageQueue里有添加和删除屏障消息的方法:

添加消息屏障

// MessageQueue.java
private int postSyncBarrier(long when) {
synchronized (this) {
//屏障消息和普通消息的区别是屏障消息没有tartget。
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) {
// 这里是说如果p指向的消息时间戳比屏障消息小,说明这个消息比屏障消息先进入队列,
// 那么这个消息不应该受到屏障消息的影响(屏障消息只影响比它后加入消息队列的消息),找到第一个比屏障消息晚进入的消息指针
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
// 上面找到第一个比屏障消息晚进入的消息指针之后,把屏障消息插入到消息队列中,也就是屏障消息指向第一个比它晚进入的消息p,
// 上一个比它早进入消息队列的prev指向屏障消息,这样就完成了插入。
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
// 如果prev是null,说明上面没有经过移动,也就是屏障消息就是在消息队列的头部了。
msg.next = p;
mMessages = msg;
}
//返回一个序号,通过这个序号可以撤销屏障
return token;
}
}

postSyncBarrier方法就是用来插入一个屏障到消息队列的,可以看到它很简单。

  1. 屏障消息和普通消息的区别在于屏障没有tartget,普通消息有target是因为它需要将消息分发给对应的target,而屏障不需要被分发,它就是用来挡住普通消息来保证异步消息优先处理的。
  2. 屏障和普通消息一样可以根据时间来插入到消息队列中的适当位置,并且只会挡住它后面的同步消息的分发。
  3. postSyncBarrier返回一个int类型的数值,通过这个数值可以撤销屏障。
  4. postSyncBarrier方法是私有的,如果我们想调用它就得使用反射。、

移除消息屏障

// MessageQueue.java
public void removeSyncBarrier(int token) {
synchronized (this) {
Message prev = null;
Message p = mMessages;
// 前面在插入屏障消息后会生成一个token,这个token就是用来删除该屏障消息用的。
// 所以这里通过判断target和token来找到该屏障消息,从而进行删除操作
// 找到屏障消息的指针p
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;
// 上面找到屏障消息的指针p后,把前一个消息指向屏障消息的后一个消息,这样就把屏障消息移除了
if (prev != null) {
prev.next = p.next;
needWake = false;
} else {
mMessages = p.next;
needWake = mMessages == null || mMessages.target != null;
}
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);
}
}
}

移除一个消息屏障,做了以下几件事:

  1. 移除次序列号的token消息
  2. 如果主线程是阻塞状态,则唤醒线程

取出消息屏障

说完了屏障消息的插入和删除,那么屏障消息在哪里起作用的?它跟前面提到的异步消息又有什么关联呢?我们可以看到MessageQueuenext方法里有这么一段:

// MessageQueue.java
Message next() {
......
// 1.如果nextPollTimeoutMillis=-1,一直阻塞不会超时。
// 2.如果nextPollTimeoutMillis=0,不会阻塞,立即返回。
// 3.如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时)
// 如果期间有程序唤醒会立即返回。
int nextPollTimeoutMillis = 0;
for (;;) {
......
//1、如果有消息被插入到消息队列或者超时时间到,就被唤醒,否则阻塞在这。
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();//获取系统开机到现在的时间
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {//2、遇到屏障 msg.target == null
do {
prevMsg = msg;
msg = msg.next;
// 这里的isAsynchronous方法就是前面设置进msg的async参数,通过它判断如果是异步消息,则跳出循环,把该异步消息返回
// 否则是同步消息,把同步消息阻塞。
} while (msg != null && !msg.isAsynchronous());//3、遍历消息链表找到最近的一条异步消息
}
if (msg != null) {
//4、如果找到异步消息
if (now < msg.when) {//异步消息还没到处理时间,就在等会(超时时间) nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else { //异步消息到了处理时间,就从链表移除,返回它。
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;
}
......
}
}

可以看到,当设置了同步屏障之后,next函数将会忽略所有的同步消息,返回异步消息。换句话说就是,设置了同步屏障之后,Handler只会处理异步消息。再换句话说,同步屏障为Handler消息机制增加了一种简单的优先级机制,异步消息的优先级要高于同步消息。

屏障消息的实际应用

屏障消息的作用是把在它之后入队的同步消息阻塞,但是异步消息还是正常按顺序取出执行,那么它的实际用途是什么呢?我们看到ViewRootImpl.scheduleTraversals()用到了屏障消息和异步消息。

TraversalRunnable的run(),在这个run()中会执行doTraversal(),最终会触发View的绘制流程:measure(),layout(),draw()。为了让绘制流程尽快被执行,用到了同步屏障技术。

// ViewRootImpl.java
//ViewRootImpl的requestLayout开启绘制流程
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();//检查是否在主线程
mLayoutRequested = true;//mLayoutRequested 是否measure和layout布局。
scheduleTraversals();//重要函数
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 这里先将主线程的MessageQueue设置了个消息屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 这里发送了个异步消息mTraversalRunnable,这个mTraversalRunnable最终会执行doTraversal(),也就是会触发View的绘制流程
// 也就是说通过设置屏障消息,会把主线程的同步消息先阻塞,优先执行View绘制这个异步消息进行界面绘制。
// 这很好理解,界面绘制的任务肯定要优先,否则就会出现界面卡顿。
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
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);
}
}
}

屏障消息demo

public class HandlerActivity extends AppCompatActivity {
private Button button1,button2,button3,button4;
public static final int MESSAGE_TYPE_SYNC=1;
public static final int MESSAGE_TYPE_ASYN=2;
private int token;
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
initView();
initHandler();
}
private void initHandler() {
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
mHandler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
Log.e("HandlerActivity","currentThread:"+Thread.currentThread().getName());
//super.handleMessage(msg);
if (msg.what == MESSAGE_TYPE_SYNC){
Log.d("HandlerActivity","收到普通消息");
}else if (msg.what == MESSAGE_TYPE_ASYN){
Log.d("HandlerActivity","收到异步消息");
}
}
};
Looper.loop();
}
}).start();
}
private void initView() {
button1 = findViewById(R.id.send_syne);
button2 = findViewById(R.id.remove_sunc);
button3 = findViewById(R.id.send_message);
button4 = findViewById(R.id.send_async_message);
button1.setOnClickListener(new View.OnClickListener() {
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
public void onClick(View v) {
sendSyncBarrier();
}
});
button2.setOnClickListener(new View.OnClickListener() {
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
public void onClick(View v) {
removeSyncBarrier();
}
});
button3.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sendSyncMessage();
}
});
button4.setOnClickListener(new View.OnClickListener() {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
@Override
public void onClick(View v) {
sendAsynMessage();
}
});
}
private void sendSyncBarrier() {
Log.d("HandlerActivity","插入同步屏障");
MessageQueue queue = mHandler.getLooper().getQueue();
try {
Method method = MessageQueue.class.getDeclaredMethod("postSyncBarrier");
method.setAccessible(true);
token = (int) method.invoke(queue);//1
} catch (Exception e) {
e.printStackTrace();
}
}
private void removeSyncBarrier() {
Log.d("HandlerActivity","移除屏障");
MessageQueue queue = mHandler.getLooper().getQueue();
try {
Method method = MessageQueue.class.getDeclaredMethod("removeSyncBarrier",int.class);
method.setAccessible(true);
method.invoke(queue,token);//2
} catch (Exception e) {
e.printStackTrace();
}
}
private void sendSyncMessage() {
Log.d("HandlerActivity","插入普通消息");
Message message = Message.obtain();
message.what = MESSAGE_TYPE_SYNC;
mHandler.sendMessageDelayed(message,1000);
}
private void sendAsynMessage() {
Log.d("HandlerActivity","插入异步消息");
Message message=Message.obtain();
message.what = MESSAGE_TYPE_ASYN;
message.setAsynchronous(true);//3
mHandler.sendMessageDelayed(message,1000);
}
}

运行结果

只发送一个同步消息

2021-12-21 11:11:19.847 7138-7138/com.zxj.myhandler D/HandlerActivity: 插入普通消息
2021-12-21 11:11:20.847 7138-7138/com.zxj.myhandler D/HandlerActivity: 收到普通消息

因为代码中是延时1s,所以1S后收到普通消息。

只发一个异步消息

2021-12-21 11:12:39.279 7138-7138/com.zxj.myhandler D/HandlerActivity: 插入异步消息
2021-12-21 11:12:40.280 7138-7138/com.zxj.myhandler D/HandlerActivity: 收到异步消息

先插入同步屏障,再发送同步和异步消息

2021-12-21 11:13:18.215 7138-7138/com.zxj.myhandler D/HandlerActivity: 插入同步屏障
2021-12-21 11:13:24.706 7138-7138/com.zxj.myhandler D/HandlerActivity: 插入普通消息
2021-12-21 11:13:28.322 7138-7138/com.zxj.myhandler D/HandlerActivity: 插入异步消息
2021-12-21 11:13:29.322 7138-7138/com.zxj.myhandler D/HandlerActivity: 收到异步消息

我们可以看到,只收到了异步消息,而同步消息没有收到

先插入同步屏障,再发送同步和异步消息。再移除同步屏障

2021-12-21 11:13:18.215 7138-7138/com.zxj.myhandler D/HandlerActivity: 插入同步屏障
2021-12-21 11:13:24.706 7138-7138/com.zxj.myhandler D/HandlerActivity: 插入普通消息
2021-12-21 11:13:28.322 7138-7138/com.zxj.myhandler D/HandlerActivity: 插入异步消息
2021-12-21 11:13:29.322 7138-7138/com.zxj.myhandler D/HandlerActivity: 收到异步消息
2021-12-21 11:14:14.882 7138-7138/com.zxj.myhandler D/HandlerActivity: 移除屏障
2021-12-21 11:14:14.886 7138-7138/com.zxj.myhandler D/HandlerActivity: 收到普通消息

同步消息和异步消息都有收到。

从这验证可以看出,满足前面说的对同步屏障的定义。

Handler中的同步屏障

源码深度解析 Handler 机制及应用

Android Handler 机制 屏障消息(同步屏障)

Android Handler拾遗 - 屏障消息

揭秘 Android 消息机制之同步屏障:target==null ?

posted @   咸鱼Jay  阅读(198)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
页脚HTML代码
点击右上角即可分享
微信分享提示
电磁波切换