启动速度优化

用户希望APP能够快速响应并加载。 一个启动速度慢的APP 不符合用户期望,可能会令用户失望,并且可能会导致用户对您的应用程序评价不佳,甚至会卸载你的应用。

本文将讨论如何优化应用的启动时间,首先我们需要了解APP启动的相关内容。

 

1.分析工具: 要想提升App的启动速度, 我们需要先找到拖后腿的点, 要想找到这些点, 我们就需要借助我们的工具了。我们使用Traceview来分析我们的启动过程。

1.1 Traceview介绍

Traceview是一个性能分析工具, 主要是分析当前线程情况, 各个方法执行时间等。如下:

指标说明:

Incl(Inclusive) Cpu Time:方法本身和其调用的所有子方法占用CPU时间.

Excl(Exclusive) Cpu Time:方法本身占用CPU时间。

Incl Real Time:方法(包含子方法)开始到结束用时。

Excl Real Time:方法本身开始到结束用时。

Call + Recursion Calls/Total:方法被调用次数 + 方法被递归调用次数。

Cpu Time/Call:方法调用一次占用CPU时间。

Real Time/Call:方法调用一次实际执行时间。

一般来说, 我们使用Real Time/Call排序来找出耗时多的方法

有必要解释下CPU Time和Real Time:

  • CPU Time 方法实际执行时间(不包括io等待时间)
  • Real Time 方法开始结束时间差(包括等待时间)

 

1.2 Traceview使用

有两种方式来使用Traceview:

a, 通过DDMS:

点击开始时会弹出一个选择trace模式的框, 默认选中”Sample based profiling”即可:

Sample based profiling(基于样本分析)

根据采样时间间隔来规律的打断VM来记录方法调用栈(Call Stack), 开销和采样频率成比例。

Trace based profiling(基于完整trace数据分析)

记录每个方法的出入口, 每个方法执行时都开启记录, 无论多小的方法, 因此开销很大。

b, 使用代码:

  1. // 在自己想要开始调试的地方start 
  2. Debug.startMethodTracing("GithubApp"); 
  3. // 在合适的地方stop 
  4. Debug.stopMethodTracing(); 

注: 以上方法开启trace的方式相当于”Trace based profiling”, 会记录每个方法的执行. Android 4.4及以上可以调用startMethodTracingSampling()来用代码开启”Sample based profiling”的trace方式。

 

2, App启动流程分析

 

  • 要想优化App启动流程, 必先了解其启动过程。
  • 具体过程请参看这篇译文: Android Application启动流程分析。
     

3, App启动方式

 

通常来说, 一个App启动也会分如下三中不同的状态:

冷启动

  • App没有启动过或App进程被killed, 系统中不存在该App进程, 此时启动App即为冷启动。
  • 冷启动的流程即为第2节所描述的App启动流程的全过程, 需要创建App进程, 加载相关资源, 启动Main Thread, 初始化首屏Activity等。
  • 在这个过程中, 屏幕会显示一个空白的窗口(颜色基于主题), 直至首屏Activity完全启动。

应用发生冷启动时,系统有三件任务要做:

    加载启动App;
    App启动之后立即展示出一个空白的Window;
    创建App的进程;

创建App进程后,会马上执行以下任务:

    初始化应用中的对象 (比如 Application 中的工作);
    启动主线程 (UI 线程) ;
    创建第一个 Activity;
    加载内容视图 (Inflating) ;
    计算视图在屏幕上的位置排版 (Laying out);
    进行第一次绘制 (draw)。
只有当应用完成第一次绘制,系统当前展示的空白背景才会消失,才会被 Activity 的内容视图替换掉。也就是这个时候,用户才能和我们的应用开始交互。

启动流程大致如下:

点击Launcher 上的 icon开加载app -->立即显示白屏或黑屏等 --> Application onCreate --> Activity Init----> Activity onCreate ---> 初始化数据,填充显示View ---> Activity onResume等,下图展示了冷启动的时间线:

 

 

热启动

  • 热启动意味着你的App进程只是处于后台, 系统只是将其从后台带到前台, 展示给用户。
  • 类同与冷启动, 在这个过程中, 屏幕会显示一个空白的窗口(颜色基于主题), 直至activity渲染完毕。

APP的热启动要比 冷启动简单得多,内存开销也更低。APP热启动时候,所有的系统都是把你的Activity带到前台。如果APP的所有Activity仍驻留在内存中,则APP可以避免重复对象初始化、布局绘制和显示等工作。
如果APP 在内存中被清理掉,比如调用ontrimmemory(),当响应热启动时,这些对象将重新被创建。

热启动与冷启动相同的屏幕行为:
系统进程会显示一个空白屏幕,直到应用程序完成渲染后将此空白屏幕移除掉,此屏幕创建会在加载APP时候立即创建,如需查看创建流程,需要查看PhoneWindosMangerAddWindows方法。


 

温启动

介于冷启动和热启动之间, 一般来说在以下两种情况下发生:

  • 用户back退出了App, 然后又启动. App进程可能还在运行, 但是activity需要重建。
  • 用户退出App后, 系统可能由于内存原因将App杀死, 进程和activity都需要重启, 但是可以在onCreate中将被动杀死锁保存的状态(saved instance state)恢复。

通过三种启动状态的相关描述, 可以看出我们要做的启动优化其实就是针对冷启动. 热启动和温启动都相对较快。

 

4, 哪些地方是App快速启动的敌人

 

这里我们重点讨论用户点击桌面后的APP启动,通过startActivity方式的启动。

调用startActivity,该方法经过层层调用,最终会调用ActivityStackSupervisor.java中的startSpecificActivityLocked,当activity所属进程还没启动的情况下,则需要创建相应的进程.

进程启动后系统还有一个工作就是:进程启动后立即显示应用程序的空白启动窗口。

一旦系统创建应用程序进程,应用程序进程就会负责下一阶段。这些阶段是:
1.创建应用程序对象
2.启动主线程
3.创建主要Activity
4.绘制视图(View)
5.布局屏幕
6.执行初始化绘制

而一旦App进程完成了第一次绘制,系统进程就会用Main Activity替换已经展示的Background Window,此时用户就可以使用App了。

 

根据冷启动的时间图, 可以看出, 对于App来说, 我们可以控制的启动时间线的点无外乎:

  • Application的onCreate
  • 首屏Activity的渲染

而我们现在的App动不动集成了很多第三方服务, 启动时需要检查广告, 注册状态等等一系列接口都是在Application的onCreate或是首屏的onCreate中做的。

  • 很多第三方平台的SDK文档也都是这么建议的。

 

这里很明显有两个优化点:


Application OnCrate()优化


当APP启动时,空白的启动窗口将保留在屏幕上,直到系统首次完成绘制应用程序。此时,系统进程会交换应用程序的启动窗口,允许用户开始与应用程序进行交互。如果应用程序中重载了Application.onCreate(),系统会调用onCreate()方法。之后,应用程序会生成主线程(也称为UI线程),并通过创建MainActivity来执行任务。


Activity onCreate()优化
onCreate()方法对加载时间的影响最大,因为它以最高的开销执行工作:加载并绘制视图,以及初始化Activity运行所需的对象。

 

Google也给出了启动加速的方向:

1、利用提前展示出来的Window,快速展示出来一个界面,给用户快速反馈的体验;
2、 避免在启动时做密集沉重的初始化(Heavy app initialization);
3、 定位问题:避免I/O操作、反序列化、网络操作、布局嵌套等

Application OnCrate()优化

在Application初始化的地方做太多繁重的事情是可能导致严重启动性能问题的元凶之一。Application里面的初始化操作不结束,其他任意的程序操作都无法进行。Application的onCreate中会做大量第三方组件的初始化工作,其实很多组件是需要做区别对待的,有些可以做延迟加载,有些可以放到其他的地方做初始化操作,特别需要留意包含Disk IO操作,网络访问等严重耗时的任务,他们会严重阻塞程序的启动。

注意点:

    项目是多进程架构,只在主进程执行Application的onCreate();
    流程梳理,延后执行;
    异步加载、延时加载、懒加载
 

1.第三方SDK初始化的处理


Application是程序的主入口,很多三方SDK示例程序中都要求自己在Application OnCreate时做初始化操作。这就是增加Application OnCreate时间的主要元凶,所以需要尽量避免在Application onCreate时同步做初始化操作。比较好的解决方案就是对三方SDK就行懒加载,不在Application OnCreate()时初始化,在真正用到的时候再去加载。

Application OnCreate 避免在主线程做大量耗时操作,例如和IO相关的逻辑,这样都会影响到应用启动速度。如果必须要做需要放到子线程中。
 

Activity onCreate()优化

避免首个Activity的onCreate进行太多的工作

使用延迟加载。确保在Activity的页面显示出来之后再进行加载数据,避免过早或过晚的加载导致页面空白时间过长。

减少LaunchActivity的View层级,减少View测量绘制时间。
避免主线程做耗时操作

 

用户体验优化

消除启动时的白屏/黑屏

 

为什么启动时会出现短暂黑屏或白屏的现象?当用户点击你的app那一刻到系统调用Activity.onCreate()之间的这个时间段内,WindowManager会先加载app主题样式中的windowBackground做为app的预览元素,然后再真正去加载activity的layout布局。
很显然,如果你的application或activity启动的过程太慢,导致系统的BackgroundWindow没有及时被替换,就会出现启动时白屏或黑屏的情况(取决于你的主题是Dark还是Light)。

使用透明主题 <item name="android:windowIsTranslucent">true</item>   Activity.onCreate()之前App不做显示

 

如何对启动时间进行量化?

 

1.目前为止见过最最牛逼的是使用机械手和高速相机测试,手机开机后使用机械手点击应用桌面图标,高速相机记录启动过程,后续通过程序分析视频,从机械手点击图标到Activity显示出来使用了多少时间。这种方式是最直观和精确的,但是成本也很高。

 

2.通过shell 命令

adb shell am start -W [packageName]/[packageName.MainActivity]


adb [-d|-e|-s <serialNumber>] shell am start -S -W [packageName]/[packageName.MainActivity] -c android.intent.category.LAUNCHER -a android.intent.action.MAIN

 

执行成功后将返回三个测量到的时间:


ThisTime:         一般和TotalTime时间一样,除非在应用启动时开了一个透明的Activity预先处理一些事再显示出主Activity,这样将比TotalTime小。
TotalTime:       应用的启动时间,包括创建进程+Application初始化+Activity初始化到界面显示。
WaitTime:       一般比TotalTime大点,包括系统影响的耗时。

开发者一般只要关心 TotalTime 即可,这个时间才是自己应用真正启动的耗时。

3.Traceview

告诉我们每一个方法执行了多长时间.这个工具可以通过 Android Device Monitor 或者从代码中启动。


3.1 Android Device Monitor启动

启动应用,点击 Start Method Tracing,应用启动后再次点击,会自动打开刚才操作所记录下的.trace文件,建议使用DDMS来查看,功能更加方便全面。

3.2 代码启动

①在onCreate开始和结尾打上trace

     Debug.startMethodTracing("GithubApp");
	...
     Debug.stopMethodTracing();

运行程序, 会在sdcard上生成一个"GithubApp.trace"的文件.

②通过adb pull将文件导出到本地

③打开DDMS分析trace文件

④分析trace文件

 

4.Systrace:

在onCreate方法里面添加trace.beginSection()与trace.endSection()方法来声明需要跟踪的起止位置,系统会帮忙统计中间经历过的函数调用耗时,并输出报表。

 

5:响应时间+白屏 测试- Timeline方法

 

小米视频国际版优化前后日志对比:

 

优化之前:

02-10 19:11:56.775  1850  2311 I Timeline: Timeline: App_transition_stopped time:99622803

02-10 19:11:59.308  2688  2688 I Timeline: Timeline: Activity_launch_request time:99625336 intent:Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.miui.videoplayer/com.miui.video.global.app.LauncherActivity bnds=[839,490][1017,668] (has extras) }

02-10 19:11:59.433  1850  2928 I Timeline: Timeline: App_transition_ready time:99625461

02-10 19:12:00.584  1850  3477 I Timeline: Timeline: App_transition_ready time:99626612

02-10 19:12:00.592  1850  3477 I Timeline: Timeline: App_transition_ready time:99626620

02-10 19:12:00.669 25989 25989 I Timeline: Timeline: Activity_launch_request time:99626697 intent:Intent { cmp=com.miui.videoplayer/com.miui.video.global.activity.HomeActivity (has extras) }

02-10 19:12:00.775  1850  3601 I Timeline: Timeline: App_transition_ready time:99626803

02-10 19:12:00.793  1850  2311 I Timeline: Timeline: App_transition_ready time:99626821

02-10 19:12:01.515  1850  2311 I Timeline: Timeline: App_transition_ready time:99627543

02-10 19:12:01.902  1850  3601 I Timeline: Timeline: App_transition_ready time:99627930

02-10 19:12:01.913  1850  3601 I Timeline: Timeline: App_transition_ready time:99627941

02-10 19:12:02.021  1850  3484 I Timeline: Timeline: App_transition_ready time:99628049

02-10 19:12:02.078  1850  2311 I Timeline: Timeline: App_transition_ready time:99628106

02-10 19:12:02.409  1850  2050 I Timeline: Timeline: Activity_windows_visible id: ActivityRecord{5704311 u0 com.miui.videoplayer/com.miui.video.global.activity.HomeActivity t4304} time:99628437

02-10 19:12:02.411  1850  2311 I Timeline: Timeline: App_transition_stopped time:99628439

 

优化之后:

02-10 17:37:37.114  2688  2688 I Timeline: Timeline: Activity_launch_request time:94136890 intent:Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.miui.videoplayer/com.miui.video.global.activity.HomeActivity bnds=[839,490][1017,668] (has extras) }

02-10 17:37:37.243  1850 14096 I Timeline: Timeline: App_transition_ready time:94137019

02-10 17:37:38.680  1850  1904 I Timeline: Timeline: App_transition_ready time:94138457

02-10 17:37:38.687  1850  1904 I Timeline: Timeline: App_transition_ready time:94138463

02-10 17:37:38.828  1850  3499 I Timeline: Timeline: App_transition_ready time:94138605

02-10 17:37:38.863  1850  2311 I Timeline: Timeline: App_transition_ready time:94138639

02-10 17:37:39.177  1850  2050 I Timeline: Timeline: Activity_windows_visible id: ActivityRecord{29919cb u0 com.miui.videoplayer/com.miui.video.global.activity.HomeActivity t4279} time:94138954

02-10 17:37:39.179  1850  2311 I Timeline: Timeline: App_transition_stopped time:94138955


6:可以通过在代码中增加log来计算启动时间

优化之前:

2020-02-10 21:41:59.688 25989-25989/com.miui.videoplayer D/TimeStatic: initialize begin

2020-02-10 21:41:59.688 25989-25989/com.miui.videoplayer D/TimeStatic: initBase  begin

2020-02-10 21:41:59.786 25989-25989/com.miui.videoplayer D/TimeStatic: initBase  over

2020-02-10 21:41:59.786 25989-25989/com.miui.videoplayer D/TimeStatic: GlobalApplication initBase cost time ==  98

2020-02-10 21:41:59.789 25989-25989/com.miui.videoplayer D/TimeStatic: initModule  begin

2020-02-10 21:41:59.949 25989-25989/com.miui.videoplayer D/TimeStatic: initModule  over

2020-02-10 21:41:59.949 25989-25989/com.miui.videoplayer D/TimeStatic: GlobalApplication initModule cost time ==  160

2020-02-10 21:41:59.949 25989-25989/com.miui.videoplayer D/TimeStatic: asyncInitModule  begin

2020-02-10 21:42:00.038 25989-25989/com.miui.videoplayer D/TimeStatic: asyncInitModule  over

2020-02-10 21:42:00.039 25989-25989/com.miui.videoplayer D/TimeStatic: GlobalApplication asyncInitModule cost time ==  90

2020-02-10 21:42:00.039 25989-25989/com.miui.videoplayer D/TimeStatic: initialize over

2020-02-10 21:42:00.039 25989-25989/com.miui.videoplayer D/TimeStatic: GlobalApplication initialize cost time ==  351

2020-02-10 21:42:00.039 25989-25989/com.miui.videoplayer D/TimeStatic: GlobalApplication onCreate cost time ==  354

2020-02-10 21:42:00.073 25989-25989/com.miui.videoplayer D/TimeStatic: LauncherActivity onCreate  begin

2020-02-10 21:42:00.184 25989-25989/com.miui.videoplayer D/TimeStatic: LauncherActivity onCreate  over

2020-02-10 21:42:00.184 25989-25989/com.miui.videoplayer D/TimeStatic: LauncherActivity onCreate cost time ==  111

2020-02-10 21:42:00.184 25989-25989/com.miui.videoplayer D/TimeStatic: LauncherActivity App cost total time ==  499

2020-02-10 21:42:00.661 25989-25989/com.miui.videoplayer D/TimeStatic: LauncherActivity: default

2020-02-10 21:42:00.973 25989-25989/com.miui.videoplayer D/TimeStatic: HomeActivity preAddMomentFragment  begin

2020-02-10 21:42:00.979 25989-25989/com.miui.videoplayer D/TimeStatic: HomeActivity preAddMomentFragment  over

2020-02-10 21:42:00.979 25989-25989/com.miui.videoplayer D/TimeStatic: HomeActivity preAddMomentFragment cost time ==  6

2020-02-10 21:42:01.086 25989-25989/com.miui.videoplayer D/TimeStatic: HomeActivity onCreate  over

2020-02-10 21:42:01.086 25989-25989/com.miui.videoplayer D/TimeStatic: HomeActivity onCreate cost time ==  279

2020-02-10 21:42:01.086 25989-25989/com.miui.videoplayer D/TimeStatic: HomeActivity onCreate App cost total  time ==  1401

2020-02-10 21:42:01.478 25989-25989/com.miui.videoplayer D/TimeStatic: HomeActivity onResume  over

2020-02-10 21:42:01.478 25989-25989/com.miui.videoplayer D/TimeStatic: HomeActivity onResume cost time ==  12

2020-02-10 21:42:01.478 25989-25989/com.miui.videoplayer D/TimeStatic: HomeActivity onResume App cost total  time ==  1793

 

 

优化之后:

2020-02-10 20:07:37.465 30530-30530/com.miui.videoplayer D/TimeStatic: initialize begin

2020-02-10 20:07:37.465 30530-30530/com.miui.videoplayer D/TimeStatic: initBase  begin

2020-02-10 20:07:37.565 30530-30530/com.miui.videoplayer D/TimeStatic: initBase  over

2020-02-10 20:07:37.565 30530-30530/com.miui.videoplayer D/TimeStatic: GlobalApplication initBase cost time ==  100

2020-02-10 20:07:37.571 30530-30530/com.miui.videoplayer D/TimeStatic: initialize over

2020-02-10 20:07:37.571 30530-30567/com.miui.videoplayer D/TimeStatic: initModule  begin

2020-02-10 20:07:37.571 30530-30530/com.miui.videoplayer D/TimeStatic: GlobalApplication initialize cost time ==  106

2020-02-10 20:07:37.571 30530-30530/com.miui.videoplayer D/TimeStatic: GlobalApplication onCreate cost time ==  109

2020-02-10 20:07:37.609 30530-30530/com.miui.videoplayer D/TimeStatic: LauncherActivity: homeActivity

2020-02-10 20:07:37.957 30530-30530/com.miui.videoplayer D/TimeStatic: HomeActivity onCreate  over

2020-02-10 20:07:37.957 30530-30530/com.miui.videoplayer D/TimeStatic: HomeActivity onCreate cost time ==  354

2020-02-10 20:07:37.957 30530-30530/com.miui.videoplayer D/TimeStatic: HomeActivity onCreate App cost total  time ==  495

2020-02-10 20:07:37.970 30530-30530/com.miui.videoplayer D/TimeStatic: HomeActivity onResume  over

2020-02-10 20:07:37.970 30530-30530/com.miui.videoplayer D/TimeStatic: HomeActivity onResume cost time ==  5

2020-02-10 20:07:37.970 30530-30530/com.miui.videoplayer D/TimeStatic: HomeActivity onResume App cost total  time ==  508

2020-02-10 20:07:38.265 30530-30567/com.miui.videoplayer D/TimeStatic: initModule  over

2020-02-10 20:07:38.266 30530-30567/com.miui.videoplayer D/TimeStatic: GlobalApplication initModule cost time ==  694

2020-02-10 20:07:38.892 30530-30530/com.miui.videoplayer D/TimeStatic: GlobalApplication asyncInitModule cost time ==  31

2020-02-10 20:07:39.974 30530-30530/com.miui.videoplayer D/TimeStatic: HomeActivity preAddMomentFragment  begin

2020-02-10 20:07:39.976 30530-30530/com.miui.videoplayer D/TimeStatic: HomeActivity preAddMomentFragment  over

2020-02-10 20:07:39.976 30530-30530/com.miui.videoplayer D/TimeStatic: HomeActivity preAddMomentFragment cost time ==  2

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值