随着项目版本的迭代,App的性能问题会逐渐暴露出来,而好的用户体验与性能表现紧密相关。来看一下Google官方文档《Launch-Time Performance》 对应用启动优化的概述;
冷启动
当后台不存在该应用的任何进程或者服务时,用户点击icon图标启动,我们称之为冷启动。
热启动
当后台存在该应用的进程或者服务时,用户点击icon图标启动,我们称之为热启动。 一般是用户按了home键回到桌面,或者返回键没有杀进程,或者app本身做了进程重启的机制。
温启动
当启动应用时,后台已有该应用的进程,但是启动的入口Activity被干掉了,比如按了back键,应用虽然退出了,但是该应用的进程是依然会保留在后台。
启动组成时间
- 冷启动时间: application初始化时间+欢迎页停留时间
- 热启动时间: 欢迎页停留时间
用户体验时间:冷启动时间+主界面展示时间
冷启动执行三个任务:
- 加载启动app
- app启动之后立即展示一个空白的window
- 创建app的进程
点击icon之后,会启动一个ipc,ipc会通知process.start,这个时候会启动activityThread,activityThread是每一个单独进程的入口,相当于入口类,里面有个main方法,里面会消息处理,进行bindApplication,通过反射调用applicaiton,进行application生命周期,后面进行activity的生命周期
启动后时间节点
Application的构造器方法——>attachBaseContext()——>onCreate()——>Activity的构造方法——>onCreate()——>配置主题中背景等属性——>onStart()——>onResume()——>测量、布局、绘制显示在界面上。
Application 初始化
application启动经历两个方法。结束后才会出现lanucher界面。
attachBaseContext ---> onCreate
- attachBaseContext 一般不会重写。只有在multiDex,或者一些特殊业务,比如插件化,导致在此方法中执行操作。
优化
作为一个用户使用的普通应用,进程级别的操作,我们没有办法进行优化。可以优化的是application,activity的创建和回调。
google官方给了优化方向:
- 利用提前展示出来的Window,快速展示出来一个界面,给用户快速反馈的体验;(这种方法不能提升启动速率,只能使交互好一点)
- 避免在启动时做密集沉重的初始化(Heavy app initialization);
- 定位问题:避免I/O操作、反序列化、网络操作、布局嵌套等。
启动加速之Avoid Heavy App Initialization
在Application以及首屏Activity中我们主要做了: MultiDex以及Tinker的初始化,最先执行;Application中主要做了各种三方组件的初始化;
- 项目中除听云之外其余所有三方组件都抢占先机,在Application主线程初始化。这样的初始化方式肯定是过重的: 考虑异步初始化三方组件,不阻塞主线程;
- 延迟部分三方组件的初始化;实际上我们粗粒度的把所有三方组件都放到异步任务里,可能会出现WorkThread中尚未初始化完毕但MainThread中已经使用的错误,因此这种情况建议延迟到使用前再去初始化;
项目修改:
- 将友盟、Bugly、听云、GrowingIO、BlockCanary等组件放在WorkThread中初始化; 延迟地图定位、ImageLoader、自有统计等组件的初始化:地图及自有统计延迟4秒,此时应用已经打开;而ImageLoader 因为调用关系不能异步以及过久延迟,初始化从Application延迟到SplashActivity;而EventBus因为再Activity中使用所以必须在Application中初始化。
启动加速之Diagnosing The Problem
分析到部分耗时操作发生在主线程,那我们把耗时操作都改到子线程是不是就万事大吉了?非也!!
- 卡顿不能都靠异步来解决,错误的使用工程线程不仅不能改善卡顿,反而可能加剧卡顿。是否需要开启工作线程需要根据具体的性能瓶颈根源具体分析,对症下药,不可一概而论;
- 而如何开启线程同样也有学问:Thread、ThreadPoolExecutor、AsyncTask、HandlerThread、IntentService等都各有利弊;例如通常情况下ThreadPoolExecutor比Thread更加高效、优势明显,但是特定场景下单个时间点的表现Thread会比ThreadPoolExecutor好:同样的创建对象,ThreadPoolExecutor的开销明显比Thread大;
正确的开启线程也不能包治百病,例如执行网络请求会创建线程池,而在Application中正确的创建线程池势必也会降低启动速度;因此延迟操作也必不可少。
通过对traceview的详细跟踪以及代码的详细比对,我发现卡顿发生在:
- 部分数据库及IO的操作发生在首屏Activity主线程;
- Application中创建了线程池;
- 首屏Activity网络请求密集;
- 工作线程使用未设置优先级;
- 信息未缓存,重复获取同样信息;
- 流程问题:例如闪屏图每次下载,当次使用;
以及其它细节问题:
- 执行无用老代码;
- 执行开发阶段使用的代码;
- 执行重复逻辑;
- 调用三方SDK里或者Demo里的多余代码;
启动app的launcher activity,计算时间。
adb shell am start -W com.leku.hmsq/com.leku.hmq.activity.WelcomeActivity
返回值
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.leku.hmsq/com.leku.hmq.activity.WelcomeActivity }
Status: ok
Activity: com.leku.hmsq/com.leku.hmq.activity.HomeTabActivity
ThisTime: 629
TotalTime: 738
WaitTime: 755
Complete
ThisTime:最后一个启动的Activity的启动耗时;
TotalTime:自己的所有Activity的启动耗时;
WaitTime: ActivityManagerService启动App的Activity时的总时间(包括当前Activity的onPause()和自己Activity的启动)。
查看每一个activity的显示时间
adb logcat | grep "ActivityManager"