优化方案
继上一篇的 Android 启动优化(一) 小试牛刀(优化预览窗口)之后,我们再进一步优化应用。
代码规整
由于业务需求快速迭代,新增的组件初始化代码陆陆续续叠加至 BaseApplication
里,代码臃肿较难维护。之前代码中的两个 Application
类代码量累加接近 1k ,没有规范的流程和管理使得代码可读性变差,小修小改容易牵一发而动全身。
兼顾好启动优化的同时也需要规范组件初始化的代码,做到能统一管理,易维护。
注册表
目前采用的方案是在启动注册表 AppInitialTable
中注册启动项。
每一个启动项由 AppInitialTask
统一管理和执行。
在合适的加载时机获取 启动注册表 中不同的 初始化任务 并且执行。
public class AppInitialTable {
public ArrayList<AppInitialTask> getVitalTask() {
String className = "com.xxx.Initializer";// 该类放置的都是优先级最高的启动项任务
ArrayList<AppInitialTask> vitalList = new ArrayList<>();
vitalList.add(new AppInitialTask(className, "initBugly"));
vitalList.add(new AppInitialTask(className, "initBuriedPoint"));
vitalList.add(new AppInitialTask(className, "initHttpComponent"));
vitalList.add(new AppInitialTask(className, "initImageComponent"));
...
return vitalList;
}
getSubThreadTask()..
}
启动任务
每个启动项都由 AppInitialTask
中的 execute()
方法统一由 反射 调用执行。
反射执行方法耗时是普通方法调用的 3 ~ 4倍,但因为启动项并不会忒多,而且调用 1 个方法执行的时间是 0.01ms 以下级别,总的来说当前方案不会影响到整体性能。
public class AppInitialTask {
public void execute() {
try {
reflectMethod();
printLog("方法执行打印");//统一处理,包括方法耗时和执行情况
} catch (Throwable e) {
e.printStackTrace();
// 打印并上报启动失败项
}
}
private void reflectMethod() throws Exception {
Class<?> apiClass = Class.forName(className);
Method method = apiClass.getMethod(methodName, methodCls);
method.setAccessible(true);
Object Obj = apiClass.newInstance();
method.invoke(Obj);
}
}
优点
由注册表 AppInitialTable
+ 启动任务 AppInitialTask
管理的启动项有以下几个便利之处:
- 每个初始化方法由
AppInitialTask
统一执行。 (统一加 log,记录方法执行时间等) - 每个初始方法都 try/catch ,崩溃或者错误不会影响到其它。(并上报bugly)
- 可以调用到不同模块的初始方法,不用将代码移动到 base 模块 (组件化)。
- 低耦合,可复用。 启动项相互隔离,便于方法 CRUD,减少初始化通用类的开销,易维护。
如果使用常规做法 : 抽象类规定启动项执行方法 ,每次新增的启动项都要继承抽象类,会导致类爆炸 (有多少启动项就有多少个类),并且 2 3 行的启动方法也需要新建一个类,代码编写难以规范并且不利于统一管理。
代码规整之后, Application
代码清晰简洁(100+ 行代码),启动项皆由 AppInitialTable
接管,控制台中可以查看启动情况和方法耗时。
优化流程
启动项代码整理完毕后,剩下的就是在合适的加载时机去初始化注册表中的启动任务了。
从桌面点击 应用图标 到进入主页可以分为 3 个时间段:
- T1 : Application 中初始化关乎应用命脉的组件
- T2 : LaunchActivity (闪屏页)中分担主页耗时任务
- T3 : 主页 加载复杂布局和模块,初始化相关逻辑直至用户可操作状态。
T1 时期在Application中可以将初始化项优先级不高的操作放置子线程中加载,避免启动过程中响应太久。
T2 时期 LaunchActivity 可以适当的分担主页主线程中的 UI 初始化耗时操作(耗时分割)。
待进入主页后,在主页空闲时期加载优先级较低的启动项,例如定位相关。
空闲时期可以通过 IdleHandler
的回调来执行 优先级较低的启动项,此时主页视图已经绘制并加载完毕。
Looper.myQueue().addIdleHandler(() -> {
for (AppInitialTask task : new AppInitialTable().getIdleTask()) {
task.execute();
}
return false;// true : 下次handle空闲时会继续回调
});
小结
- 启动优化后的测试可以延迟 (sleep 1~3s) 不同阶段的启动项加载时机,避免因为没有启动导致其它组件的使用崩溃或者数据相关的功能问题。(数据准确性)
- 迭代性的代码需要统一规范和规整,否则容易东拼西凑添加代码导致整理臃肿难以管理。
- 优化应用的得力助手是对问题的监控,如果可以规范和统一监控app的各个方法执行耗时,优化会更得心应手。(三分靠优化,七分靠监控.)