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 ...
随机推荐
- 设计模式 - 创建型模式 - 单例模式(C++)
1.前言 单例模式属于创建型模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点. 单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方 ...
- SPFA -----队列优化的Bellman-Ford
SPFA ------队列优化的Bellman-Ford 由Bellman-Ford算法实现带有负权边的单源最短路,时间复杂度是O(VE),也就是边数乘顶点数.但是根据Bellman-Ford的状态转 ...
- 蓝鲸:安装SaaS组件bk_monitor失败分析解决
使用./bk_install saas-o 安装发现bk_monitor(蓝鲸监控)组件报错"ERROR deploy failed: timeout". 单独尝试安装各个组件: ...
- 17.5 稀疏调拨的内存映射文件--《Windows核心编程》
原文链接:https://www.likecs.com/show-306421749.html,原文中代码是C++MFC程序,更详细.本文是C语言测试代码. (1)稀疏文件(Sparse File)定 ...
- 借助 .NET 开源库 Sdcb.DashScope 调用阿里云灵积通义千问 API
在昨天的博文中,我们通过 Semantic Kernel 调用了自己部署的通义千问开源大模型,但是自己部署通义千问对服务器的配置要求很高,即使使用抢占式按量实例,每次使用时启动服务器,使用完关闭服务器 ...
- NC15445 wyh的吃鸡
题目链接 题目 题目描述 最近吃鸡游戏非常火,你们wyh学长也在玩这款游戏,这款游戏有一个非常重要的过程,就是要跑到安全区内,否则就会中毒持续消耗血量,我们这个问题简化如下 假设地图为n*n的一个图, ...
- powerdesigner自定义实体显示的属性
我做概要设计的时候需要画实体的逻辑模型图,默认的时候是这样的: 但是我想只保留属性名,隐藏数据类型和下面的横线怎么办?效果如下: 按以下操作即可实现:
- junit使用stub进行单元测试
stub是代码的一部分,我们要对某一方法做单元测试时,可能涉及到调用第三方web服务.假如当前该服务不存在或不可用咋办?好办,写一段stub代码替代它. stub 技术就是把某一部分代码与环境隔离起来 ...
- windows 程序启动后挂到后台
开发中遇到一个问题,程序启动后,并没有出现在前台,而是自动挂到后台,程序处于卡死状态,基本任何模块都没加载进来. 后面排查发现跟一个功能的第三方 dll 有关系,在那个 dll 加载时导致程序卡死,因 ...
- win32 - GDI+ 高斯模糊的使用
虽然标题中标有GDI+,但其实真正实施的时候并没有用到. 不过GDI+的相关文档有一些关于高斯模糊的api说明,见下面链接: Blur class (gdipluseffects.h) 使用Bl ...