FullyDrawnReporter—一个官方冷启动耗时统计小工具

今天给大家带来官方库activityx.activity库提供的另一个小工具:FullyDrawnReporter,它可以帮助我们实现应用冷启动的耗时统计,并且可定制化。至于怎么个定制化,且往下看。

本篇文章基于SDK34和activityx.acitivity-1.8.0版本分析:

groovy 复制代码
dependencies {
    implementation "androidx.activity:activity-ktx:1.8.0"
}

应用冷启动耗时常见查看方式

线下可以直接通过logcat过滤Displayed关键字即可,比如我当前冷启动一个应用:

这个Displayed打印的冷启动时间又称TTID,包括下面几个流程(来源于官方):

  • 启动进程。
  • 初始化对象。
  • 创建并初始化 activity。
  • 膨胀布局。
  • 首次绘制应用。

请注意,这个时间统计一直到应用绘制的第一帧,而我们应用界面的完全显示很多场景下是依赖网络请求或者数据库读写的,然后再异步加载更新UI完整显示界面实际内容,所以这个Displayed过滤的显示时间并不代表当前界面已经完整显示了

如果我们想要统计从应用冷启动到界面完整显示的这段时间,我们可以借助官方的APIActivity#reportFullyDrawn()实现,只需要在你认为该结束冷启动的地方调用这个API即可。

比如我们想要在Dialog显示的时候才算应用冷启动结束,可以这样写:

kotlin 复制代码
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val customComponentDialog = CustomComponentDialog(this)
        customComponentDialog.setOnShowListener {
            reportFullyDrawn()
        }
        customComponentDialog.show()
}

通过logcat过滤关键字Fully drawn

或者在我们项目中,如果主界面网络请求结束才算冷启动结束,那就再请求响应之后的地方调用Activity#reportFullyDrawn()即可。

但是不知道大家是否发现一个问题,如果我们主界面的不同的UI部分依赖不同的网络请求,必须等到所有网络请求都结束才算冷启动结束,那我们该怎么统计冷启动耗时呢?

假设当前存在A、B、C请求,由于网络请求响应的速度不同,可能这次冷启动A请求响应最后到达,下次冷启动C请求最后到达,但我们只有一个Activity#reportFullyDrawn(),是加载A请求响应还是加在C请求响应之后呢?所以这种场景下,光靠一个 Activity#reportFullyDrawn() 定制冷启动耗时无法实现

官方肯定也考虑到了这个情况,所以FullyDrawnReporter应运而生了。

FullyDrawnReporter应运而生

先来张官方介绍:

一句话总结,FullyDrawnReporter就是解决上面描述的问题的,它运行监听多个事件,等到多个事件都执行完毕之后,才去调用Activity#reportFullyDrawn()统计冷启动时间。

咱们先看下基本使用,再分析原理;

1. 基本使用

假设当前存在A、B、C三个请求:

kotlin 复制代码
    override fun onResume() {
        super.onResume()
        //发起A请求
        lifecycleScope.launch {
            //获取A响应结果
            val result1 = request1()
            Log.i("", "onResume: result1 = $result1")
        }

        //发起B请求
        lifecycleScope.launch {
            //获取B响应结果
            val result2 = request2()
            Log.i("", "onResume: result2 = $result2")
        }

        //发起C请求
        lifecycleScope.launch {
            //获取C响应结果
            val result3 = request3()
            Log.i("", "onResume: result3 = $result3")
        }
    }

    suspend fun request1(): String {
        //模拟A请求请求耗时
        delay(2000)
        return "result-A"
    }

    suspend fun request2(): String {
        delay(1000)
        return "result-B"
    }

    suspend fun request3(): String {
        delay(1000)
        return "result-C"
    }

等到这三个请求全部结束后再统计冷启动时间,增加如下逻辑:

kotlin 复制代码
    override fun onResume() {
        super.onResume()
        //发起A请求
        lifecycleScope.launch {
            fullyDrawnReporter.addReporter()
            //获取A响应结果
            val result1 = request1()
            fullyDrawnReporter.removeReporter()
            Log.i("", "onResume: result1 = $result1")
        }

        //发起B请求
        lifecycleScope.launch {
            //获取B响应结果
            fullyDrawnReporter.addReporter()
            val result2 = request2()
            fullyDrawnReporter.removeReporter()
            Log.i("", "onResume: result2 = $result2")
        }

        //发起C请求
        lifecycleScope.launch {
            //获取C响应结果
            fullyDrawnReporter.addReporter()
            val result3 = request3()
            fullyDrawnReporter.removeReporter()
            Log.i("", "onResume: result3 = $result3")
        }
    }

在每个请求发起前调用API fullyDrawnReporter.addReporter(),请求响应后调用API:fullyDrawnReporter.removeReporter()即可,然后看下输出:

如果想要监听上面所有请求结束后调用Activity#reportFullyDrawn()的事件点,可以通过APIaddOnReportDrawnListener{}添加监听器::

kotlin 复制代码
        fullyDrawnReporter.addOnReportDrawnListener {
            Log.i("", "addOnReportDrawnListener")
        }

输出结果:

该监听器支持添加多个,且不会发生覆盖。

除此之外,官方还给我们提供了协程APIreportWhenComplete{}封装:等待方法体中的逻辑执行完毕之后才去调用Activity.reportFullyDrawn()统计冷启动耗时:

本质上也是借助上面的addReporter()和removeReporter()方法实现,使用如下:

kotlin 复制代码
    suspend fun belongToColdTime() {
        fullyDrawnReporter.reportWhenComplete {
            //...执行某段逻辑
        }
    }

2. 机制解析

这里我们来详细分析下FullyDrawnReporter的实现机制:

ComponentActivity提供了FullyDrawnReporter类对象:

  • FullyDrawnReporter构造参数executor表示另一个参数reportFullyDrawn在哪个线程环境中执行;
  • reportFullyDrawn该构造参数表示所有事件全部执行后待执行的逻辑,这里ComponentActivity直接指定了执行reportFullyDrawn()结束冷启动统计时间;

然后当我们调用addReporter()方法时:

先判断reportedFullyDrawn变量,当前是否已经结束了冷启动统计,如果为true表示已经结束直接返回即可。

如果还没结束冷启动统计, reporterCount属性+1 ,拿上面的例子举例,发送三个请求,相当于reporterCount增加到3,表示当前有3个待响应的事件;

继续看下removeReporter()方法:

还是先判断是否结束冷启动统计了,如果没结束,则将reporterCount属性-1 ,拿上面例子举例,每个请求响应后,则将待响应的事件数量-1,然后调用方法postWhenReportersAreDone()

检测当前reporterCount即待响应的事件数量是否为0,如果为0表示,所有监听的事件比如上面的A、B、C三个网络请求已经全部执行完毕了,可以执行构造参数reportFullyDrawn回调了,这里就是执行ComponentActivity注册的Activity#reportFullyDrawn()监听。

最后再看下fullyDrawnReported()方法:

在该方法中就会将reportedFullyDrawn变量置为true,表示已结束冷启动统计时间,后续再调用addReporter()/removeReporter()就没啥意义了。

然后onReportCallbacks是个集合,就是通过前面提到的APIaddOnReportDrawnListener注册的监听,依次执行,这个流程结束。

扩展

ComponentActivity中,FullyDrawnReporter的作用是用来监听所有的事件全部执行后,结束冷启动统计时间并上报的,这是由于在 FullyDrawnReporter构造函数指定了callback为 reporterFullyDrawn() 决定的

换个角度思考,如果我们存在其他场景,需要依赖几个其他逻辑执行完毕之后再执行我们的逻辑,那我们就可以再FullyDrawnReporter构造函数指定callback为我们的逻辑,然后再依赖的逻辑执行前后增加addReporter()/removeReporter()调用,这不就简简单单实现了一个类似于CountDownLatch的功能,只不过CountDownLatchawait()方法是同步阻塞的,而我们的FullyDrawnReporter是通过回调监听。

总之来说, FullyDrawnReporter非常适合这种依赖执行的场景,而且很多方法还是加了代码块锁的,也不用担心并发场景

希望本篇文章能对你有所帮助,感谢阅读。

历史文章

一文洞彻:Application为啥不能作为Dialog的context?

子线程刷UI->Barrier屏障->主线程装死->应用GG?太难了

ComponentDialog---集各种特性于一体的大成Dialog

相关推荐
泥嚎泥嚎1 天前
【Android】给App添加启动画面——SplashScreen
android·java
全栈派森1 天前
初见 Dart:这门新语言如何让你的 App「动」起来?
android·flutter·ios
q***98521 天前
图文详述:MySQL的下载、安装、配置、使用
android·mysql·adb
恋猫de小郭1 天前
Dart 3.10 发布,快来看有什么更新吧
android·前端·flutter
恋猫de小郭1 天前
Flutter 3.38 发布,快来看看有什么更新吧
android·前端·flutter
百锦再1 天前
第11章 泛型、trait与生命周期
android·网络·人工智能·python·golang·rust·go
会跑的兔子1 天前
Android 16 Kotlin协程 第二部分
android·windows·kotlin
键来大师1 天前
Android15 RK3588 修改默认不锁屏不休眠
android·java·framework·rk3588
江上清风山间明月1 天前
Android 系统超级实用的分析调试命令
android·内存·调试·dumpsys
百锦再1 天前
第12章 测试编写
android·java·开发语言·python·rust·go·erlang