Handler屏障消息
Handler 屏障消息
Handler Message 种类
Handler的Message种类分为3种:
- 普通消息
- 屏障消息
- 异步消息
同步消息
我们默认用的都是同步消息,即前面讲Handler里的构造函数参数的async参数默认是false,同步消息在MessageQueue里的存和取完全就是按照时间排的,也就是通过msg.when来排的。
异步消息
异步消息就是在创建Handler如果传入的async是true或者发送来的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,它最大的特征就是target为null(只有屏障的target可以为null,如果我们自己设置Message的target为null的话会报异常),并且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方法就是用来插入一个屏障到消息队列的,可以看到它很简单。
- 屏障消息和普通消息的区别在于屏障没有
tartget,普通消息有target是因为它需要将消息分发给对应的target,而屏障不需要被分发,它就是用来挡住普通消息来保证异步消息优先处理的。 - 屏障和普通消息一样可以根据时间来插入到消息队列中的适当位置,并且只会挡住它后面的同步消息的分发。
postSyncBarrier返回一个int类型的数值,通过这个数值可以撤销屏障。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);
}
}
}
移除一个消息屏障,做了以下几件事:
- 移除次序列号的
token消息 - 如果主线程是阻塞状态,则唤醒线程
取出消息屏障
说完了屏障消息的插入和删除,那么屏障消息在哪里起作用的?它跟前面提到的异步消息又有什么关联呢?我们可以看到MessageQueue的next方法里有这么一段:
// 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: 收到普通消息
同步消息和异步消息都有收到。
从这验证可以看出,满足前面说的对同步屏障的定义。
揭秘 Android 消息机制之同步屏障:target==null ?
Handler屏障消息的更多相关文章
- Handler发送消息
Handler发送消息小结 字数283 阅读210 评论0 喜欢1 obtainMessage()得到一个Message对象. 创建一个Message然后发送是这么写的: Message msg = ...
- [Android]Handler的消息机制
最经面试中,技术面试中有一个是Handler的消息机制,细细想想,我经常用到的Handler无非是在主线程(或者说Activity)新建一个Handler对象,另外一个Thread是异步加载数据,同时 ...
- Thread+Handler 线程 消息循环(转载)
近来找了一些关于android线程间通信的资料,整理学习了一下,并制作了一个简单的例子. andriod提供了 Handler 和 Looper 来满足线程间的通信.例如一个子线程从网络上下载了一副图 ...
- Android中利用Handler实现消息的分发机制(三)
在第二篇文章<Android中利用Handler实现消息的分发机制(一)>中,我们讲到主线程的Looper是Android系统在启动App的时候,已经帮我们创建好了,而假设在子线程中须要去 ...
- Android----Thread+Handler 线程 消息循环(转载)
近来找了一些关于android线程间通信的资料,整理学习了一下,并制作了一个简单的例子. andriod提供了 Handler 和 Looper 来满足线程间的通信.例如一个子线程从网络上下载了一副图 ...
- Android正在使用Handler实现消息分发机制(零)
演讲前,AsyncTask文章.我们在最后谈到.AsyncTask它是利用Handler异步消息处理机制,操作结果.使用Message回到主线程,从而执行UI更新线程. 而在我们的日常开发工作,Han ...
- android handler传递消息机制
当工作线程给主线程发送消息时,因为主线程是有looper的,所以不需要初始化looper,注意给谁发消息就关联谁的handler,此时用的就是主线程的handler handler会把消息发送到Mes ...
- handler发消息的形式
1.onCreate()中写好handler的接受机制,准备接受并处理消息 2.thread中利用handler.post(Runnable r): protected void onCreate(B ...
- Android正在使用Handler实现消息分发机制(两)
在开始这篇文章之前,.首先,我们在总结前两篇文章Handler, Looper和MessageQueue像一些关键点: 0)在创建线程Handler之前,你必须调用Looper.prepare(), ...
- Android为TV端助力 handler传递消息机制
当工作线程给主线程发送消息时,因为主线程是有looper的,所以不需要初始化looper,注意给谁发消息就关联谁的handler,此时用的就是主线程的handler handler会把消息发送到Mes ...
随机推荐
- ABC 311 A - E
ABC 311 A - E 不提供代码 A 题意:求一个字符串的第一个 ABC 最早出现的位置,可以打乱顺序,可以间隔 建立三个变量,然后以此判断即可,直到三种字符都出现就可以了 B 题意:给定每个人 ...
- Navicat 15 for MySQL 破解【我亲测可用】
1.去官网下载正版 https://www.navicat.com.cn/ 2.破解下载:https://files-cdn.cnblogs.com/files/del88/NavicatKeygen ...
- 你应该知道的提升Visual Studio开发能力的5个技巧
如果你像我一样,或许你也沉迷于开发者工具.这就是我喜欢 Visual Studio 的原因之一--它有无数的生产力技巧. 这篇文章将展示五个这样的技巧,这些技巧对我每天的工作都有帮助.请注意,这些仅适 ...
- 2023年多校联训NOIP层测试2
2023年多校联训NOIP层测试2 爆零了 T1 HDU 4786 Fibonacci Tree \(0pts\) @wangyunbiao: 不可以,总司令 我:不,可以,总司令 @wangyunb ...
- 快速上手typescript(进阶篇)
壹 ❀ 引 我们在快速上手typescript(基础篇)一文中,已经介绍了typescript大部分基础知识,文章结尾也提到这些知识点已足以支撑日常typescript开发,而本文算是对于前文知识点的 ...
- 计算机算法设计与分析(第5版)PDF
<计算机算法设计与分析(第5版)>是2018年电子工业出版社出版的图书,作者是王晓东. 整本书的结构是:先介绍算法设计策略思想,然后从解决经典算法问题来学习,通过实践的方式去学习算法. 网 ...
- STM32F401+nRF24L01无线传输音频(对讲机原型)
尝试结合STM32F401的ADC, PWM, SPI(NRF24L01)和TIM, 试验了一下音频的无线传输(对讲机原型) 工作机制 音频采样 因为硬件的限制, 包括STM32F401片内存储, 内 ...
- 【Unity3D】阴影原理及应用
1 阴影原理 光源照射到不透明物体上,会向该物体的后面投射阴影,如果阴影区域存在其他物体,这些物体不被光源照射的部分就需要渲染阴影.因此,我们可以将阴影的生成抽象出 2 个流程:物体投射阴影.物体 ...
- eclipse解决maven不自动下载jar包问题
今天在eclipse中import一个maven项目,由于我手工中断了相关pom.xml中配置的jar包下载. 导致项目找不到一些jar包.具体错误见下图: 原因就是有些jar包下载没有完成导致的. ...
- 网络上收集的C++常见面试题
1. 进程与线程的关系,图解 进程简单理解就是我们平常使用的程序,进程拥有自己独立的内存空间地址,拥有一个以上的线程. 线程可以理解为轻量级的进程,是程序执行的最小单元.在某个进程启动后,会默认产生一 ...