Android中Handler的消息机制--同步屏障

小二玩编程

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 = -1
var mH: Handler? = null
override 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: 处理同步消息

本篇文章写到这里就结束了,由于技术水平有限,文章中难免会出错,欢迎大家批评指正。

阅读 297

活到老,学到老。

4 声望
1 粉丝
0 条评论
你知道吗?

活到老,学到老。

4 声望
1 粉丝
文章目录
宣传栏