应用启动速度优化(二)

再谈谈冷启动速度优化。

这是之前写的另一篇(不好意思,半成品),。对冷启动速度优化有了一些新的认识,所以今天再次旧话重提。

本文的主要内容包括:

  • 优化冷启动速度的关注点
    • Application.onCreate() 中的初始化过程。注意多进程对这个方法的影响
    • Activity.onCreate() 中的初始化过程。重点关注应用的第一个 Activity
  • 冷启动耗时的测量方法
  • 闪屏页的优化方法

启动速度的关注点

应用冷启动过程是这样的:

Android性能优化典范中提到应用开发者能够控制并且需要特别关注的地方主要有三处:

  • Application.onCreate() 流程,对于大型的APP来说,通常会在这里做大量的通用组件的初始化操作
  • Activity.onCreate() 流程,特别是UI的布局与渲染操作,如果布局过于复杂很可能导致严重的启动性能问题
  • 自定义的启动窗口(也就是通常所说的闪屏页)

我根据自己的开发经验补充一个关注点:现在的app动不动就多进程,多进程app会多次调用 Application.onCreate() 方法进行初始化,而实际上某些初始化过程仅仅对主进程才有意义。反复调用 Application.onCreate() 不仅拖慢启动速度,还会无意义地占用内存。

有两种简单的测试 Activity 启动时间的方法:

  • 在 Android Studio logcat 中搜索 ActivityManager: Displayed ... 日志
  • adb logcat -v time | grep 'Displayed com.tencent.PmdCampus/.view.IndexActivity'

小知识:Displayed Time 是界面 inflate 完成的时间,是区别于界面真实加载完成的时间的(比如说,inflate 完成后可能会异步加载图片,图片加载完才算真实加载完成)。如果关注这里的差异的话,一个解决方法是使用 activity.reportFullyDrawn() 告诉系统以便获取真实加载时间。

Method Tracing

上述提到的三个关注点完全适用于我们的app。

  • 首先,我们app在 Application.onCreate() 中初始化了大量第三方 SDK
  • 其次,我们app在 IndexActivity.onCreate() 中加载了复杂布局。IndexActivity 作为首页承载了5个Tab页
  • 最后,我们app使用了不完全合理的闪屏实现方式

Application.onCreate() 中初始化大量第三方 SDK 到底有多耗时呢?需要先测出数据。

使用了一个笨办法,通过 Log.i 输出各个方法调用的耗时。

容易想到另外一个工具,Android Studio 中的 CPU Profiler。但使用 CPU Profiler 的有个问题,它不便于精确分析应用启动时的 cpu 状态。

所以更好的办法是使用 Debug.startMethodTracing()。方法如下:

1
2
3
4
5
6
7
8
9
10
public class MyApplication extends Application {

@Override
public void onCreate() {
super.onCreate();
Debug.startMethodTracing();
...
Debug.stopMethodTracing();
}
}

缺省的文件为 dmtrace.trace,通过如下方式导出来:

adb pull /sdcard/Android/data/<package>/files/dmtrace.trace

Android Studio 3.0 之后 traceview 工具已废弃,我们直接在 Studio 中分析 .trace 文件就可以了。方法如下:

Profiler > SESSIONS > Load from file,然后选择 .trace 文件

Call Chart

Top Down

通过 Top Down 这张图,拎出启动速度慢的罪魁祸首简直易于反掌。名单整理如下:

  • IGameApplication.initBugly() - 初始化 bugly SDK,耗时 389.62.ms
    • RqdHotfix.init() - 389.26ms
  • ReservoirHelper.init() - 初始化 Reservoir 库,耗时 101.43ms
  • LoginHelper.getInstance() - 初始化 Wtlogin SDK,耗时 755.6ms
    • WtloginHelper() - 755.42.ms

首页启动速度优化

首页承载多个 Tab 导致启动慢时,一般通过懒加载的方法来避免启动时立即加载全部 Fragment。可以参考这里,不再赘述。

闪屏页的优化

我们 app 中闪屏页跟首页是分离的。每次启动时都会先显示闪屏页再跳转到首页 (注意是每次,而不仅仅是冷启动时)。 感觉这一做法有背离闪屏页初衷的嫌疑,而且从技术角度上讲多启动了一个 Activity 增加了启动耗时。

  • 闪屏页 - SplashActivity,
  • 首页 - IndexActivity,有5个Tab

更好的做法是将闪屏页与首页合并。要点如下:

  • 创建一个用于闪屏的主题 Laucher 以及一个应用主题 MyApp
  • 创建 laucher_drawable,它包含跟 MyApp 主题相近的背景色以及一个居中的小 logo
  • Laucher 主题使用 laucher_drawable 作为 windowBackground
  • manifest 中将 IndexActivity 的主题设置为 Laucher
  • IndexActivity.onCreate() 方法中使用 setTheme() 方法将主题从 Laucher 切换到 MyApp

注意:在调用 super.onCreate() 之前调用 setTheme()

核心代码如下:

laucher_drawable.xml 文件:

1
2
3
4
5
6
<layer-list>
<item android:drawable="@color/color_app" />
<item>
<bitmap android:src="@drawable/logo" android:gravity="center"/>
</item>
</layer-list>

Laucher 主题:

1
2
3
<style name="AppTheme.Laucher" parent="@style/Theme.AppCompat.NoActionBar">
<item name="android:windowBackground">@drawable/laucher_drawable</item>
</style>

manifest 文件:

1
<activity name=".view.IndexActivity" theme="@style/AppTheme.Laucher" />

IndexActivity.java 文件:

1
2
3
4
5
6
7
8
9
10
11
public class IndexActivity extends AppCompactActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {

// 在 `super.onCreate()` 之调用 `setTheme()`
setTheme(R.style.Theme_MyApp);

super.onCreate(savedInstanceState);
}
}