引言:页面启动时间怎么计算?
在做启动优化的过程中,在做了一堆的优化工作之后,需要对整个的启动优化效果进行对比和评估,优化之后的启动时间和优化之前对比,真的变短了吗?之前是多少?现在是多少?
首先我们自己 从用户的角度 来定义以下Activity页面的加载时间。我认为页面的加载时间应该是: 从页面开始创建——页面渲染结束,对用户可见并且用户可操作 的时间。
那么,从开发者的角度 ,我们怎么测算这个加载时间呢?起初我以为页面的启动时间 = OnCreate 的时间 + onStart的时间,但通过打日志发现,这个时间往往较短,跟体验到以及监控到的时间不符。另外在onstart时间打出日志的时候,往往页面还并不可见。


可以看出 ,onCreate开始到onStart结束,总时间为298 ms,而此时我们过滤系统打出的Displayed日志 ,发现达到了1111 ms,差距这么大!
那么,Displayed日志,是什么时候打印出来的呢?
页面在onStart之后,800+ ms的时间里面,干了什么工作呢?
要回答这两个问题,我们先从Activity的启动过程说起。
页面启动流程第一步:Activity生命周期
为了了解Activity的启动入口,我在Activity的onResume中设置了一个断点,通过断点查看堆栈调用信息如下:

可以看到页面的启动从NativeStart 方法的 main() 函数开始,调用到ActivityThread的main() 函数,通过Looper 发送消息调用到 ActivityThread 的 handleLaunchActivity() 方法。
那么的 handleLaunchActivity 里面做些什么呢?可以看到先执行了performLaunchActivity方法,然后执行了handleResumeActivity方法:

performLaunchActivity 方法中,执行了Activity对象的创建,oncreate和onstart:


所以总结下来的,Activity的启动过程就一定会依次执行Activity的生命周期中的onCreate(),onStart()和onResume会依次执行,不可打断。
更加详细的启动过程见下图:

onresume执行之后,会开始进行Activity的绘制,将Activity的dectorView attach 到window上,此后开始界面的渲染绘制。
因此,根据我们一般的开发习惯,在oncreate中执行setContentView,调用了xml的inflate方法,并不意味着布局文件已经渲染完毕,LayoutInflater 进行 inflate的过程,仅仅是将布局文件中定义的 view 元素通过调用createViewFromTag方法实例化的过程。setContentView实现了布局文件到java对象的转换,却并没有开始渲染和绘制。

因此,在Activity的onresume执行完毕之后,Activity的界面UI在刚刚开始渲染,整个的页面对用户并不可见。这就是为什么onCreate~onStart 或者 onResume 整体的时间往往远远小于页面加载的时间(页面对用户可见并可操作)。
页面启动流程第二步:页面渲染
我们认为Activity启动结束的时间应该是这个页面绘制完毕的时间点,前文说到在setContentView中仅仅是完成了布局文件的实例化,并没有开始渲染,真正渲染的部分是在Activity 的onResume之后,那么view的绘制具体是在什么时候被调用,什么时候执行的呢?
首先通过日志看下现象:

从日志可以看出,view的绘制发生在activity attachToWindow之后。
Activity的hadleResumeActivity的部分代码如下,可以看到在执行完performResumeActivity(包括了Activity的onResume回调)之后,执行了WindowManagerImpl的addView方法,继续追踪,执行到了WindowManagerGlobal 的addView方法。之后的流程便如下面的图所示,通过requestLayout依次调用了ViewRootImpl 的 scheduleTraversals-》TraversalRunnable-》doTraversal-》performTraversals。在performTraversals中就包括了performMeasure,performLayout,performDraw的过程。


如上,view的绘制包括三个步骤:
onMeasure:测量大小
onLayout:计算摆放的位置
onDraw:绘制过程
上面的三个步骤均是通过递归的形式逐步完成的,从父View开始,逐级向子view传递,最终完整整个view tree的绘制。view tree的基本结构如下图:

回到主题:启动时间的计算
现在,我们来回答文章开头提出的两个问题:
那么,Displayed日志,是什么时候打印出来的呢?
页面在onStart之后,800+ ms的时间里面,干了什么工作呢?
问题1:Displayed日志,是什么时候打印出来的呢?
以上,view draw过程结束后,一个完整的对用户可见的Activity总算是好了。理论上说,所有的view绘制结束,并且将网络请求回来的数据作用于Activity的界面,改变UI,实现我们期待的展示效果,就算是页面load完成了。
但是事实上系统并不能很准确地知道你的各个view是否已经渲染完成并且展示了正确的数据。因此系统采用了一个比较偷懒的方式,尽管不能完全准确,但可以作为评估Activity启动耗时的反映。
Displayed日志的打印是在ActivityRecord中打印出来的。具体的源码大家可以自行去查看。
在Activity中有一个方法,reportFullyDrawn,如下。

这个方法可以由用户在activity启动真正结束的地方调用,这样就可以获得更加准确的启动耗时,Displayed日志就回打印出真正结束的时间。
如果用户没有调用,系统默认采用的是window首次绘制的时间,如上面注释中所表达。
另外,为了实现更加准确的自动化无侵入的耗时监测,也可以通过Choreographer类,去坚挺的每一帧的绘制,计算已经完成绘制的view占Activity总view的数量的比例,如果超过了一定的比例,就认为绘制已经完成,也是可取的方法。
问题2: 页面在onStart之后,800+ ms的时间里面,干了什么工作呢?
这个问题大家应该自然而然明白了吧,前面说到,Activity的启动过程经过了onCreate,onStart,onResume,但是这个过程还并没有开始View的绘制,所以,onResume之后的一大块时间,大多是完成页面View的measure,layout和draw去啦。
感谢看到这里,希望对你有帮助~~
网友评论