用户希望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:
1.2 Traceview使用
有两种方式来使用Traceview:
a, 通过DDMS:
点击开始时会弹出一个选择trace模式的框, 默认选中”Sample based profiling”即可:
Sample based profiling(基于样本分析)
根据采样时间间隔来规律的打断VM来记录方法调用栈(Call Stack), 开销和采样频率成比例。
Trace based profiling(基于完整trace数据分析)
记录每个方法的出入口, 每个方法执行时都开启记录, 无论多小的方法, 因此开销很大。
b, 使用代码:
注: 以上方法开启trace的方式相当于”Trace based profiling”, 会记录每个方法的执行. Android 4.4及以上可以调用startMethodTracingSampling()来用代码开启”Sample based profiling”的trace方式。
2, App启动流程分析
3, App启动方式
通常来说, 一个App启动也会分如下三中不同的状态:
冷启动
应用发生冷启动时,系统有三件任务要做:
加载启动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
的热启动要比 冷启动简单得多,内存开销也更低。APP
热启动时候,所有的系统都是把你的Activity
带到前台。如果APP
的所有Activity
仍驻留在内存中,则APP
可以避免重复对象初始化、布局绘制和显示等工作。
如果APP
在内存中被清理掉,比如调用ontrimmemory()
,当响应热启动时,这些对象将重新被创建。
热启动与冷启动相同的屏幕行为:
系统进程会显示一个空白屏幕,直到应用程序完成渲染后将此空白屏幕移除掉,此屏幕创建会在加载APP
时候立即创建,如需查看创建流程,需要查看PhoneWindosManger
中AddWindows
方法。
温启动
介于冷启动和热启动之间, 一般来说在以下两种情况下发生:
通过三种启动状态的相关描述, 可以看出我们要做的启动优化其实就是针对冷启动. 热启动和温启动都相对较快。
4, 哪些地方是App快速启动的敌人
这里我们重点讨论用户点击桌面后的APP启动,通过startActivity方式的启动。
调用startActivity,该方法经过层层调用,最终会调用ActivityStackSupervisor.java中的startSpecificActivityLocked,当activity所属进程还没启动的情况下,则需要创建相应的进程.
进程启动后系统还有一个工作就是:进程启动后立即显示应用程序的空白启动窗口。
一旦系统创建应用程序进程,应用程序进程就会负责下一阶段。这些阶段是:
1.创建应用程序对象
2.启动主线程
3.创建主要Activity
4.绘制视图(View)
5.布局屏幕
6.执行初始化绘制
而一旦App进程完成了第一次绘制,系统进程就会用Main Activity替换已经展示的Background Window,此时用户就可以使用App了。
根据冷启动的时间图, 可以看出, 对于App来说, 我们可以控制的启动时间线的点无外乎:
而我们现在的App动不动集成了很多第三方服务, 启动时需要检查广告, 注册状态等等一系列接口都是在Application的onCreate或是首屏的onCreate中做的。
这里很明显有两个优化点:
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的页面显示出来之后再进行加载数据,避免过早或过晚的加载导致页面空白时间过长。
减少LaunchActivity的View层级,减少View测量绘制时间。
避免主线程做耗时操作
用户体验优化
消除启动时的白屏/黑屏
为什么启动时会出现短暂黑屏或白屏的现象?当用户点击你的app那一刻到系统调用Activity.onCreate()之间的这个时间段内,WindowManager会先加载app主题样式中的windowBackground做为app的预览元素,然后再真正去加载activity的layout布局。
很显然,如果你的application或activity启动的过程太慢,导致系统的BackgroundWindow没有及时被替换,就会出现启动时白屏或黑屏的情况(取决于你的主题是Dark还是Light)。
使用透明主题
如何对启动时间进行量化?
1.目前为止见过最最牛逼的是使用机械手和高速相机测试,手机开机后使用机械手点击应用桌面图标,高速相机记录启动过程,后续通过程序分析视频,从机械手点击图标到Activity显示出来使用了多少时间。这种方式是最直观和精确的,但是成本也很高。
2.通过shell 命令:
adb shell am start -W [packageName]/[packageName.MainActivity]
或adb [-d|-e|-s
执行成功后将返回三个测量到的时间:
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文件
在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