插件化之启动优化实践

1、引言

插件化是一把双刃剑,引入插件化实现后,每个APP都会面临插件框架带来的启动性能问题。性能问题不局限于以下所列项:

  1. ART Runtime上首次启动,dex2aot耗时问题;
  2. 内致的静态插件和部分SDK,启动时作了大量的初始化操作,表现为Splash页停留5s+以上;

微店也不例外,在插件化运行初期,微店APP仅静态插件加载时长超5s的每日量达2w+,另个一个店长版APP更不乐观。针对启动时长,我们尝试做了以下优化。

2、启动优化

优化前,微店APP启动流程可简化成下图所示。APP在Applicaction的attachBaseContext,onCreate以及SplashActivity的onStart,onWindowFocusChanged函数中实现了插件的加载和SDK的初始化。

  1. application attachBaseContext阶段:Muitidex处理后,在异步线程,插件框架收集所有静态插件信息(插件文件,插件组件解析)并进行loadDex等操作;
  2. application onCreate阶段:执行必要的插件以及核心SDK初始化代码,以保证后续功能调用成功,这里的核心SDK包括诸如网络,Crash上报库,埋点库等;
  3. activity onStart阶段:这里再细化为主线程和异步线程,执行部分SDK的初始化,比如Fresco;
  4. activity onWindowFocusChanged阶段:这一阶段执行不太紧急的SDK初始化,比如IM长连接,第三方统计库等。

在这一阶段,已经做到了SDK分层加载,但还是不太理想,主要表现在:

  1. application attachBaseContext阶段,所有插件都主动加载,并未按需加载或懒加载,比如App里的足迹插件,其实可以做为懒加载插件,在启动足迹页时,进行插件加载;
  2. application onCreate阶段,大量的SDK初始化,直接加长了启动时长,直接表现为用户看到的splash图其实是window背景,splash页一直在loading中;
  3. 在Splash Activity周期函数中执行了SDK初始化代码,这里在绝大数场景下是没问题。但在自动化测试过程,可能不会先跳splash页,会引起由于SDK没初始化,导致异常表现。

在收到华为市场启动时长过长警告后(华为要求电商APP启动时长不能超2s),决定优化启动流程。

3、优化设计

针对上章节问题,对流程重构,设计了以下流程。

插件分为懒加载插件和非懒加载插件,启动时仅对非懒加载插件进行加载,懒加载插件仅做校验等工作。
在Application onCreate阶段,不再在主线程执行SDK初始化,在工作线程执行完核心SDK始化后,启动了多个其他工作线程对其余SDK进行操作;
在首个Activity onResume中对必须在主线程执行的SDK进行操作;

4、优化实现

代码实现其实做了很多工作,非常优秀的团队小伙伴实现了包括ART上首次启动禁用dex2aot优化[1],应用秒开优化[2]。一些核心实现简述如下:

  1. ART首次启动优化,主要思路hook runtime->image_dex2oat_enabled_实现动态禁用dex2aot,由于Android版本多,rom包碎片化,这里涉及到大量的适配工作,主要在于runtime的偏移量矫正;
  2. 在启动后,会在工作进程对所有插件进行dex2aot操作[3];
  3. APP hook instrumentation, 在第一个Activity实例化时,如果SDK init工作还未完成,先启动一个HookedActivity,等所有工作完成后,再启动业务Splash页。

如果是拦截启动了hookedActivity,一定要在hookedInstrumentation.callActivityOnCreate方法重置theme为hookActivity自身主题,否则可能会抛出异常,原因我们可以看ActiviyThread.performLaunchActivity的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
.....
//r是ActivityRecord,r.activityInfo是从目标activity组件信息里读取的
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
activity.setTheme(theme);
}
activity.mCalled = false;
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}

.....
}

经过一系列的优化后,我们拿到了比较理想的数据。从1w的采样数据我们发现,绝大多数启动在2s以内,有一定的量分布在10s以上。10s以上认定为不太正常的统计数据,通过onWindowFocusChanged方式无法适配Push唤醒,黑屏等场景。下一步的想法是只统计launcher程序启动APP时长,忽略其他唤醒方式,异常数据可能会大量减少。

参考

[1] art dex2oat 加载加速浅析:https://fucknmb.com/2018/12/30/art-dex2oat%E5%8A%A0%E8%BD%BD%E5%8A%A0%E9%80%9F%E6%B5%85%E6%9E%90/
[2] Android端应用秒开优化体验:http://zhengxiaoyong.com/2016/07/18/Android%E7%AB%AF%E5%BA%94%E7%94%A8%E7%A7%92%E5%BC%80%E4%BC%98%E5%8C%96%E4%BD%93%E9%AA%8C/
[3] atlas:https://github.com/alibaba/atlas