前言
app首帧的定义主要有两种解释。
- app启动可见时的第一帧,一般是logo展示页,没有做logo展示页则是白屏页;
- 人为定义的app的第一个页面的第一帧;
这里第一种解释没有太大的意义,因为此时一般都被Application的初始化给阻塞着,所以咱们主要讨论第二种解释。
作用
为啥要定义一个首帧呢?
保证页面对用户可见可交互的同时,处理一些可以延迟初始化的逻辑,减轻Application的初始化工作量,提升app启动速度,同时可懒加载一些业务,优化用户体验。
定义
通常一个app的第一个可交互的页面,是首页,我们就拿MainActivity来讲吧。
什么是MainActivity的第一帧?
我们使用viewTreeObserver.addOnGlobalLayoutListener
监听页面layout完成,但是layout完成不代表绘制完成。我们可以看ViewRootImpl的源码:
//performTraversals()方法的简化版
void performTraversals() {
//......
performMeasure();
//......
performLayout();
//......
performDraw();
}
我们可以知道layout完成之后马上就会draw,所以使用在onGlobalLayout
中使用Handler post一次即可保证第一帧页面可见。
完整示例:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val viewTreeObserver = window.decorView.viewTreeObserver
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
Handler(Looper.getMainLooper()).post {
println("首帧")
viewTreeObserver.removeOnGlobalLayoutListener(this)
}
}
})
}
总结
首帧之后到底可以做那些事儿?
- 网络异步IO,将app启动时非必要及时加载的网络请求放在首帧以后。
一般app启动过程中有许多埋点,我们可以把这些埋点延后上报,减少app启动时的线程消耗。
在减少网络请求的时候,还可以减少本地IO,减轻磁盘IO负载。 - 布局异步初始化,删掉接下来的页面xml解析时间;
- 非启动必须库的异步加载,需要注意的时,这种库使用一定要做兜底操作;
- 一些业务的懒加载;