ActivityManagerService解读之Activity启动时间闲聊--优雅的优化我们应用的启动时间

ActivityManagerService解读之Activity启动时间闲聊--Android Framework层时间计算介绍一文从Android Framework角度杂谈了一波应用启动时系统各种时间计算的整个过程。想要获取我们开发的应用启动的详细时间,直接使用Android系统提供的“adb shell am start -W -S”命令,如下所示,这个命名会很优雅的输出你的应用启动过程的整个时间

adb shell am start -W -S com.android.settings/.Settings
Stopping: com.android.settings
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.android.settings/.Settings }
Status: ok
Activity: com.android.settings/.Settings
ThisTime: 780
TotalTime: 780
WaitTime: 875
Complete

一般我们更多的关注ThisTime,当然TotalTime和WaitTime也很重要,复杂场景下的Activity启动需要用到。我们在ActivityManagerService解读之Activity启动时间闲聊--Android Framework层时间计算介绍中的“系统是如何计算应用启动的时间的?”章节做过介绍。上一篇文章我们侧重点在于介绍Android系统计算应用启动时间的过程,并没有提及如何去优化我们的应用启动时间。本文将来点实际需要的,优雅的优化我们应用的启动时间。本文内容首先将总结性介绍一下上一篇文章的内容,然后并从各种优化工具入手来介绍优化我们应用启动时间,仅提供一些优化思路,并未深入详细的讲解细节上,

前文回顾

abm

通过前文的介绍我们可以将应用的时间分为了三个时间段A,B和C,并且我们也介绍了了各个时间点所对应的开始Log,这里就不在过多的赘述。我们来看下上图,一般情况下ThisTime和TotalTime相等,上图显示的是含有NoDisplay的Activity启动时的情况。根据不同的行为,我们一般将Activity的启动分为三种情况Cold,Hot和Warm。Cold是指带有创建进程的启动,Hot是指当用户按了Home键退出到后台再次启动的情况,Warm是在Cold的基础上不需要创建进程,正常走Activity生命周期onCreate->onResume。正常情况我们都是以Cold启动方式来计算应用的启动时间。从上图我们可以看出,我们如果要优化应用的启动时间,我们应该更可能的减少TotalTime和ThisTime。而结合ActivityManagerService解读之Activity启动时间闲聊--Android Framework层时间计算介绍一文的介绍,TotalTime和ThisTime是对应的启动三个时间段的B和C。

本文测试用例-说明

MainActivity在onCreate方法中启动Main2Activity并且将自己finish掉,分别在MyApplication,MainActivity,Main2Activity组件各个生命周期中使用了Thread.sleep(1000ms)来模拟耗时操作。同时我们为MainActivity配置了自己的进程".abm"。接下来开始我们的分析。

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:name=".MyApplication"
    android:roundIcon="@mipmap/ic_launcher_round">

<activity
    android:name=".MainActivity"
    android:process=".abm"
    android:launchMode="standard">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

优雅分析姿势一----Log入手

我们先从ActivityManagerService解读之Activity启动初探一文中拿来一段对应我们B和C时间段的启动堆栈:(具体信息请参阅前文介绍)

SystemServer进程
ActivityManagerService.java
activityPaused(token);
  ActivityStack.java
  activityPausedLocked(token, false);
    completePauseLocked(true /* resumeNext */, null /* resumingActivity */);
      ActivityStackSupervisor.java
      resumeFocusedStackTopActivityLocked(topStack, prev, null);
        ActivityStack.java
        resumeTopActivityUncheckedLocked(target, targetOptions);
          ActivityStackSupervisor.java
          startSpecificActivityLocked(next, true, true);
            ActivityManagerService.java
            startProcessLocked(r.processName, r.info.applicationInfo, true, 0,"activity", r.intent.getComponent(), false, false, true,(null != r.launchedFromPackage ? r.launchedFromPackage : "NA"));
 
QQ进程
一些列调用到ActivityThread.java的main方法,我们称之为应用程序的入口
attach(false, startSeq);
attachApplication(mAppThread, startSeq);---Binder IPC--->attachApplicationLocked
              
SystemServer进程
ActivityManagerService.java
attachApplicationLocked(thread, callingPid, callingUid, startSeq);//am_proc_bound
  bindApplication(processName, appInfo, providers, null, profilerInfo,null, null, null, testMode,mBinderTransactionTrackingEnabled, enableTrackAllocation,isRestrictedBackupMode || !normalMode, app.persistent,new Configuration(getGlobalConfiguration()), app.compat,getCommonServicesLocked(app.isolated),mCoreSettingsObserver.getCoreSettingsLocked(),buildSerial, isAutofillCompatEnabled);//触发应用Application的创建操作
  ActivityStackSupervisor.java
  attachApplicationLocked 
    realStartActivityLocked(activity, app,top == activity /* andResume */, true /* checkConfig */)//am_restart_activity
      ClientLifecycleManager.java
        scheduleTransaction(clientTransaction);
          ClientTransaction.java
            schedule();---Binder IPC--->scheduleTransaction
 
QQ进程
IApplicationThread.java
scheduleTransaction(this);
  ActivityThread.java
    scheduleTransaction(transaction);
      sendMessage(EXECUTE_TRANSACTION)->handleMessage
        TransactionExecutor.java
        execute(transaction);
          executeCallbacks(transaction);
            LaunchActivityItem.java
            execute(mTransactionHandler, token, mPendingActions);
              ActivityThread.java
              handleLaunchActivity(r, pendingActions, null /* customIntent */);
          executeLifecycleState(transaction);
            ResumeActivityItem.java
            execute(mTransactionHandler, token, mPendingActions);
              ActivityThread.java
              handleResumeActivity(token, true /* finalStateRequest */, mIsForward,"RESUME_ACTIVITY");
            postExecute(mTransactionHandler, token, mPendingActions);---Binder IPC--->activityResumed
 
SystemServer进程
ActivityManagerService.java
activityResumed(token);
原文:https://blog.csdn.net/abm1993/article/details/82773652 
版权声明:本文为博主原创文章,转载请附上博文链接!

我们再来有一段启动测试Log(建议使用命令:adb logcat -b all | grep "am_\|ActivityManager:"):

/*startTime*/
01-01 20:22:44.171 28907  3552 I ActivityManager: START u0 {flg=0x10000000 cmp=com.example.hoperun.myapplication/.MainActivity} from uid 0
01-01 20:22:44.189 28907  3552 I am_focused_stack: [0,1,0,reuseOrNewTask]
01-01 20:22:44.192 28907  3552 I am_create_task: [0,145]
01-01 20:22:44.192 28907  3552 I am_create_activity: [0,89434600,145,com.example.hoperun.myapplication/.MainActivity,NULL,NULL,NULL,268435456]
01-01 20:22:44.202 28907  3552 I am_pause_activity: [0,160659114,com.android.launcher3/.Launcher]
01-01 20:22:44.204  2757  2757 I am_on_paused_called: [0,com.android.launcher3.Launcher,handlePauseActivity]//如果前一个ActivityonPause耗时怎么办?没关系Android系统设置了500ms的timeout时间,超过timeout系统继续启动操作。
/*mLaunchStartTime*/
01-01 20:22:44.251 28907 29100 I am_proc_start: [0,3593,10066,.abm,activity,com.example.hoperun.myapplication/.MainActivity]
01-01 20:22:44.252 28907 29100 I ActivityManager: Start proc 3593:.abm/u0a66 for activity com.example.hoperun.myapplication/.MainActivity
01-01 20:22:44.381 28907  3552 I am_proc_bound: [0,3593,.abm]//如果我们应用采用了多进程,这也会成为一个耗时点
01-01 20:22:44.408 28907  3552 I am_restart_activity: [0,89434600,145,com.example.hoperun.myapplication/.MainActivity]
01-01 20:22:44.418 28907  3552 I am_set_resumed_activity: [0,com.example.hoperun.myapplication/.MainActivity,minimalResumeActivityLocked]
01-01 20:22:44.421 28907  3552 I ActivityManager: screenStatusRequestTag =0
01-01 20:22:44.496  3593  3593 D ABM#MyApplication: onCreate//如果我们应用复写了自己的Application,Application的创建也是一个耗时点,如果应用使用了多进程,我们的复写的Application还是重复创建多次,这也是一个耗时点
01-01 20:22:44.496  3593  3593 D ABM#MyApplication: sleep....
01-01 20:22:45.503  3593  3593 D ABM#MyApplication: wake.
01-01 20:22:45.549  3593  3593 D ABM#MainActivity: onCreate//MainActivity的onCreate耗时点
01-01 20:22:45.549  3593  3593 D ABM#MainActivity: sleep....
01-01 20:22:46.549  3593  3593 D ABM#MainActivity: wake.
01-01 20:22:46.556 28907  3552 I ActivityManager: START u0 {cmp=com.example.hoperun.myapplication/.Main2Activity} from uid 10066
01-01 20:22:46.562 28907  3552 I am_create_activity: [0,24308867,145,com.example.hoperun.myapplication/.Main2Activity,NULL,NULL,NULL,0]
01-01 20:22:46.566 28907  3552 I am_pause_activity: [0,89434600,com.example.hoperun.myapplication/.MainActivity]
01-01 20:22:46.583 28907  3552 I am_finish_activity: [0,89434600,145,com.example.hoperun.myapplication/.MainActivity,app-request]
01-01 20:22:46.796 28907  3552 I am_proc_start: [0,3610,10066,com.example.hoperun.myapplication,activity,com.example.hoperun.myapplication/.Main2Activity]
01-01 20:22:46.797 28907  3552 I ActivityManager: Start proc 3610:com.example.hoperun.myapplication/u0a66 for activity com.example.hoperun.myapplication/.Main2Activity
01-01 20:22:46.922 28907  3552 I am_proc_bound: [0,3610,com.example.hoperun.myapplication]
01-01 20:22:46.949 28907  3552 I am_restart_activity: [0,24308867,145,com.example.hoperun.myapplication/.Main2Activity]
/*displayStartTime*/
01-01 20:22:46.954 28907  3552 I am_set_resumed_activity: [0,com.example.hoperun.myapplication/.Main2Activity,minimalResumeActivityLocked]
01-01 20:22:46.955 28907  3552 I ActivityManager: screenStatusRequestTag =0
01-01 20:22:47.027  3610  3610 D ABM#MyApplication: onCreate//对于多进程的应用此时复写的MyApplication由重新被创建一次,耗时点
01-01 20:22:47.027  3610  3610 D ABM#MyApplication: sleep....
01-01 20:22:48.028  3610  3610 D ABM#MyApplication: wake.
01-01 20:22:48.068  3610  3610 D ABM#Main2Activity: onCreate//Main2Activity的onCreate耗时点
01-01 20:22:48.294  3610  3610 D ABM#Main2Activity: onCreate
01-01 20:22:48.294  3610  3610 D ABM#Main2Activity: sleep....
01-01 20:22:49.294  3610  3610 D ABM#Main2Activity: wake.
01-01 20:22:49.297  3610  3610 D ABM#Main2Activity: onStart//Main2Activity的onStart耗时点
01-01 20:22:49.298  3610  3610 D ABM#Main2Activity: sleep....
01-01 20:22:50.298  3610  3610 D ABM#Main2Activity: wake.
01-01 20:22:50.317  3610  3610 D ABM#Main2Activity: onResume//Main2Activity的onResume耗时点,onResume耗时主要用于View绘制,这里需要注意自身应用的布局是否可优化
01-01 20:22:50.318  3610  3610 D ABM#Main2Activity: sleep....
01-01 20:22:51.318  3610  3610 D ABM#Main2Activity: wake.
01-01 20:22:51.319  3610  3610 D ABM#Main2Activity: onPostResume
01-01 20:22:51.320  3610  3610 I am_on_resume_called: [0,com.example.hoperun.myapplication.Main2Activity,LAUNCH_ACTIVITY]
/*endTime*/
01-01 20:22:51.717 28907 28929 I am_activity_launch_time: [0,24308867,com.example.hoperun.myapplication/.Main2Activity,4929,7476]
01-01 20:22:51.717 28907 28929 I ActivityManager: Displayed com.example.hoperun.myapplication/.Main2Activity: +4s929ms (total +7s476ms)
01-01 20:22:51.807 28907 28922 I am_destroy_activity: [0,89434600,145,com.example.hoperun.myapplication/.MainActivity,finish-imm]
01-01 20:22:51.814 28907 28922 I am_stop_activity: [0,160659114,com.android.launcher3/.Launcher]
01-01 20:22:51.822  2757  2757 I am_on_stop_called: [0,com.android.launcher3.Launcher,handleStopActivity]
01-01 20:22:51.825  3593  3593 D ABM#MainActivity: onDestroy

在测试应用中,我直接暴力使用了Thread的sleep方法来模拟我们应用存在的耗时点,主要为了让大家更加清晰的观察到几个我们平时优化启动的一些耗时点。大家可以自行参考去优化自己的应用。

优雅分析姿势二----TraceView分析具体方法耗时

打开Android的Device Monitor工具选择你的应用进程然后点击Starter Method Profile,最后执行去启动你的应用,然后点Stop,Device Monitor会自动生成一份以.trace结尾的文件如下图所示:

abm

TraceView中的列名介绍如下:

列名描述
Name该线程运行过程中所调用的函数名
Incl Cpu Time某函数占用的CPU时间,包含内部调用其它函数的CPU时间
Excl Cpu Time某函数占用的CPU时间,但不含内部调用其它函数所占用的CPU时间
Incl Real Time某函数运行的真实时间(以毫秒为单位),内含调用其它函数所占用的真实时间
Excl Real Time某函数运行的真实时间(以毫秒为单位),不含调用其它函数所占用的真实时间
Call+Recur Calls/Total某函数被调用次数以及递归调用占总调用次数的百分比
Cpu Time/Call某函数调用CPU时间与调用次数的比。相当于该函数平均执行时间
Real Time/Call同CPU Time/Call类似,只不过统计单位换成了真实时间

生成.trace文件还有一种方法就是使用"adb shell am start -W -S -P -n " -P 后面需要加上你的文件路径地址比如:

adb shell am start -n com.example.hoperun.myapplication/.MainActivity  -W -S -P data/local/tmp/test.trace
Stopping: com.example.hoperun.myapplication
Starting: Intent { cmp=com.example.hoperun.myapplication/.MainActivity }
Status: ok
Activity: com.example.hoperun.myapplication/.Main2Activity
ThisTime: 5019
TotalTime: 8756
WaitTime: 9249
Complete

我们取出结果test.trace文件直接放在AndroidStudio中,便会自动解析生成如下所示的图片:

abm

我们会发现加上生成trace文件时的启动时间要慢于正常启动我们的应用,因此我们认为这里面是有一定的误差的。所以我们在查看TraceView的时候更多的关注Incl Cpu Time和Incl Real Time的百分比时间,找出占比比较大的方法,然后具体分析代码逻辑是否存在耗时。有一点需要指出的是本文所列的例子从一方面很好的解释了Incl Cpu Time和Incl Real Time的区别,由于使用了线程sleep方法,因此sleep的时间是不算在Incl Cpu Time内的。总的来说我们理解了各个时间的含义,找出耗时的操作应该不难。

优雅分析姿势三-TraceView有趣的变形dmtracedump

这里提及dmtracedump只是为了一乐,目的在于让大家了解一下原来Google还给了我们这么一个有趣又好玩的工具(个人感觉有点鸡肋,不如TraceView和systrace,更不如下面提到的Android Porfiler),先来张图介绍一下这个工具的作用:

abm

 可以使用dmtracedump  -g命令将.trace文件转化成png图片查看一些函数调用堆栈,我们还可以使用“dmtracedump  -h ddms3567038905260884485.trace > out1.html”命令将trace文件转化成html,我这边截取一点信息分享一下:

abm

 至于我们通过dmtracedump能获取什么有用的内容,就看各位自己了(至少我现在好像没有获取到一些实质性的有用信息,如果您有什么发现,不吝赐教,这是我的邮箱1056908181@qq.com)。

优雅分析姿势四-使用systrace分析

我为本文中的例子也抓取一段10s的systrace,如下图所示:

abm

Android提供的systrace工具会收集给定时间段内的系统进程的总体情况,但是不会收集有关应用程序进程中代码执行的信息。如果我们想要获取我们自己应用具体代码的执行信息,我们可以自己添加Trace Tag让systrace去抓取:

Trace.beginSection("我们需要设置的TAG");
try {
    //我们需要测试的执行代码
} finally {
    Trace.endSection();
}

我们可以从systrace中获取很多信息以本文例子来说:

01-01 20:22:44.381 28907  3552 I am_proc_bound: [0,3593,.abm]
01-01 20:22:44.408 28907  3552 I am_restart_activity: [0,89434600,145,com.example.hoperun.myapplication/.MainActivity]
01-01 20:22:44.418 28907  3552 I am_set_resumed_activity: [0,com.example.hoperun.myapplication/.MainActivity,minimalResumeActivityLocked]
01-01 20:22:44.496  3593  3593 D ABM#MyApplication: onCreate
01-01 20:22:44.496  3593  3593 D ABM#MyApplication: sleep....
01-01 20:22:45.503  3593  3593 D ABM#MyApplication: wake.
01-01 20:22:45.549  3593  3593 D ABM#MainActivity: onCreate
01-01 20:22:45.549  3593  3593 D ABM#MainActivity: sleep....
01-01 20:22:46.549  3593  3593 D ABM#MainActivity: wake.
01-01 20:22:46.556 28907  3552 I ActivityManager: START u0 {cmp=com.example.hoperun.myapplication/.Main2Activity} from uid 10066
01-01 20:22:46.562 28907  3552 I am_create_activity: [0,24308867,145,com.example.hoperun.myapplication/.Main2Activity,NULL,NULL,NULL,0]
01-01 20:22:46.566 28907  3552 I am_pause_activity: [0,89434600,com.example.hoperun.myapplication/.MainActivity]
01-01 20:22:46.583 28907  3552 I am_finish_activity: [0,89434600,145,com.example.hoperun.myapplication/.MainActivity,app-request]

其实我们可以通过对比Log的时间戳,大致上就能够定位处组件某个生命周期方法是否存在耗时操作,但是如果使用systrace,我们会更加的优雅比如:

abm

截取了对应Log部分的systrace,我们可以很清晰的看见在创建Application和Activity的时候,我们的主进程sleep的1000ms,因此只要我们耐心的去查看,我们一定能从systrace中定位出耗时操作所在。

优雅分析姿势五-Android Profiler

Android Profiler是Google提供给Android开发者的一个非常强大的专门用来分析我们应用性能的工具。它不仅仅可以帮助我们分析应用启动时间,还可以用来分析应用的内存分配情况,电量使用等一些其他的关于性能的分析场景。这里就直接介绍如何使用Android Profiler来分析我们应用的启动了,至于工具如何使用就不详细介绍了,这里暂且认为大家都会了。直接切入本文正题:

abm

我们可以看下Android Profiler的界面更像是systrace和TraceView的结合体,因此Android Profiler的界面里的一些信息都是和systrace,TraceView一样的。不过厉害的是Android Profiler使用更简单,更方便程序员跟踪问题信息。但是不足的是它只能抓到最后一个界面的信息,比如本例中第一个MainActivity就不能被Android Profiler给获取(也许是我对工具的理解不到位,如果有大神,麻烦告知小弟,万分感谢!)。我们简单介绍以下这个图片里所涵盖的一些信息:Event Timeline和Activity Lifecycle Timeline:这个时间线会记录Activity的生命周期的不同状态,以及与用户交互的信息,比如接收了什么按键事件,屏幕旋转等等。CPU Timeline:显示了应用进程运行过程中的CPU了使用情况。All Thread State Timeline:显示的则是应用进程中的所有线程在应用运行过程中的状况,并且这个时间线还用了不同颜色用以区分线程的不同状态,绿色表示Running,黄色表示waiting I/O,棕色表示sleeping。Method Profile界面则是在你点击start method Profile再次点击stop之后才会显示的界面。Method Profile界面由四种不同的选项,Call Chart(正常的调用图),Flame Chart(火焰图),Top Down(Top-Down顺序的调用树图),和Bottom Up(bottom-up顺序的调用树图)我们可以根据不同需要去选择不同的选项。

写在最后

应用启动时间优化是一个漫长的旅程,对于我们自己开发的应用,优化自己的代码,算不上是一种煎熬。如果换作debug别人的应用或者让我们优化一些复杂的大型的商业应用,那便是一种折磨。铁打的我们,多变的代码啊。煎熬也好,折磨也罢,我们都得去分析解决,关键是姿势要重要,得优雅。至于本文所提的五种分析思路,基本都是点到为止,想要详细了解,度娘上有很多大神的博文有做详细介绍。文章最后给大家一些平时用到的应用时间优化的建议:布局文件在达到相同界面效果的时候尽可能的层次少一点,界面中不需要加载的View请使用ViewStub代替,第一次初始话过程中请使用lazy加载方式加载不需要用到的静态变量,耗时的操作请放到后台线程不要阻塞UI线程,尽可能的少使用堵塞式的API比如SharePerference的apply和commit,使用对进程的应用请区别初始话Application,有一些初始化的操作不需要在显示之前的请仿照系统调用方式使用“Looper.myQueue().addIdleHandler”(如果您还不了解IdleHandler,建议您看下Android消息机制-Looper MessageQueue Handler).......这里一下不能考虑全优化策略,后续持续补充,如果您还有什么建议,请在评论区留下您宝贵的建议,感谢!本文皆为自己平时的一些经验总结,难免有一些理解不到位的地方,望谅解,望指正,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值