Skip to content

利用消息调度机制优化启动速度和首帧绘制

Notifications You must be signed in to change notification settings

bboylin/MessageScheduler

Repository files navigation

利用消息调度机制优化启动速度和首帧绘制

大型App的启动通常有较多任务需要执行,有时候个别任务可能在一些关键Message之前执行,例如首页Activity的生命周期Message,或者UI绘制的SyncBarrier消息, 从而影响启动的性能表现。

可以通过人为干预消息队列,将需要优先执行的Message提前执行,来达到提升启动速度和首帧耗时的功能。

在Application的onCreate之后会跳转到第一个Activity(闪屏或者首页),那么在Application的onCreate中可以开始hook消息队列,通过Looper的printer获取消息的执行时机,在每个消息执行之后遍历一次消息队列,找到其中启动Activity的message,拷贝之后插入消息队列队首,同时删除原message,从而让启动activity的消息优先执行。

判断是不是启动Activity的消息需要注意兼容9.0前后两种不同实现:

        private var method: Method? = null

        fun isLaunchActivity(msg: Message): Boolean {
            return if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O_MR1) {
                if (msg.what == EXECUTE_TRANSACTION && msg.obj != null) {
                    try {
                        if (null == method) {
                            val clazz =
                                Class.forName("android.app.servertransaction.ClientTransaction")
                            method = clazz.getDeclaredMethod("getCallbacks")
                            method!!.isAccessible = true
                        }
                        val list = method!!.invoke(msg.obj) as List<*>
                        if (list.isNotEmpty()) {
                            val isLaunchActivity =
                                list[0]!!.javaClass.name.endsWith(".LaunchActivityItem")
                            if (isLaunchActivity) {
                                Log.d(TAG, "isLaunchActivity")
                            }
                            return isLaunchActivity
                        }
                    } catch (e: Exception) {
                        Log.e(TAG, "[ActivityThreadHacker isLaunchActivity] %s", e)
                    }
                }
                msg.what == LAUNCH_ACTIVITY
            } else {
                msg.what == LAUNCH_ACTIVITY || msg.what == RELAUNCH_ACTIVITY
            }
        }

而如何将这个消息提到队首主要是对MessageQueue和Message的反射和链表操作(前一个节点的next指向要删除节点的下一个节点), 可以自己看代码,不加赘述。

而对于SyncBarrier,它是UI绘制的触发器,MessageQueue在next方法中会取出当下需要执行的消息,如果遇到SyncBarrier,就会遍历队列,找到第一个异步消息(UI绘制都是异步消息)执行。而SyncBarrier通常是在viewrootimpl的scheduleTraversal中插入到主线程消息队列,然后在执行到doTraversal真正进行UI刷新的时候removeSyncBarrier。因为Activity的UI是在onResume之后才attach到window上的,可以在第一个Activity的onResume中接管消息队列,把队列里的SyncBarrier提到队首,让UI绘制优先执行。

判断SyncBarrier很简单,target为null就是了。而删除原消息可以通过反射removeSyncBarrier方法,传入原SyncBarrier消息的token。

    override fun isHighPriorityMessage(message: Message): Boolean {
        return message.target == null
    }

    override fun moveMessageToFrontOfQueue(preMessage: Message, targetMessage: Message) {
        val newBarrier = Message.obtain(targetMessage)
        removeSyncBarrier(newBarrier.arg1)
        handler.sendMessageAtFrontOfQueue(newBarrier)
        // target置null
        newBarrier.target = null
        Log.d(TAG, "SyncBarrierScheduler move SyncBarrier to Front Of Queue")
    }

    private fun removeSyncBarrier(token: Int) {
        if (removeSyncBarrierMethod == null) {
            removeSyncBarrierMethod =
                MessageQueue::class.java.getDeclaredMethod("removeSyncBarrier", Int::class.java)
            removeSyncBarrierMethod!!.isAccessible = true
        }

        removeSyncBarrierMethod!!.invoke(looper.queue, token)
        Log.d(TAG, "removeSyncBarrier success")
    }

在App模块我通过连续post 10个delay 200ms的消息来模拟启动过程耗时操作,优化之前生命周期和首帧都在这10个msg执行完成之后:

优化之后:

About

利用消息调度机制优化启动速度和首帧绘制

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages