Handler中的Message可以分为两类:同步消息、异步消息。消息类型可以通过以下函数得知

//Message.java
public boolean isAsynchronous() {return (flags & FLAG_ASYNCHRONOUS) != 0;
}

一般情况下这两种消息的处理方式没什么区别,只有在设置了同步屏障时才会出现差异。

1 什么是同步屏障

同步屏障可以通过MessageQueue.postSyncBarrier函数来设置

   /**** @hide*/public int postSyncBarrier() {return postSyncBarrier(SystemClock.uptimeMillis());}private int postSyncBarrier(long when) {synchronized (this) {final int token = mNextBarrierToken++;//1、屏障消息和普通消息的区别是屏障消息没有tartget。final Message msg = Message.obtain();msg.markInUse();msg.when = when;msg.arg1 = token;Message prev = null;Message p = mMessages;//2、根据时间顺序将屏障插入到消息链表中适当的位置if (when != 0) {while (p != null && p.when <= when) {prev = p;p = p.next;}}if (prev != null) { // invariant: p == prev.nextmsg.next = p;prev.next = msg;} else {msg.next = p;mMessages = msg;}//3、返回一个序号,通过这个序号可以撤销屏障return token;}}

postSyncBarrier方法就是用来插入一个屏障到消息队列的,可以看到它很简单,从这个方法我们可以知道如下:

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

可以看到,Message 对象初始化的时候没有给 target 赋值,因此, target == null的 来源就找到了。上面消息的插入也做了相应的注释。这样,一条target == null 的消息就进入了消息队列。

该函数仅仅是创建了一个Message对象并加入到了消息链表中。乍一看好像没什么特别的,但是这里面有一个很大的不同点是该Message没有target。

我们通常都是通过Handler发送消息的,Handler中发送消息的函数有post***、sendEmptyMessage***以及sendMessage***等函数,而这些函数最终都会调用enqueueMessage函数

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {msg.target = this;if (mAsynchronous) {msg.setAsynchronous(true);}return queue.enqueueMessage(msg, uptimeMillis);}

可以看到enqueueMessage为msg设置了target字段。

所以,从代码层面上来讲,同步屏障就是一个Message,一个target字段为空的Message。

2 同步屏障的工作原理

同步屏障只在Looper死循环获取待处理消息时才会起作用,也就是说同步屏障在MessageQueue.next函数中发挥着作用。
next函数我们在 Handler工作原理源码解析 中曾经分析过,只不过由于该机制并不影响Handler整体工作流程因此没有展开讲,下面我将相关代码重新贴出来

Message next() .....//省略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) {//获取系统开机到现在的时间final long now = SystemClock.uptimeMillis();Message prevMsg = null;Message msg = mMessages; //当前链表的头结点//如果target==null,那么它就是屏障,需要循环遍历,一直往后找到第一个异步的消息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());}if (msg != null) {//如果有消息需要处理,先判断时间有没有到,如果没到的话设置一下阻塞时间,//场景如常用的postDelayif (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;msg.markInUse();//返回拿到的消息return msg;}} else {//没有消息,nextPollTimeoutMillis复位nextPollTimeoutMillis = -1;}.....//省略}

从上面可以看出,

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());}

其实通过代码,当出现屏障的时候,会滤过同步消息,而是直接获取其中的异步消息并返回。如下图所示:

当消息队列开启同步屏障的时候(即标识为msg.target == null),消息机制会通过循环遍历,优先处理异步消息。这样,同步屏障就起到了一种过滤和优先级的作用。

下面用示意图简单说明:

如上图所示,在消息队列中有同步消息和异步消息(黄色部分)以及一道墙----同步屏障(红色部分)。有了内存屏障的存在,msg_2这个异步消息可以被处理,而后面的 msg_3等同步消息不会被处理。那么什么时候这些同步消息可以被处理呢?那就需要移除这个内存屏障,调用removeSyncBarrier()即可。
举个栗子。开演唱会的时候,观众们都在体育馆门口排队依次等候检票入场(相当于消息队列中的普通消息),这个时候有一大波工作人员来了(相当于异步消息,优先级高于观众),如果他们出示工作证(不出示工作证,就相当于普通观众入场,也还是需要排队,这种情形就是最前面所说的仅仅设置了msg.setAsynchronous(true)),保安立马拦住(出示工作证就拦住就相当于开启了同步屏障)进场的观众,先让工作人员进去(只处理异步消息,而过滤掉同步消息)。等工作人员全部进去了,保安不再阻拦观众(即移除内存屏障),这样观众又可以进场了。只要保安不解除拦截,那么后面的观众就永远不可能进场(不移除内存屏障,同步消息就不会得到处理)。

MessageQueue取出消息整体流程

MessageQueue取消息的流程画了一个简单的流程图

总结

  • Handler在发消息时,MessageQueue已经对消息按照了等待时间进行了排序。
  • MessageQueue不仅包含了Java层消息机制同时包含Native消息机制
  • Handler消息分为异步消息同步消息两种。
  • MessageQueue中存在**“屏障消息“**的概念,当出现屏障消息时,会执行最近的异步消息,同步消息会被过滤。
  • MessageQueue在执行完消息队列中的消息等待更多消息时,会处理一些空闲任务,如GC操作等。

3 如何发送异步消息

通常我们使用Handler发消息时,这些消息都是同步消息,如果我们想发送异步消息,那么在创建Handler时使用以下构造函数中的其中一种(async传true)

public Handler(boolean async);
public Handler(Callback callback, boolean async);
public Handler(Looper looper, Callback callback, boolean async);

然后通过该Handler发送的所有消息都会变成异步消息

4 同步屏障的应用

Android应用框架中为了更快的响应UI刷新事件在ViewRootImpl.scheduleTraversals中使用了同步屏障

void scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled = true;//设置同步障碍,确保mTraversalRunnable优先被执行mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();//内部通过Handler发送了一个异步消息mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);if (!mUnbufferedInputDispatch) {scheduleConsumeBatchedInput();}notifyRendererOfFramePending();pokeDrawLockIfNeeded();}
}

mTraversalRunnable调用了performTraversals执行measure、layout、draw

为了让mTraversalRunnable尽快被执行,在发消息之前调用MessageQueue.postSyncBarrier设置了同步屏障

postCallback()最终走到了Choreographer 的 postCallbackDelayedInternal()

private void postCallbackDelayedInternal(int callbackType,Object action, Object token, long delayMillis) {synchronized (mLock) {final long now = SystemClock.uptimeMillis();final long dueTime = now + delayMillis;mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);//delayMillis传的是0,故此处进入条件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);}}}

最后,当要移除内存屏障的时候需要调用ViewRootImpl#unscheduleTraversals()。

    void unscheduleTraversals() {if (mTraversalScheduled) {mTraversalScheduled = false;//移除内存屏障mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);mChoreographer.removeCallbacks(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);}}

5、实战

1、当点击同步消息会发送一个延时1秒执行普通消息,执行的结果打印log。

2、同步屏障会挡住同步消息。通过点击发送同步屏障->发送同步消息->移除同步消息测试

3、当点击发送同步屏障,会挡住同步消息,但是不会挡住异步消息。通过点击插入同步屏障->插入同步消息->插入异步消息->移除同步屏障 来测试(需要注意不要通过弹土司来测试,通过打印log。不然看不出效果)

测试代码如下(省略布局文件):

public class MainActivity extends AppCompatActivity implements View.OnClickListener {private Handler handler;private int token;public static final int MESSAGE_TYPE_SYNC=1;public static final int MESSAGE_TYPE_ASYN=2;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initHandler();initListener();}private void initHandler() {new Thread(new Runnable() {@Overridepublic void run() {Looper.prepare();handler=new Handler(){@Overridepublic void handleMessage(Message msg) {if (msg.what == MESSAGE_TYPE_SYNC){Log.d("MainActivity","收到普通消息");}else if (msg.what == MESSAGE_TYPE_ASYN){Log.d("MainActivity","收到异步消息");}}};Looper.loop();}}).start();}private void initListener() {findViewById(R.id.btn_postSyncBarrier).setOnClickListener(this);findViewById(R.id.btn_removeSyncBarrier).setOnClickListener(this);findViewById(R.id.btn_postSyncMessage).setOnClickListener(this);findViewById(R.id.btn_postAsynMessage).setOnClickListener(this);}//往消息队列插入同步屏障@RequiresApi(api = Build.VERSION_CODES.M)public void sendSyncBarrier(){try {Log.d("MainActivity","插入同步屏障");MessageQueue queue=handler.getLooper().getQueue();Method method=MessageQueue.class.getDeclaredMethod("postSyncBarrier");method.setAccessible(true);token= (int) method.invoke(queue);} catch (Exception e) {e.printStackTrace();}}//移除屏障@RequiresApi(api = Build.VERSION_CODES.M)public void removeSyncBarrier(){try {Log.d("MainActivity","移除屏障");MessageQueue queue=handler.getLooper().getQueue();Method method=MessageQueue.class.getDeclaredMethod("removeSyncBarrier",int.class);method.setAccessible(true);method.invoke(queue,token);} catch (Exception e) {e.printStackTrace();}}//往消息队列插入普通消息public void sendSyncMessage(){Log.d("MainActivity","插入普通消息");Message message= Message.obtain();message.what=MESSAGE_TYPE_SYNC;handler.sendMessageDelayed(message,1000);}//往消息队列插入异步消息@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)private void sendAsynMessage() {Log.d("MainActivity","插入异步消息");Message message=Message.obtain();message.what=MESSAGE_TYPE_ASYN;message.setAsynchronous(true);handler.sendMessageDelayed(message,1000);}@RequiresApi(api = Build.VERSION_CODES.M)@Overridepublic void onClick(View v) {int id=v.getId();if (id == R.id.btn_postSyncBarrier) {sendSyncBarrier();}else if (id == R.id.btn_removeSyncBarrier) {removeSyncBarrier();}else if (id == R.id.btn_postSyncMessage) {sendSyncMessage();}else if (id == R.id.btn_postAsynMessage){sendAsynMessage();}}}

总结

当我们调用mHandler.getLooper().getQueue().postSyncBarrier()时,target 即为 null ,也就开启了同步屏障。当消息队列 MessageQueue 处理消息时,如若开启了内存屏障,会过滤同步消息而优先循环处理其中的异步消息。

Handler sync barrier(同步屏障)相关推荐

  1. android同步方法和对象的区别是什么,(4.1.10.8)Android Handler之同步屏障机制(sync barrier)...

    一.概述 简单理解为 异步消息插队并优先执行. 场景:排队买票 先来了一个普通用户来排队,买完票走了. 后面又来了一个VIP用户A来买票 就一直站在卖窗口这里 也不走(ps:添加屏障 ) 紧接者又来了 ...

  2. Handler机制——同步屏障

    一.消息种类 关于Handler机制的基本原理不了解可以看这里: Handler机制源码解析. Message分为3种:普通消息(同步消息).屏障消息(同步屏障)和异步消息.我们通常使用的都是普通消息 ...

  3. 什么是Handler的同步屏障机制?

    前言 对于Handler机制,想必大家都已经非常熟悉了吧,从迈进Android开发这扇大门的时候,就不停的研究和使用它,同样的这也是Android系统架构的精髓之一.然而在我们使用的时候,往往会忽略掉 ...

  4. 同步屏障Barrier

    最近写6s081实验写到了lab7,发现了有一个barrier的概念很有意思,查了查资料总结了一下. 同步屏障可以用来管理一个应用中的不同线程,在一个应用中设立一个Barrier,当有指定数目的线程到 ...

  5. Android:同步屏障的简单理解和使用

    同步屏障的简单理解和使用 1.背景 2.何为同步屏障? 2.1. 发送屏障消息--postSyncBarrier 2.2.发送异步消息 2.3.处理消息 2.4.移除屏障消息--removeSyncB ...

  6. java多线程 门闩_Java线程与并发编程实践----同步器(倒计时门闩,同步屏障)...

    Java提供的synchronized关键字对临界区进行线程同步访问.由于基于synchronized很难 正确编写同步代码,并发工具类提供了高级的同步器.倒计时门闩(countdown latch) ...

  7. 高级同步器:可重用的同步屏障Phaser

    引自:https://shift-alt-ctrl.iteye.com/blog/2302923 在JAVA 1.7引入了一个新的并发API:Phaser,一个可重用的同步barrier.在此前,JA ...

  8. Java多线程同步屏障计算_Java多线程之CountDownLatch和CyclicBarrier同步屏障的使用

    一:CountDownLatch CountDownLatch是一个执行 完成任务线程数 的 倒数计数器.我们考虑这种情况:士兵晨练,必须全队士兵集合完毕才开始跑步.用程序描述就:在晨练线程中,逐个启 ...

  9. Java并发编程的艺术(八)——闭锁、同步屏障、信号量详解

    1. 闭锁:CountDownLatch 1.1 使用场景 若有多条线程,其中一条线程需要等到其他所有线程准备完所需的资源后才能运行,这样的情况可以使用闭锁. 1.2 代码实现 // 初始化闭锁,并设 ...

最新文章

  1. ruby实时查看日志
  2. zh-cn 与 zh-hans 是什么关系、有什么区别
  3. Java实现定时调度的三种方法
  4. Linux C 中连接操作符##
  5. Redis的安装与部署
  6. linux之多任务的同步与互斥
  7. 09-一对多关系建表
  8. 利用Android属性动画实现Banner的原理与实践
  9. Mysql学习总结(36)——Mysql查询优化
  10. CVSNT Manual
  11. Flask: windows下flask + tornado+ nginx组合
  12. java生成wsdl文件_webservice之通过wsdl文件生成客户端
  13. Android自定义popWindow教程
  14. 8153网卡linux驱动,绿联Type-c千兆网卡RTL8153驱动
  15. php盘古分词,百度分词技术_百度输入法分词怎么关_百度分词原理
  16. 机器学习 | 台大林轩田机器学习基石课程笔记5 --- Training versus Testing
  17. 【数据处理】 python 极速极简画图——频数(率)分布直方图
  18. Windows一键清空回收站
  19. 基于vegan包对otu表抽平——r语言
  20. 新概念二册 Lesson 46 Expensive and uncomfortable既昂贵又受罪(同位语从句+doing/being done+介词后的宾语从句)

热门文章

  1. php1050r210,parkerPARKESL parker维修PARKEROP-ETCATparker油管PARKESLV压力传感器
  2. 共筑安全内容分发,知道创宇与华为云签署合作备忘录
  3. 笔记本外接显示器无声音
  4. Workbench Command——cifti格式操作,如何制作label文件即 Human_MMP.lh.label.gii?
  5. 能量景观(Energy landscape)
  6. Python实验报告
  7. 如何通过图片定位位置
  8. Unity-IOS遇到的坑 --记账本
  9. Android M App Permissions
  10. COGS 336 Vijos 1018 NOI2003 智破连环阵