相信很多人在被问到这个问题的时候,心里面能想到的就只有开启异步线程去做sdk的初始化,其他可以优化的点绞尽脑汁也想不到,其实在没有系统的学习framework之前,关于这些方面我能做的也少的可怜,不过经过系统性的学习后,我觉得今天可以给大家提供一些比较好的思路
想要做启动优化就需要知道在启动过程中都经历了哪些步骤,哪些步骤我们是可以参与进来的,下面就简单的介绍一下App 启动过程中都需要经过哪些步骤
1.组装 request ,显示黑白屏 , 此时可以通过 Theme 中的windows 属性将黑白屏变成我们想要的背景
2. zygote 创建进程 这个过程完全是有 SystemServer 来决定的,不能人为的进行干扰,
3.进程创建后启动 ActivityThread ,创建 context ,调用 AMS attachApplication 绑定进程, 同时解析所有的provider, 这里也是由 ActivityThread 来决定的,由于没有任何可以获得实例化的接口,所以这个过程也无法操作
4.AMS 调用 ApplicationThread 的 bindApplication方法, 在这里 就到了Application 的入口了,而且这里进行了非常重要的三步,下面贴一下代码
(1)创建 Application 调用 application 的attch 方法, 向下传递到Application.attachBaseContext 方法
ActivityThread.handleBindApplication()
app = data.info.makeApplication(data.restrictedBackupMode, null);// TODO 创建Application
-->LoadedApk.makeApplication()
app = mActivityThread.mInstrumentation.newApplication( cl, appClass, appContext);反射创建Applicaiton
-->Instrumentation.newApplication()
app.attach(context);
--> Application.attachBaseContext(context);
从上面代码的逻辑能分析出来,Application 第一个被调用的方法就是 attachBaseContext 这个方法,而在5.0之前如果出现了65526 方法个数超限的这个问题时,也是在这里解决的,现在已经放弃了这种机制了,如果在项目中没有使用 插件化的方案,这里其实也是没有什么需要改动的,但是如果使用了插件化的方案,由于在这里需要进行反射,必然就会带来加载性能上的问题,这个也是无法避免的,毕竟慢一点总比崩溃强,
说道插件化,可能很多人并不知道他的原理,在这里就来大体的说一下插件化的实现方案,以及为什么需要在 attachBaseContext 进行反射
在类的加载过程中的ClassLoader 可以使用的PathClassLoader 与 DexClassLoader , 但是他们的loadClass 方法没有实现,在调用findclass 的方法都调用到了BaseDexClassLoader, 在加载类的过程中由于我们所需要加载的类在系统中没有,就需要调用 BaseDexClassLoader 他自身的findclass 方法, BaseDexClassLoader 构造方法中存在一个DexPathList 这样一个数据,里面存在的是 Element[] 数组,在findclass 的过程是遍历这个数组找到需要的类,如果找到了就直接返回,不回继续去加载,这也就给插件化方案一个漏洞,如果我把我需要修改的dex 插入到这个Element[]数组的前面,那么 BaseDexClassLoader 在findclass 的过程中就会先遍历我的修复包,找到后退出,那么有问题的包就永远也不会被加载,这样问题就修复了具体代码逻辑如下
public Class findClass(string name, List<Throwable>suppressed){
for (Element element : dexElements)
fDexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
if (dexElementsSuppressedExceptions != nul1) {
suppressed.addA11(Arrays.asList(dexElementsSuppressedExceptions))
}
return null;
}
但是随之而来的问题就是在系统在加载我们的类之前,我们就需要将新的dex 插入进去,而且是必须每次打开app 都需要执行插入这个动作,至于为什么要在 attachBaseContext 里面进行,因为他是我们可以接触到的第一个方法,为了防止 classload 将错误的类加载进来,我们就需要将这个事情提前到所有方法之前
(2) installContentProviders 这段代码就在 创建Application 之后,也就是说,如果apk中如果大量的使用了provider 的话,这会极大的导致app启动速度变慢,又看过字节他们的方案是将FileProvider 的初始化放到了使用时在初始化,可以见得他们除了这个provider其他都是内部的库,没有使用provider这种方法,所有的provider是通过aidl 的binder 传递到ActivityThread mBoundApplication 这个数据上,如果我们可以在 attachBaseContext 通过反射获取到这个list ,并将这list保存起来,同时将这个list 设置为空,那么就不在会初始化provider了,同时可以在适当的时机通过反射在来初始化相应的代码,但是在实际开发过程中,这种操作也只能在隐私权限中使用,其他的就会导致我们调整非常多的初始化方案有点得不偿失,而且我在分析了我原来公司的apk后发现,虽然我们自己就使用FileProvider ,但是合并后的清单文件中的 provider 一共有20多个,如果想要做到极致的优化,相信这里还是有非常多的可以优化的地方的
(3) Application.onCreate
进行到了这里相信绝大多数人就已经非常清楚了,而且大家也都非常有经验了,但是对于开启线程来初始化三方库的过程,如果想要分析清楚,或者有什么比较好的方案,我会在后续详细的出一篇相应的文章,而且现在市面上大多数app都会有一个广告页面,为什么呢,仅仅只是为了推广的广告费用吗,其实他们将很大一部分初始化的工作放到了这里
我们需要解决的问题如下
1. 一个任务在随意插入任务队列后,如何保证他的执行顺序
2.一个任务想要执行,如何明确他的前置任务已经执行完毕
3.后置任务使用前置任务的结果,如同解耦获取前置任务结果
4.如何保证多线程调度任务的准确性
5.如何准确的控制初始化任务的节点,在特定任务执行完成后,恢复主线程开启开始启动app,后续初始化工作继续进行,但是不影响首屏工作
这里我写了一个简单的启动任务构建器,上面写了非常多的注释,大家可以下载看一下
https://github.com/tsm1991/StartUp ,当然他还要很多的不足,希望大家提出自己的意见
5. 启动Activity
到了这里可以优化的点我想到的有3个
网友评论