浅谈Activity 启动过程

作者: 平凡小天地 | 来源:发表于2017-03-02 15:40 被阅读0次

引言:页面启动时间怎么计算?

在做启动优化的过程中,在做了一堆的优化工作之后,需要对整个的启动优化效果进行对比和评估,优化之后的启动时间和优化之前对比,真的变短了吗?之前是多少?现在是多少?

首先我们自己 从用户的角度 来定义以下Activity页面的加载时间。我认为页面的加载时间应该是: 从页面开始创建——页面渲染结束,对用户可见并且用户可操作 的时间。

那么,从开发者的角度 ,我们怎么测算这个加载时间呢?起初我以为页面的启动时间 = OnCreate 的时间 + onStart的时间,但通过打日志发现,这个时间往往较短,跟体验到以及监控到的时间不符。另外在onstart时间打出日志的时候,往往页面还并不可见。

onCreate + onStart的时间 Display日志

可以看出 ,onCreate开始到onStart结束,总时间为298 ms,而此时我们过滤系统打出的Displayed日志 ,发现达到了1111 ms,差距这么大! 

那么,Displayed日志,是什么时候打印出来的呢?

页面在onStart之后,800+ ms的时间里面,干了什么工作呢?

要回答这两个问题,我们先从Activity的启动过程说起。

页面启动流程第一步:Activity生命周期

为了了解Activity的启动入口,我在Activity的onResume中设置了一个断点,通过断点查看堆栈调用信息如下:

Activity启动堆栈

可以看到页面的启动从NativeStart 方法的 main() 函数开始,调用到ActivityThread的main() 函数,通过Looper 发送消息调用到 ActivityThread 的 handleLaunchActivity() 方法。

那么的 handleLaunchActivity 里面做些什么呢?可以看到先执行了performLaunchActivity方法,然后执行了handleResumeActivity方法:

ActivityThread 的 handleLaunchActivity 方法

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

ActivityThread 的 performLaunchActivity 方法(1) ActivityThread 的 performLaunchActivity 方法(2)

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

更加详细的启动过程见下图:

Activity启动流程

onresume执行之后,会开始进行Activity的绘制,将Activity的dectorView attach 到window上,此后开始界面的渲染绘制。

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

inflate过程源代码

因此,在Activity的onresume执行完毕之后,Activity的界面UI在刚刚开始渲染,整个的页面对用户并不可见。这就是为什么onCreate~onStart 或者 onResume 整体的时间往往远远小于页面加载的时间(页面对用户可见并可操作)。

页面启动流程第二步:页面渲染

我们认为Activity启动结束的时间应该是这个页面绘制完毕的时间点,前文说到在setContentView中仅仅是完成了布局文件的实例化,并没有开始渲染,真正渲染的部分是在Activity 的onResume之后,那么view的绘制具体是在什么时候被调用,什么时候执行的呢?

首先通过日志看下现象:

activity的生命周期与view的绘制

从日志可以看出,view的绘制发生在activity attachToWindow之后。

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

view 开始绘制的入口

如上,view的绘制包括三个步骤:

onMeasure:测量大小

onLayout:计算摆放的位置

onDraw:绘制过程

上面的三个步骤均是通过递归的形式逐步完成的,从父View开始,逐级向子view传递,最终完整整个view tree的绘制。view tree的基本结构如下图:

view tree

回到主题:启动时间的计算

现在,我们来回答文章开头提出的两个问题:

那么,Displayed日志,是什么时候打印出来的呢?

页面在onStart之后,800+ ms的时间里面,干了什么工作呢?

问题1:Displayed日志,是什么时候打印出来的呢?

以上,view draw过程结束后,一个完整的对用户可见的Activity总算是好了。理论上说,所有的view绘制结束,并且将网络请求回来的数据作用于Activity的界面,改变UI,实现我们期待的展示效果,就算是页面load完成了。

但是事实上系统并不能很准确地知道你的各个view是否已经渲染完成并且展示了正确的数据。因此系统采用了一个比较偷懒的方式,尽管不能完全准确,但可以作为评估Activity启动耗时的反映。

Displayed日志的打印是在ActivityRecord中打印出来的。具体的源码大家可以自行去查看。

在Activity中有一个方法,reportFullyDrawn,如下。

reportFullyDrawn

这个方法可以由用户在activity启动真正结束的地方调用,这样就可以获得更加准确的启动耗时,Displayed日志就回打印出真正结束的时间。

如果用户没有调用,系统默认采用的是window首次绘制的时间,如上面注释中所表达。

另外,为了实现更加准确的自动化无侵入的耗时监测,也可以通过Choreographer类,去坚挺的每一帧的绘制,计算已经完成绘制的view占Activity总view的数量的比例,如果超过了一定的比例,就认为绘制已经完成,也是可取的方法。

问题2: 页面在onStart之后,800+ ms的时间里面,干了什么工作呢?

这个问题大家应该自然而然明白了吧,前面说到,Activity的启动过程经过了onCreate,onStart,onResume,但是这个过程还并没有开始View的绘制,所以,onResume之后的一大块时间,大多是完成页面View的measure,layout和draw去啦。

感谢看到这里,希望对你有帮助~~

参考文章:

Android Activity启动过程

Android Activity.startActivity流程简介

View的生命周期方法和Activity生命周期方法关系

Android应用层View绘制流程与源码分析

相关文章

网友评论

    本文标题:浅谈Activity 启动过程

    本文链接:https://www.haomeiwen.com/subject/vsfmwttx.html