美文网首页Android FrameWork 学习
9.基于FrameWork 的App 终极启动优化

9.基于FrameWork 的App 终极启动优化

作者: Tsm_2020 | 来源:发表于2023-08-24 00:51 被阅读0次

相信很多人在被问到这个问题的时候,心里面能想到的就只有开启异步线程去做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个

(1) 在广告页面或者 application 中提前加载 首页需要的简单数据,快速相应,在进入首页后快速填充,看了一下京东 淘宝都是这么做的,即使没有网络也会有简单的数据填充,不让用户等待,这是一个非常好的操作
(2)AsyncLayoutInfter 异步加载布局, 了解了ViewRootImpl 的原理就知道,在Activity onCreate 的过程中由于 ViewRootImpl 还没有创建,我们将View 的创建与初始化的工作放到了异步线程来创建和初始是没有问题,但是需要注意的是在 一些自定义View 中的handelr 需要绑定 Looper.getMainLooper(),由于子线程中没有looper ,此时创建Handler 就会报错,也可以使用X2C 的方式来加速首页控件的速度
(3) Message 相信大家肯定都知道android 系统都是基于Message 来运行的,那么如果某些sdk 在初始化的过程中即使你是在异步线程中初始化的,但是由于他创建了一个主线程的handler ,并通过他来做一些耗时不多但是比较频繁的事情,也会导致Activity 的启动时机延后,此时我们就可以 在Applicaiton 的attachBaseContext方法最前面添加下面代码 Looper.getMainLooper().setMessageLogging. 来查看具体是哪个sdk 使用了handler来发送消息,适当延后这个sdk的初始化工作

相关文章

网友评论

    本文标题:9.基于FrameWork 的App 终极启动优化

    本文链接:https://www.haomeiwen.com/subject/oncmmdtx.html