Skip to content

Latest commit

 

History

History
91 lines (80 loc) · 7.55 KB

introduction.md

File metadata and controls

91 lines (80 loc) · 7.55 KB

FastStartup 实现原理

市场上开源的启动组件已经有很多了,但是可能没有一款特别适合我们的,所以我决定重新撸一个。

1. 核心内容 有向无环图(DAG)

首先是它主要解决的问题:根据启动的依赖关系,排列出一个符合要求的顺序。
C需要依赖A、B,B需要依赖A,那么排序就是A->B->C。 当然这只是一个简单的🌰,实际的依赖关系可能更加复杂,我们可以利用有向无环图(DAG)解决这个问题,
简单来说就是每次都从列表中查找没有被其他任务与依赖的那个任务,简称入度为0的任务, 当其完成后删除其他任务对这个任务的依赖关系,然后再次查找入度为0的任务。
持续这个过程直到所有任务都排序完毕,这样任务就被有序排列了,依次进行初始化就好了。当然这也仅仅是DAG的一个简单例子, 更加详细的算法内容推荐参考有向无环图,或者其他文章,本文不做深入讲解。

2. 第一次优化--添加多线程执行

如果我们的任务很多,同时一次能够检索出来的入度为0的任务也有很多,那么我们怎么样能保证代码执行时间最短呢?当然是多线程。
多线程执行就能够同时执行这些任务,能够在最短的时间内执行完,当执行完毕进行下一次计算,计算出入度为0的任务。

3. 第二次优化--添加运行线程的控制

如果有些任务必须要执行在主线程怎么办?那我们就维护两个队列,一个队列存放需要在主线程执行的任务, 另一个队列存放不需要在主线程执行的任务,每个任务执行完毕后都重新计算出入度为0的任务并添加到相应的队列。 注意每个任务执行完毕后都重新计算出入度为0的任务并添加到相应的队列这个过程需要加🔒控制,不然可能存在多线程并发导致的问题。

4. 第三次优化--添加主线程等待

如果有些任务可以运行在子线程,但是主线程后面的执行操作需要这个任务执行完毕才能进行,比如网络的初始化, 这样我们就需要计算一下主线程有多少个任务需要等待,主线程需要等待所有需要等待的任务执行完毕之后再放行。 综上,主线程到此需要等待的场景有两个:

  1. 需要在主线程执行的任务没有执行完毕
  2. 需要主线程等待的任务没有执行完毕
    private fun dispatchOnMain() {
        var startup: IStartup<*>? = startupSortStore.uiZeroDeque.removeFirstOrNull()
        while (uiThreadTaskSize.get() > 0 || needUIThreadWaitTaskSize.get() > 0) {
            if (startup != null) {
                StartupRunnable(
                    startup, startupConfig, startupSortStore, startupInfoStore, this,
                    startupCostTimesUtils
                ).run()
            } else {
                countDownLatch = CountDownLatch(1)
                countDownLatch?.await()
            }
            countDownLatch = null
            startup = startupSortStore.uiZeroDeque.removeFirstOrNull()
        }
        this.startupCostTimesUtils?.recordUIThreadStartupsEnd()
    }

5. 第四次优化--添加隐私模式

这部分我感觉是最难的,其实思想并不难,难的是如何在最短时间内计算出所有符合隐私要求的所有任务。 在2021这一年我们行业内遇到了一些比较大的变革,其中一个变革就是隐私合规检测,在用户没有同意隐私协议的时候, 我们不能做一些操作,比如获取用户的设备ID、联网、收集设备相关的信息等等,但是我们还可能需要执行一些任务来进行一不涉及到隐私内容的操作, 比如崩溃检测等等,这就要求我们在执行任务的时候需要分三种,一种是自己需要隐私权限的, 一种是自己不需要隐私权限的,其依赖的任务关系中都不需要隐私权限,还有一种是自己不需要隐私权限的,其依赖的任务关系中包含需要隐私权限的任务。 这三种任务需要拆分成两部分来进行执行, 1.自己不需要隐私权限的,其依赖的任务关系中都不需要隐私权限的任务,这种任务在第一次就可以执行 2.己需要隐私权限的、自己不需要隐私权限的,其依赖的任务关系中包含需要隐私权限的任务,这两种任务都要放在第二次执行, 在隐私协议同意之后,进行restart,来执行剩余的任务

6. 第五次优化--添加解藕模式

目前我们很多任务的开发采用组件化的模式进行开发,组件化开发有一个好处就是可以组件更加专注自己的功能开发。 而在这个过程中组件又需要依赖其他组件来进行协作,一种比较好的形式就是采用接口的形式进行依赖, 组件依赖其他组件提供的接口来进行开发,这部分也不深入讲解了。 FastStartup中依赖关系这部分也是一样,如果本组件需要依赖其他组件,也仅仅是需要依赖一个接口,然后在内部会用一个Map维护接口和实例之间的关系。 在内部真实计算依赖关系的时候其实是会转化为具体实例对象。 比如IA是A类接口,IB是B类接口,B任务只需要依赖IA,内部就会自行转化为B依赖A。具体内容可以阅读源码。

7. 第六次优化--添加各种类型任务执行完毕的监听

添加了UI任务、所有任务执行完毕的监听,又添加了每一个任务执行完毕的监听

8. 第七次优化--添加任务执行完毕的返回值的获取

在内部维护了一个Map用来保存每一个任务执行完毕的返回值,用来供给其他组件使用

9. 第八次优化--添加任务耗时统计

在每一个任务执行前和执行后分别记录一个时间, 在内部维护了一个Map用来保存每一个任务执行耗时。

10. 第九次优化--添加执行任务时的参数携带

在执行有些任务的时候需要携带一些参数,比如你使用某统计SDK,初始化的时候就需要传入一些ID之类的, 某些SDK需要传入Debug状态,但是aar中又没有办法很好获取Debug状态,针对种种场景。特意配置了三个参数Application, isDebug,Object,三种,其中object可以是任意类型。这三个参数会在执行过程中传递到各个入任务中,满足了大部分场景的使用。

11. 第十次优化--添加依赖缺失和环检测

有时候我们可能一不小心(其他人坑我)配置成了环依赖,A->B,B->A, 或者B->IA,但是初始化的时候忘记了添加A,等等场景,又不知道丢了哪个,太头疼。
针对这个问题特意添加了环检测和依赖缺失检测,能够通过算法帮你计算出哪个任务出了问题,并能够同时打印出问题的任务的依赖关系,很是贴心。

12. 第十一次优化--添加依赖关系打印

有的时候我们可能排查一些问题需要知道当前所有组件的依赖关系图,针对这一场景特意添加了打印依赖关系图这个功能。这样就不用一个一个的任务去找了。

13. 第十二次优化--添加AOP方案自动注入组件,实现依赖即注入

通常我们要执行哪些任务需要手动添加到列表中,然后在传入FastStartup中,但是大厂一般都有流水线或者集成工具。 需要集成哪些组件可能动态配置,这时候再手动去维护这些任务就不太何时,所以针对这一场景开发了Gradle插件,能够通过一个注解, 就自动完成需要执行的组件,很是方便。