Android 无入侵 Activity 耗时统计方案

408 阅读3分钟

前言

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();                         
                    }                     
                });                 
             }             
});

耗时统计节点

image2020-7-6_20-16-53.png