1、前言
随着项目版本的迭代,App的性能问题会逐渐暴露出来,而好的用户体验与性能表现紧密相关,性能问题从应用的启动优化开始,下面会根据实际app性能测试案例,进行app性能评测之启动时间的分析和总结。
2、App启动方式了解
通常来说, 一个App启动也会分如下三中不同的状态:
-
冷启动
当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动,也就是先实例化Application冷启动的流程即为App启动流程的全过程, 需要创建App进程, 加载相关资源, 启动Main Thread, 初始化首屏Activity等.
在这个过程中, 屏幕会显示一个空白的窗口(颜色基于主题), 直至首屏Activity完全启动.
下图展示了冷启动的时间线:
-
热启动
当启动应用时,后台已有该应用的进程,这时会从已有的进程来启动Activity(不需要重新创建Application)类同与冷启动, 在这个过程中, 屏幕会显示一个空白的窗口(颜色基于主题), 直至activity渲染完毕.
-
温启动
介于冷启动和热启动之间, 一般来说在以下两种情况下发生:用户back退出了App, 然后又启动. App进程可能还在运行, 但是activity需要重建.
用户退出App后, 系统可能由于内存原因将App杀死, 进程和activity都需要重启, 但是可以在onCreate中将被动杀死锁保存的状态(saved instance state)恢复.
通过三种启动状态的相关描述, 可以看出我们要做的启动优化其实就是针对冷启动. 热启动和温启动都相对较快.
3、性能评测结果案例分析
3.1 XX银行 APP启动时间评测结果分析
3.1.1 启动时间测试结果
统计的时间为APP的冷启动时间,4G网络情况下,平均冷启动时间为14.1S,平均热启动时间为6.7S,无网络时平均启动时间为5.2S。由于APP的启动时间耗时远远超过行业标准水平3S,当app启动时,任何一个地方有耗时操作都会拖慢我们应用的启动速度,所以针对APP启动时间做了具体分析
3.1.2评测结果分析
首先来看App从点击桌面图标到我们看到App的主界面整个过程中经过了哪些步骤:
启动虚拟机—>启动AMS —>通过Zygote创建ApplicationProcess进程–>Application的构造器方法——>attachBaseContext()——>onCreate()——>Activity的构造方法——>onCreate()——>配置主题中背景等属性——>onStart()——>onResume()——>测量布局绘制显示在界面上。
1)初始化Application
- 【初始化Application】应用程序初始化总共耗费了3.26S,耗时已超过行业APP启动标准基线时间3S。具体看下耗时详情分析:
(1)应用Application.attach的时间较长。应用存在多个dex文件,请控制应用方法数量。
(2)onCreate方法耗时长,主要体现BaseApplication创建上。
长耗时方法排名如下:
1.com.pingan.fstandard.framework.base.BaseApplication.onCreate(BaseApplication.java)
- com.pingan.fstandard.common.c.a(InitManager.java)
- com.pafinancialtech.pafs.kitchendaddy.f.b(PAFFToast.java:83
4.com.pingan.fstandard.HomeApplication.d(HomeApplication.java:51
综上分析,BaseApplication创建上耗费较长时间,需要开发进行更详细的分析优化。另外,初始化Application时,也包含了任意门相关等非核心功能的程序,建议启动时不创建该类程序。
2)初始化Activity
- 【初始化Activity】耗时也过长,耗时约1s左右
主要耗时的地方是在闪屏界面创建oncreate时com.pingan.fstandard.activity.SplashActivity.onCreate,建议开发优化
3)加载请求及响应
首先来一张流程图了解一下我们APP首页加载的一个流程
- 【网络请求及响应】总体来看,首页加载耗时占比最多,需要6s左右,对应的网络消耗时间占比45%左右。
根据抓包及代码段分析显示APP启动到首页加载完成是一个预加载和异步加载的过程。
(1) 项目中大部分第三方组件都抢占先机,在Application主线程初始化。这样的初始化方式肯定是过重的,建议考虑异步初始化三方组件,不阻塞主线程;延迟部分三方组件(比如埋点统计、任意门、ImageLoader的初始化)。
(2) 启动到首页加载完成网络请求密集,请求内容丰富,部分资源重复请求。
请求了相关配置信息、启动页广告、个推、磁贴配置信息、商城理财产品列表,运营广告Banner、F后端接口,广告BANNER位、插件信息、任意门、百度地图SDK(talkingdata、灰度升级)等林林总总加起来就有40+个网络请求,加载的数据+广告页一共有1.7M左右,这说明了我们的APP首页设计的内容比较丰富,部分资源重复请求,部分内容需要调外部接口,所以耗时比较长,这是产品设计问题、信息未做缓存处理和部分外部关联方接口响应慢的原因导致,建议优化。
(3)非核心功能资源过早请求加载
另外其中类似百度地图SDK、任意门、微信sdk相关插件只有在生活页\分享时使用到的非主要业务功能启动时也加载出来了,建议开发同学和产品同学调整APP启动时网络请求的加载策略,非核心/主要/高PV的功能相关的API,SDK、插件等没有必要在启动时做加载缓存,这是非常耗时且得不偿失的。
3.1.3启动时间优化建议
(1)应用Application.attach的时间较长。应用存在多个dex文件,请控制应用方法数量。
(2)onCreate方法耗时长,主要体现BaseApplication创建上,需要开发进行更详细的分析优化,建议考虑异步初始化三方组件,不阻塞主线程;延迟或者和产品一起梳理需要的去除的部分三方组件(比如统计、任意门、ImageLoader的初始化)。
(3)com.pingan.fstandard.activity.MainActivity存在过度绘制导致卡顿,首页UI布局层次过多,建议开发进一步分析优化
(4)建议产品调整优化app启动时网络请求的加载策略,非核心/主要/高PV的功能相关的API,SDK、插件等没有必要在启动时做加载缓存
(5)部分资源重复请求,建议信息缓存,常用信息只在第一次获取,之后从缓存中取
(6)建议开发梳理无用但被执行的老代码,去掉无用但被执行的老代码
综上,建议开发童鞋尽快投入做APP启动时性能改善和优化,以提升行业相比竞品的竞争力。
4、App端启动耗时排查方法及思路
4.1 排查方法:使用Traceview分析
TraceView 是 Android SDK 中内置的1个工具,它可以加载 trace 文件,用图形的情势展现代码的履行时间、次数及调用栈,便于我们分析。
(1)使用Android Studio工具DDMS
生成Traceview 进行分析查看具体Trace期间各方法调用关系,调用次数以及耗时比例
(2)使用代码生成 trace 文件
Debug.startMethodTracing("shixintrace");
//开始 trace,保存文件到 "/sdcard/shixintrace.trace"
// ...
Debug.stopMethodTracing(); //结束
代码很简单,当你调用开始代码的时候,系统会生产 trace 文件,并且产生追踪数据,当你调用结束代码时,会将追踪数据写入到 trace 文件中。
下1步使用 adb 命令将 trace 文件导出到电脑:
adb pull /sdcard/shixintrace.trace /tmp
使用代码生成 trace 方式的好处是容易控制追踪的开始和结束,缺点就是步骤略微多了一点。
(3)直接使用android studio生成trace文件
Android Studio 内置的 Android Monitor 可以很方便的生成 trace 文件到电脑。
注意:此方法仅仅适用于下拉代码到本地生成DEBUG测试包到手机进行调试
分析指标:
不应在Application以及Activity的生命周期回调中做任何费时操作,具体指标大概是你在onCreate,onResume,onStart等回调中所花费的总时间最好不要超过400ms,否则用户在桌面点击你的应用图标后,将感觉到明显的卡顿。
4.2 开启严苛模式(StrictMode)
检查是否有主线程做了耗时操作: 开启 严苛模式(StrictMode)
(1)StrictMode可以用于捕捉发生在应用程序 主线程中耗时的磁盘、网络访问或函数调用,使主线程处理UI和动画在磁盘读写和网络操作时变得更平滑,避免主线程被阻塞,导致ANR窗口的发生。
下面是启用StrictMode的实例,可以在Application的OnCreate中添加,这样就能在程序启动的最初一刻进行监控了。
private void setStrictMode() {
if (Integer.valueOf(Build.VERSION.SDK) > 3) {
Log.d(LOG_TAG, "Enabling StrictMode policy over Sample application");
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectAll() // 这里可以替换为.detectDiskReads().detectDiskWrites().detectNetwork()。
// detectAll() 包括了磁盘读写和网络I/O
.penaltyLog() //打印logcat,当然也可以定位到dropbox,通过文件保存相应的log
.penaltyDeath()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectAll()
.penaltyLog()
.build());
}
}
(2)除了通过日志查看之外,我们也可以在开发者选项中开启严格模式,开启之后,如果主线程中有执行时间长的操作,屏幕则会闪烁,这是一个更加直接的方法。
4.3、通过抓包工具分析
可以通过抓包工具Charels、Fiddler等查看网络请求加载了哪些资源,加载资源的顺序、哪些资源重复加载、信息是否缓存
4.4、查看Logcat
通过adb输出log信息,过滤日志查看APP启动时每个方法的大致耗时。
4.5、启动时间排查思路
对于App来说, 我们可以控制的启动时间线的点无外乎:
Application的onCreate
首屏Activity的渲染
而我们现在的App动不动集成了很多第三方服务, 启动时需要检查广告, 注册状态等等一系列接口都是在Application的onCreate或是首屏的onCreate中做的.
5、启动时间耗时常见原因及优化建议
5.1、 常见主要问题(持续补充ing)
- 部分数据库及IO的操作发生在首屏Activity主线程;
- Application中创建了线程池;
- 启动时做密集沉重的初始化(Heavy app initialization);
- Multidex的使用,也是拖慢启动速度的元凶;
- UI存在过度绘制;
- 首屏Activity网络请求密集;
- 非核心功能资源过早请求加载
- 工作线程使用未设置优先级;
- 信息未缓存,重复获取同样信息;
- 流程问题:例如闪屏图每次下载,当次使用;
- 执行无用老代码;
- 执行开发阶段使用的代码;
- 执行重复逻辑;
- 调用三方SDK里或者Demo里的多余代码;
5.2、建议:
- 去掉无用但被执行的老代码;
- 去掉开发阶段使用但线上被执行的代码;
- 去掉重复逻辑执行代码;
- UI渲染优化,去除重复绘制,减少UI重复绘制时间
- 去掉调用三方SDK里或者Demo里的多余代码;
- 信息缓存,常用信息只在第一次获取,之后从缓存中取;
- 项目是多进程架构,只在主进程执行Application的onCreate();
- 根据优先级的划分,一些初始化工作能否将任务优先级划分成3个等级,任务优先级为2,3的,通过懒加载的方式在首页渲染完成后进行加载
- 避免I/O操作、反序列化、网络操作、布局嵌套等。
参考文献:
学习地址:
https://www.cnblogs.com/sunzn/p/3192231.html
http://www.wfuyu.com/technology/27625.html
http://blog.csdn.net/qq_16131393/article/details/51172488