Android 性能优化之 Activity 启动耗时分析

桔妹导读:Activity 的启动速度是很多开发者关心的问题,当页面跳转耗时过长时,App 就会给人一种非常笨重的感觉。在遇到某个页面启动过慢的时候,开发的第一直觉一般是 onCreate 执行速度太慢了,然后在 onCreate 方法前后记录下时间戳计算出耗时。不过有时候即使把 onCreate 方法的耗时优化了,效果仍旧不明显。实际上影响到 Activity 启动速度的原因是多方面的,需要从 Activity 的启动流程入手,才能找到真正问题所在。

0.
目录

1. Activity启动流程

  • ActiivtyA Pause流程

  • ActivityB Launch流程

  • ActivityB Render流程

2. 耗时统计方案

  • 系统耗时统计

  • 三种耗时

    • Pause耗时

    • Launch耗时

    • Render耗时

  • 应用内统计方案

    • Hook Instrumentation

    • Hook Looper-Printer

    • Hook ActivityThread$H

3. 总结

4. 参考

1.
Activity启动流程

如果要给 Activity 的「启动」做一个定义的话,个人觉得应该是:从调用 startActivity到Activity可被操作为止,代表启动成功。所谓的可被操作,是指可接受各种输入事件,比如手势、键盘输入之类的。换个角度来说,也可以看成是主线程处于空闲状态,能执行后续进入的各种Message。

Activity的启动可以分为三个步骤,以ActivityA启动ActivityB为例,三步骤分别为:

  • 以ActivityA调用startActivity,到ActivityA成功pause为止

  • ActivityB成功初始化,到执行完resume为止

  • ActivityB向WSM注册窗口,到第一帧绘制完成为止

Activity启动涉及到App进程与ActivityManagerService(AMS)、WindowManagerService(WMS)的通信,网上关于这个流程的文章很多,这边就不再具体描述了,只列一下关键方法的调用链路。

ActiivtyA Pause流程

当ActivityA使用startActivity方法启动ActivityB时,执行函数链路如下:

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.onPauseActivityManagerNative.getDefault().activityPaused

当 App 请求 AMS 要启动一个新页面的时候,AMS 首先会 pause 掉当前正在显示的 Activity,当然,这个 Activity 可能与请求要开启的 Activity 不在一个进程,比如点击桌面图标启动 App ,当前要暂停的 Activity 就是桌面程序 Launcher。在 onPause 内执行耗时操作是一种很不推荐的做法,从上述调用链路可以看出,如果在 onPause 内执行了耗时操作,会直接影响到 ActivityManagerNative.getDefault().activityPaused() 方法的执行,而这个方法的作用就是通知 AMS,“当前 Activity 已经已经成功暂停,可以启动新 Activity 了”。

ActivityB Launch流程

在AMS接收到App进程对于activityPaused方法的调用后,执行函数链路如下

ActivityManagerService.activityPaused->ActivityStack.activityPausedLocked->ActivityStack.completePauseLocked->ActivityStackSupervisor.resumeFocusedStackTopActivityLocked->ActivityStackSupervisor.resumeFocusedStackTopActivityLocked->ActivityStack.resumeTopActivityUncheckedLocked->ActivityStack.resumeTopActivityInnerLocked->ActivityStackSupervisor.startSpecificActivityLocked-> └1.启动新进程:ActivityManagerService.startProcessLocked 暂不展开 └2.当前进程:ActivityStackSupervisor.realStartActivityLocked->ActivityThread$$ApplicationThread.scheduleLaunchActivity->Activity.handleLaunchActivity-> └Activity.onCreate └Activity.onRestoreInstanceState └handleResumeActivity   └Activity.onStart->   └Activity.onResume->   └WindowManager.addView->

AMS 在经过一系列方法调用后,通知 App 进程正式启动一个 Actviity,注意如果要启动 Activity 所在进程不存在,比如点击桌面图标第一次打开应用,或者 App 本身就是多进程的,要启动的新页面处于另外一个进程,那就需要走到 ActivityManagerService.startProcessLocked 流程,等新进程启动完毕后再通知 AMS,这里不展开。按照正常流程,会依次走过 Activity 生命周期内的 onCreate、onRestoreInstanceState、onStart、onResume 方法,

这一步的耗时基本也可以看成就是这四个方法的耗时,由于这四个方法是同步调用的,所以可以通过以 onCreate 方法为起点, onResume 方法为终点,统计出这一步骤的总耗时。

ActivityB Render流程

在ActivityB执行完onResume方法后,就可以显示该Activity了,调用流程如下:

WindowManager.addView->WindowManagerImpl.addView->ViewRootImpl.setView->ViewRootImpl.requestLayout-> └ViewRootImpl.scheduleTraversals-> └Choreographer.postCallback->WindowManagerSerivce.add

这一步的核心实际上是Choreographer.postCallback,向Choreographer注册了一个回调,当Vsync事件到来时,就会执行下面的回调进行ui的渲染。

ViewRootImpl.doTraversal->ViewRootImpl.performTraversals-> └ViewRootImpl.relayoutWindow └ViewRootImpl.performMeasure └ViewRootImpl.performLayout └ViewRootImpl.performDrawViewRootImpl.reportDrawFinished

这里分别执行了  performMeasure、performLayout、performDraw,实际上就是对应到DecorView 的测量、布局、绘制三个流程。由于 Android 的UI是个树状结构,作为根View的DecorView的测量、布局、绘制,会调用到所有子View相应的方法,因此,这一步的总耗时就是所有子View在测量、布局、绘制中的耗时之和,如果某个子View在这三个方法中如果进行了耗时操作,就会拖慢整个UI的渲染,进而影响Activity第一帧的渲染速度。

2.
耗时统计方案

知道了Actviity启动流程的三个步骤和对应的方法耗时统计方法,那该如何设计一个统计方案呢?在这之前,可以先看看系统提供的耗时统计方法。

系统耗时统计

打开 Android Studio的Logcat ,输入过滤关键字 ActivityManager, 在启动一个 Actviity 后就能看到如下日志:

末尾的+59ms便是启动该Activity的耗时。这个日志是Android系统在AMS端直接输出的,《WMS常见问题一(Activity displayed延迟)》这篇文章分析了系统耗时统计的方法,简单来说,上述日志是通过ActivityRecord.reportLaunchTimeLocked方法打印出来的。

 ActivityRecord.java
private void reportLaunchTimeLocked(final long curTime) { ...... final long thisTime = curTime - displayStartTime; final long totalTime = sta
  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值