打开应用如果需要需要用户等待很长时间,比如10秒,那么会给用户一个很差的使用体验,甚至会导致用户的流失。所以应用的启动时间要在一个合理的时间范围,才能提升用户的使用体验,所以也就有了启动优化的必要性。
启动方式
系统会根据应用的当前状态,分为三种启动方式:冷启动、热启动、温启动
冷启动
冷启动一般指的的是当前应用没被打开,即进程不存在,需要走完应用的启动应用的流程,启动应用的具体流程可以参考Activity启动流程一文。
冷启动的大致流程有:
- binder进程间通信,进入AMS启动应用
- 创建并显示应用空白启动窗口,即应用的预览视图
- process创建并启动进程
- 进入ActivityThread启动主线程
- 创建应用对象Application,并初始化、调用其生命周期attach、onCreate
- 创建主Activity,并初始化、调用其attach、生命周期onCreate等
- 加载view并进行绘制渲染
热启动
热启动一般指的是应用从后台切回前台的操作,回调对应生命周期如:onRestart、onResume等。开销相比冷启动要低很多。
温启动
温启动的开销介于冷启动和热启动之间,以下是是常见的温启动方式:
- 用户退出应用后又重新启动应用。此时进程还在,只是activity不在,所以会走:创建activity并初始化并调用其相应的生命周期,如onCreate等,接着加载并渲染view
- 应用处于后台时,由于内存等其他原因系统将应用从内存中退出,接着用户又重新启动应用。此时进程和activity需要重启,但会传递state bundle ,可以减少一些资源的创建开销。
我们一般对冷启动方式进行优化,也对应的优化了热启动、温启动的方式,因为冷启动包括了热启动和温启动的流程。
启动优化点
启动有哪些优化点呢?我们可以从冷启动的流程可以进行针对性的优化。冷启动的流程中binder进程间通信、process创建并启动进程、进入ActivityThread启动主线程、创建应用对象Application、创建主Activity这些是系统的流程,我们干预不了,但是我们可以干预:
- 禁用空白窗口
- Application的attach、onCreate;
- Activity的生命周期
- view的加载及绘制
空白Window
启动应用时如果时间比较长,会长时间显示一个空白的预览窗口,给用户很不友好的体验。
解决方案1:禁用创建并显示应用空白启动窗口可以在主题中设置android:windowDisablePreview为false,这样可以减少一定的启动时间开销,但是带来的问题是:如果启动时间比较长可能会出现用户启动应用的过程中没有收到任何反馈,会让用户无法确定应用是否在正常运行。
解决方案2(推荐):不禁用空白窗口,给主题设置一个背景图片android:windowBackground,这样用户看到就不是空白窗口而是应用闪屏图片,一个过渡效果。
优化Application
Application的事务有的在attach、有的在onCreate中进行,我们尽可能不要在Application的启动流程attach、onCreate做耗时的任务,如读写文件、其他io操作、耗时计算、创建大内存对象或者创建大量对象等,因为会严重影响启动时间。在开发大项目的时候会接入各种库,很多库都是建议甚至要求在Application中进行初始化,所以得要权衡。
优化建议:
- 不必要的初始化,在使用时再进行初始化
- 有必要但优先级低的初始化,可以配合闪屏超时逻辑进行延迟初始化,如handler.post or handler.postDelay(runnable, xxx)
- 耗时操作放入子线程,如读写文件、io操作等
- 不要在在Application启动阶段是创建大内存对象或者创建大量对象,因为容易引起内存抖动进而触发GC,GC可能会阻塞所有线程(包括主线程),一般GC阻塞线程时间很短,但频繁的GC,阻塞线程的时间累加起来就多。
优化Activity
启动应用是对Activity的优化与对Application的优化是差不多一样的,不要初始化及onCreate中做没必要、过多的计算,优化建议:
- 不必要的初始化则去除
- 必要的初始化,如果耗时转入子线程中处理
- 不要创建大内存对象或者大量对象
- 对Activity的view进行优化
- 避免过度绘制:避免重叠的view、避免重叠的背景、减少透明度的使用
- 减少view层级,考虑使用constrainlayout、Relativelayout减少层级
- view复用,使用include、merge、viewstub等
- 绘制流程如measure、layout、onDraw中避免创建大内存对象或者大量对象
启动瓶颈检测与分析
启动时间
可以通过在logcat中过滤Displayed查看应用启动时间,Displayed值包括:
- binder进程间通信
- 创建并启动进程
- 启动应用主线程
- 创建应用对象Application,并初始化、生命周期方法
- 创建Activity,并初始化、生命周期方法
- view加载及绘制
logcat中Displayed的值形如:
system_process I/ActivityManager: Displayed com.yuezhuanstore.app/com.fn.demo40new.MainActivity: +499ms
开发如果发现Displayed的值不在设定的范围值,则需要考虑进行启动优化了,以下是各个启动方式启动时间视为过长,需要考虑进行优化:
- 冷启动>=5秒视为启动时间过长(可通过Displayed观测)
- 热启动>=1.5秒视为启动时间过长(自行打印需要的时间)
- 温启动>=2秒视为启动时间过长(可通过Displayed观测)
检测与分析
检测与分析瓶颈的好方法就是使用Android studio CPU性能分析器,在Application启动的生命周期attach或者onCrreate中使用Debug的startMethodTracing()方法开始跟踪记录启动信息,在启动的Activity完成绘制时(getViewTreeObserver().addOnPreDrawListener()作为首帧绘制完成)调用Debug的stopMethodTracing()方法停止追踪启动信息,最终绘制sdcard的应用目录下生产对应的trace文件。
startMethodTracing和stopMethodTracing是成对出现,内部可以出现多对,startMethodTracing可以设置名称作为每对trace文件的名称,如:
public class DemoApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Debug.startMethodTracing("application");
//.....
Debug.stopMethodTracing();
}
}
在主Activity:
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Debug.startMethodTracing("MainActivity");
findViewById(R.id.page_container).getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
findViewById(R.id.page_container).getViewTreeObserver().removeOnPreDrawListener(this);
Debug.stopMethodTracing();
return false;
}
});
}
}
最终会在sdcard/android/data/packagename/files目录中生成了application和MainActivity两个trace文件,追踪记录的是application的onCreate的日志信息、MainActivity的onCreate及首帧绘制的日志信息。
我们将这两个文件导出,通过android studio的cpu性能分析器cpu profiler从本地文件加载trace进行分析。CPU性能分析器怎么检测及分析性能瓶颈,在后续的文件进行分析讲解。
网友评论