05.Handler源码之同步消息屏障

104 阅读4分钟

同步消息屏障会阻碍同步消息的处理,但异步消息不受影响。

同步消息:按照消息队列顺序执行的消息,当消息队列添加同步屏障消息,同步屏障之前的消息不受影响,之后的同步消息将不被处理,直到同步屏障消息被移除;

异步消息:Message.isAsynchronous()==true的消息,这类消息不受同步消息屏障影响,在设置了消息屏障后,异步消息会首先得到执行;

同步屏障消息:该类消息单纯用于限制同步消息的处理,不影响异步消息处理。

设置同步屏障的源码如下:

public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

//根据注释,插入屏障消息并不会唤醒队列
    private int postSyncBarrier(long when) {
        synchronized (this) {
            final int token = mNextBarrierToken++;    //生成屏障消息的令牌
            final Message msg = Message.obtain();    //直接从全局消息池返回一个消息
            msg.markInUse();    //标记为正在使用
            msg.when = when;
            msg.arg1 = token;    //信息的arg1字段携带令牌

            Message prev = null;
            Message p = mMessages;
            if (when != 0) {    //以时间为顺序找到合适的插入位置
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }
            //将同步屏障消息插入消息队列
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;    //返回令牌
        }
    }

可以看到,同步屏障消息是在消息队列中直接创建插入的,并没有和Handler进行绑定。前面的几处源码都出现了msg.target==null的判断,其实就是判断是否为同步屏障消息。

根据消息队列的遍历原理可以知道,当遍历发现同步屏障消息时,会往下一直遍历找到异步消息,如果没有异步消息会一直阻塞等待。

同步屏障消息的删除:

   public void removeSyncBarrier(int token) {
        synchronized (this) {
            Message prev = null;
            Message p = mMessages;
            //遍历找到同步屏障消息
            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;    //是否需要唤醒
            //删除屏障消息
            if (prev != null) {        //屏障消息在队列的中间
                prev.next = p.next;
                needWake = false;
            } else {                    //屏障消息在队列头部                  
                mMessages = p.next;
                needWake = mMessages == null || mMessages.target != null;    //队列为空或者队头不是屏障消息,则唤醒
            }
            p.recycleUnchecked();        //回收屏障消息

            // 唤醒消息
            if (needWake && !mQuitting) {
                nativeWake(mPtr);
            }
        }
    }

删除同步屏障需要传入令牌,查找屏障消息时会比对令牌,只有令牌一致才会将屏障消息删除。当被删除的屏障消息在队列头部,会唤醒消息队列。

消息屏障的相关方法无法直接调用,只能通过反射调用:

//先创建Handler
private inner class MHandler(looper: Looper) : Handler(looper) {
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
            Log.e(
                TAG,
                "${msg.obj} ${msg.what},currentTime=${System.currentTimeMillis().format(format)}"
            )
        }
    }
    
    
private val TAG = this.javaClass.simpleName
private val format = "hh:mm:ss"
private lateinit var mHandler: MHandler


//设置消息屏障的方法
 @RequiresApi(Build.VERSION_CODES.M)
    private fun setBarrier() {
        try {
            val queue = mainLooper.queue    //获取MessageQueue
            val queueClass = queue.javaClass
            val postSync = queueClass.getMethod("postSyncBarrier")  //获取设置屏障的方法
            val removeSync = queueClass.getMethod("removeSyncBarrier", Int::class.java) //获取删除屏障的方法

            Log.e(TAG, "post sync barrier:" + System.currentTimeMillis())
            val tooken = postSync.invoke(queue) //插入一条屏障消息  

            //5s后删除屏障消息
            Thread {
                Thread.sleep(5000)
                Log.e(TAG, "remove sync barrier:" + System.currentTimeMillis())
                removeSync.invoke(queue, tooken)
            }.start()

        } catch (e: NoSuchMethodException) {
            e.printStackTrace()
            Log.e(TAG, "invoke error:${e.toString()}")
        } catch (e: ClassNotFoundException) {
            e.printStackTrace()
            Log.e(TAG, "invoke error:${e.toString()}")
        }
    }

//发送消息
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_timer)

        val mainLooper = Looper.getMainLooper()    //获取主线程Looper
        mHandler = MHandler(mainLooper)

        //同步消息
        repeat(5) {
            mHandler.obtainMessage(it, "同步无延时消息").sendToTarget()
        }
        //异步消息
        repeat(5) {
            mHandler.obtainMessage(it+10, "异步无延时消息").let {
                it.isAsynchronous=true    //设置消息为异步消息
                mHandler.sendMessage(it)
            }
        }
        //带延时的同步消息,如果没有设置屏障每延时1s将接收到一条消息
        repeat(5) {
            mHandler.obtainMessage(it + 20, "同步有延时消息").apply {
                mHandler.sendMessageDelayed(this, 1000L + it * 1000)
            }
        }
        //异步消息
        repeat(5) {
            mHandler.obtainMessage(it + 30, "异步有延时消息").apply {
                this.isAsynchronous = true
                mHandler.sendMessageDelayed(this, 1000L + it * 1000)
            }
        }
        setBarrier()
    }

当不调用setBarrier()时,异步消息和同步消息并没有区别。无延时的消息会先输出,延时时间相同的消息会一起输出。

E/TimerActivity: 同步无延时消息 0,currentTime=01:15:45 E/TimerActivity: 同步无延时消息 1,currentTime=01:15:45 E/TimerActivity: 同步无延时消息 2,currentTime=01:15:45 E/TimerActivity: 同步无延时消息 3,currentTime=01:15:45 E/TimerActivity: 同步无延时消息 4,currentTime=01:15:45 E/TimerActivity: 异步无延时消息 10,currentTime=01:15:45 E/TimerActivity: 异步无延时消息 11,currentTime=01:15:45 E/TimerActivity: 异步无延时消息 12,currentTime=01:15:45 E/TimerActivity: 异步无延时消息 13,currentTime=01:15:45 E/TimerActivity: 异步无延时消息 14,currentTime=01:15:45 E/TimerActivity: 同步有延时消息 20,currentTime=01:15:46 E/TimerActivity: 异步有延时消息 30,currentTime=01:15:46 E/TimerActivity: 同步有延时消息 21,currentTime=01:15:47 E/TimerActivity: 异步有延时消息 31,currentTime=01:15:47 E/TimerActivity: 同步有延时消息 22,currentTime=01:15:48 E/TimerActivity: 异步有延时消息 32,currentTime=01:15:48 E/TimerActivity: 同步有延时消息 23,currentTime=01:15:49 E/TimerActivity: 异步有延时消息 33,currentTime=01:15:49 E/TimerActivity: 同步有延时消息 24,currentTime=01:15:50 E/TimerActivity: 异步有延时消息 34,currentTime=01:15:50

当调用setBarrier()时,屏障消息的插入对异步消息并没有影响,后面的同步消息需要等到屏障消息删除后才会执行:

E/TimerActivity: post sync barrier:1686655132621 E/TimerActivity: 同步无延时消息 0,currentTime=01:18:52 E/TimerActivity: 同步无延时消息 1,currentTime=01:18:52 E/TimerActivity: 同步无延时消息 2,currentTime=01:18:52 E/TimerActivity: 同步无延时消息 3,currentTime=01:18:52 E/TimerActivity: 同步无延时消息 4,currentTime=01:18:52 E/TimerActivity: 异步无延时消息 10,currentTime=01:18:52 E/TimerActivity: 异步无延时消息 11,currentTime=01:18:52 E/TimerActivity: 异步无延时消息 12,currentTime=01:18:52 E/TimerActivity: 异步无延时消息 13,currentTime=01:18:52 E/TimerActivity: 异步无延时消息 14,currentTime=01:18:52 E/TimerActivity: 异步有延时消息 30,currentTime=01:18:53 E/TimerActivity: 异步有延时消息 31,currentTime=01:18:54 E/TimerActivity: 异步有延时消息 32,currentTime=01:18:55 E/TimerActivity: 异步有延时消息 33,currentTime=01:18:56 E/TimerActivity: remove sync barrier:1686655137632 E/TimerActivity: 异步有延时消息 34,currentTime=01:18:57 E/TimerActivity: 同步有延时消息 20,currentTime=01:18:57 E/TimerActivity: 同步有延时消息 21,currentTime=01:18:57 E/TimerActivity: 同步有延时消息 22,currentTime=01:18:57 E/TimerActivity: 同步有延时消息 23,currentTime=01:18:57 E/TimerActivity: 同步有延时消息 24,currentTime=01:18:57