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: 收到普通消息
同步消息和异步消息都有收到。
从这验证可以看出,满足前面说的对同步屏障的定义。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!