ps:本文是转载,浏览原文可读性会更好,文章开端会有原文链接

ps:源码是基于 android api 27 来剖析的,demo 是用 kotlin 语言写的。

后面咱们花了很长时间用两篇文章(Android中Handler的音讯机制剖析(一)和Android中Handler的音讯机制剖析(二))剖析 Handler 的音讯机制,这一篇咱们来剖析 Handler 音讯机制中的一种性能叫同步屏障;Message 可分为3种:一般音讯、屏障音讯和异步音讯,其中一般音讯又叫同步音讯,屏障音讯又叫同步屏障;屏障音讯就是在音讯队列中插入一个屏障,在屏障之后的所有一般音讯都会被挡着,不能被解决,屏障不会挡住异步音讯;屏障音讯的目标是确保异步音讯的优先级,让异步音讯先执行。

咱们看看 MessageQueue 的 next 办法,看看哪些是屏障音讯;

Message next() {

    ......    for (;;) {        ......        synchronized (this) {            ......            Message msg = mMessages;            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());            }            ......        }        ......    }}

在 MessageQueue 的 next 办法中,获取以后将要执行的音讯时,对 Message 的 target(属于 Handler 类型) 字段进行了判断,如果以后 Message 的 target 为空,那么以后的 Message 是屏障音讯,则屏蔽以后的 Message 以及后续所有同步音讯的执行,异步音讯不受影响,可能这样形容不是很让人了解;我举个例子,假如 msg1 是屏障音讯,msg1 的下一个音讯 msg2 是同步音讯,msg2 的下一个音讯是 msg3 是异步音讯,那么 msg1 和 msg2 就不会执行,msg3 就会执行;当 !msg.isAsynchronous() 为 true 时,msg 就是同步音讯。

增加同步屏障的操作在 MessageQueue 的 postSyncBarrier 办法,它在 UI 的绘制方面有用到,理解更多 UI 绘制过程中进行音讯屏障,可参考对Android中View的post办法进行摸索这篇文章,咱们且看 postSyncBarrier 办法;

public int postSyncBarrier() {    return postSyncBarrier(SystemClock.uptimeMillis());}private int postSyncBarrier(long when) {    // Enqueue a new sync barrier token.    // We don't need to wake the queue because the purpose of a barrier is to stall it.    synchronized (this) {        //1、        final int token = mNextBarrierToken++;        //2、        final Message msg = Message.obtain();        msg.markInUse();        msg.when = when;        msg.arg1 = token;        Message prev = null;        Message p = mMessages;        //3、        if (when != 0) {            while (p != null && p.when <= when) {                prev = p;                p = p.next;            }        }        //4、        if (prev != null) { // invariant: p == prev.next            msg.next = p;            prev.next = msg;        } else {            msg.next = p;            mMessages = msg;        }        return token;    }}

postSyncBarrier 办法调用了参数类型为 long 的 MessageQueue.postSyncBarrier 办法,该参数示意的是 MessageQueue 的 next 办法取出音讯的工夫;正文1 示意 mNextBarrierToken 属性获取一个 int 类型的 token,它是该办法的返回值,当移除屏障时会应用到 token;正文2 示意创立一个屏障音讯,为什么说是屏障音讯呢?因为这个音讯的 target 属性为空,看到没有,咱们这个 postSyncBarrier 办法外面没有给 音讯的 target 属性赋值;正文3 中的 while 循环示意依据 when 属性,将同步屏障音讯从音讯队列中找到适合的地位;正文4 中的 if 语句里的代码示意依据 when 属性,将同步屏障音讯增加到音讯队列的适合地位。

有了增加屏障音讯的操作那必定也有删除屏障音讯的操作,它的具体实现是在 MessageQueue 的 removeSyncBarrier 办法里;

public void removeSyncBarrier(int token) {

    // Remove a sync barrier token from the queue.    // If the queue is no longer stalled by a barrier then wake it.    synchronized (this) {        Message prev = null;        Message p = mMessages;        //5、        while (p != null && (p.target != null || p.arg1 != token)) {            prev = p;            p = p.next;        }        //6、        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;        //7、        if (prev != null) {            prev.next = p.next;            needWake = false;            //8、        } 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);        }    }}

MessageQueue 的 removeSyncBarrier 办法中的 token 参数要必须是咱们增加屏障音讯的返回值 token,否则删除屏障音讯就会失败;正文5 示意先判断以后音讯 Message,是否是同步屏障音讯,如果以后音讯不是同步屏障音讯,则遍历音讯链表进行查找;正文6 示意如果没有找到咱们指定要删除的屏障音讯,就抛出异样;正文7 示意如果屏障音讯 p 还未执行,则删除该音讯,不必执行唤醒取音讯的线程;以后音讯是屏障音讯 p,那么删除该音讯后,还须要依据条件,进一步判断是否须要唤醒取音讯的线程。

好了,剖析了那么多,咱们来写一个 demo 来体验一下吧,这里咱们的 demo 性能有插入屏障音讯、插入一般音讯、插入异步音讯和移除屏障音讯(留神:demo 最小的 API 是 23)。

(1)新建一个 kotlin 语言类型的 Activity,名叫 MainActivity:

class MainActivity : AppCompatActivity() {

companion object {    var TAG: String = "MainActivity"    var HANDLE_SYNCHRONOUS_MESSAGE: Int = 1    var HANDLE_ASYNCHRONOUS_MESSAGE: Int = 2    var EXIT: Int = 3}var token: Int = -1var mH: Handler? = nulloverride fun onCreate(savedInstanceState: Bundle?) {    super.onCreate(savedInstanceState)    setContentView(R.layout.activity_main)    MyT().start()}fun onClick(v: View) {    when (v.id) {        R.id.btn_1 -> {            sendMessage("插入一条一般音讯",HANDLE_SYNCHRONOUS_MESSAGE,false)        }        R.id.btn_2 -> {            sendMessage("插入一条异步音讯",HANDLE_ASYNCHRONOUS_MESSAGE,true)        }        R.id.btn_3 -> {            postSyncBarrier();        }        R.id.btn_4 -> {            removeSyncBarrier();        }    }}fun sendMessage(optinMessage: String,what: Int,asynchronous: Boolean) {    Log.d(TAG,optinMessage)    var msg: Message = Message.obtain();    msg.what = what    msg.isAsynchronous = asynchronous    if (mH != null) {        mH!!.sendMessage(msg)    }}fun postSyncBarrier() {    Log.d(TAG,"插入同步屏障")    if (mH != null) {        var mq: MessageQueue = mH?.getLooper()!!.queue        var method: Method = MessageQueue::class.java.getDeclaredMethod("postSyncBarrier")        method.isAccessible = true        token = (method.invoke(mq)) as Int    }}fun removeSyncBarrier() {    if (token != -1) {        Log.d(TAG,"移除同步屏障")        if (mH != null) {            var mq: MessageQueue = mH?.getLooper()!!.queue            var method: Method = MessageQueue::class.java.getDeclaredMethod("removeSyncBarrier",Int::class.java)            method.isAccessible = true            method.invoke(mq,token)        }        token = -1    }}override fun onPause() {    super.onPause()    if (mH != null) {        mH!!.sendEmptyMessage(EXIT)    }}override fun onDestroy() {    super.onDestroy()    if (mH != null) {        mH!!.removeCallbacksAndMessages(null)        mH = null    }}inner class MyH(looper: Looper) : Handler(looper) {    override fun handleMessage(msg: Message?) {        super.handleMessage(msg)        when (msg?.what) {            HANDLE_SYNCHRONOUS_MESSAGE -> {                Log.d(TAG, "解决同步音讯");            }            HANDLE_ASYNCHRONOUS_MESSAGE -> {                Log.d(TAG, "解决异步音讯");            }            EXIT -> {                Looper.myLooper().quit()            }        }    }}inner class MyT : Thread() {    override fun run() {        super.run()        Looper.prepare()        mH = MyH(Looper.myLooper())        Looper.loop()    }}

}

(2)新建 MainActivity 对应的布局文件 activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout

xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context="com.xe.handlerdemo3.MainActivity"><Button    android:id="@+id/btn_1"    android:layout_width="match_parent"    android:text="插入一条一般音讯"    android:onClick="onClick"    android:layout_height="wrap_content" /><Button    android:id="@+id/btn_2"    android:layout_width="match_parent"    android:text="插入一条异步音讯"    android:onClick="onClick"    android:layout_height="wrap_content" /><Button    android:id="@+id/btn_3"    android:layout_width="match_parent"    android:text="插入一条屏障音讯"    android:onClick="onClick"    android:layout_height="wrap_content" /><Button    android:id="@+id/btn_4"    android:layout_width="match_parent"    android:text="移除屏障音讯"    android:onClick="onClick"    android:layout_height="wrap_content" />

</LinearLayout>

程序运行时,界面展现如下所示:

图片

当我点击 “插入一条屏障音讯” 按钮后再点击 “插入一条一般音讯” 按钮,日志输入如下所示,发现没有看到 MyH 解决一般音讯;

10-05 22:54:46.678 13496-13496/com.xe.handlerdemo3 D/MainActivity: 插入同步屏障
10-05 22:55:01.336 13496-13496/com.xe.handlerdemo3 D/MainActivity: 插入一条一般音讯

当我持续点击 “插入一条异步音讯” 按钮后,日志打印如下所示,发现 MyH 解决了异步音讯;

10-05 22:57:43.228 13496-13496/com.xe.handlerdemo3 D/MainActivity: 插入一条异步音讯
10-05 22:57:43.229 13496-13661/com.xe.handlerdemo3 D/MainActivity: 解决异步音讯

当我持续点击 “移除屏障音讯” 按钮时,日志打印如下所示,发现 MyH 解决了之前没有解决的一般音讯;

10-05 23:05:01.587 16365-16400/com.xe.handlerdemo3 D/MainActivity: 解决同步音讯

本篇文章写到这里就完结了,因为技术水平无限,文章中难免会出错,欢送大家批评指正。