前言
Activity承载页面中元素的容器,Activity启动时间过长 ,表现为点击按钮后在当前页面停留很长时间才跳转到下个页面,这种情况频繁出现,App给人非常笨重的感觉,对于使用的用户来说,最直观的体验是卡顿,而对于开发者来说,第一直觉一般就是onCreate处理逻辑过多,执行速度太慢了,然后把Activity onCreate方法前后记录下时间戳计算出耗时统计上来,实际上影响到启动速度的原因是多方面的,针对开发者比较关心Activity的问题,简单阐述下经过改良,现有APM SDK对于启动耗时这一块是怎么做的。
Activity启动流程分析
Activity的启动可以分为三个步骤,以ActivityA启动ActivityB为例
1.ActivityA调用startActivity,到ActivityA执行pause方法,从可见到不可见,暂停当前Activity 2.ActivityB成功初始化,执行onCreate、onRestoreInstanceState、onStart,到执行onResume为止 3.ActivityB向WSM注册窗口,到第一帧绘制完成为止
总结为Activity的启动分为Pause、Launch和Render三个步骤,在启动一个新Activity时,会先Pause前一个正在显示的Activity,再加载新Activity,然后开始渲染,直到第一帧渲染成功,Activity才算启动完毕
Pause流程
启动新Activity前,AMS首先会pause掉当前正在显示的Activity,如果在onPause内执行了耗时操作,会直接影响到ActivityManagerNative.getDefault().activityPaused()方法的执行,导致新页面启动的滞后。
ActivityA.startActivity-> Instrumentation.execStartActivity->
ActivityManagerNative.getDefault.startActivity->
ActivityManagerService.startActivityAsUser-> ActivityStarter.startActivityMayWait->
ActivityStarter.startActivityLocked-> ActivityStarter.startActivityUnchecked->
ActivityStackSupervisor.resumeFocusedStackTopActivityLocked->
ActivityStack.resumeTopActivityUncheckedLocked->
ActivityStack.resumeTopActivityInnerLocked-> ActivityStack.startPausingLocked->
ActivityThread$$ApplicationThread.schedulePauseActivity->
ActivityThread.handlePauseActivity->
└ActivityA.onPause
ActivityManagerNative.getDefault().activityPaused
ActivityB 启动流程
按照正常流程,会依次走过Activity生命周期内的onCreate、onRestoreInstanceState、onStart、onResume方法,这一步的耗时基本也可以看成就是这四个方法的耗时,由于这四个方法是同步调用的,所以可以通过以onCreate方法为起点,onResume方法为终点,统计出这一步骤的总耗时。
Render流程
ActivityB执行完onResume方法后,就可以显示该Activity了
WindowManager.addView-> WindowManagerImpl.addView-> ViewRootImpl.setView->
ViewRootImpl.requestLayout->
└ViewRootImpl.scheduleTraversals->
└Choreographer.postCallback-> WindowManagerSerivce.add ViewRootImpl.doTraversal-> ViewRootImpl.performTraversals->
└ViewRootImpl.relayoutWindow
└ViewRootImpl.performMeasure
└ViewRootImpl.performLayout
└ViewRootImpl.performDraw ViewRootImpl.reportDrawFinished
performMeasure、performLayout、performDraw,实际上就是对应到DecorView的测量、布局、绘制三个流程,render总耗时就是所有View在测量、布局、绘制中的耗时之和。
pageLoad耗时分析
计算上一页面onPause方法的耗时+ActivityB Launch耗时
计算公式 onPause、onCreate、onRestoreInstanceState、onStart、onResume四个函数的耗时相加得出
onCreate一般是最重的那个方法,有个耗时大户是LayoutInfalter.infalte方法
render耗时
通过getWindow().getDecorView()获取到DecorView后,调用post方法,此时由于DecorView的attachInfo为空,会将这个Runnable放置runQueue中。runQueue内的任务会在ViewRootImpl.performTraversals的开始阶段被依次取出执行,我们知道这个方法内会执行到DecorView的测量、布局、绘制操作,不过runQueue的执行顺序会在这之前,所以需要再进行一次post操作。第二次的post操作可以继续用DecorView().post或者其普通Handler.post(),并无影响。此时mAttachInfo已不为空,DecorView().post也是调用了mHandler.post()。
activity.getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
activity.getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
renderEnd();
}
});
}
});