• 每天记录学习的新知识:AppInit


    前言

    是官方Demo中的内容,拷贝出来便于查看
    一、AppInit:Android 应用初始化框架
    二、AppInit-使用文档
    三、AppInit-设计文档

    第四部分是个人使用记录
    四、个人框架使用和理解

    一、AppInit:Android 应用初始化框架

    [Download ][(https://api.bintray.com/packages/bingoogolapple/maven/bga-appinit-plugin/images/download.svg)]
    :https://bintray.com/bingoogolapple/maven/bga-appinit-plugin/_latestVersion

    [PRs Welcome][(https://img.shields.io/badge/PRs-welcome-brightgreen.svg)]
    https://github.com/bingoogolapple/AppInit/pulls

    AppInit 是一款 Android 应用初始化框架,基于组件化的设计思路,功能灵活,使用简单。

    AppInit 用于解决美团收银 B 端 App 在业务演进过程中的实际问题,取得了不错的效果,因此我们决定将其开源,希望更多技术同行一起开发,应用到更广泛的场景里去。

    背景

    随着业务的快速发展,新项目新业务不断出现,以及项目组件化的实施,项目里需要初始化的业务模块和 SDK 也逐渐增多,而且有些业务模块间可能有着错综复杂的依赖关系,在项目开发和测试人员不足、新加入开发同学不熟悉项目的情况下,难免会出现少测漏测的情况,如何使各模块初始化代码解耦、按正确的顺序初始化是我们需要思考的问题。

    功能简介

    • 可以在指定进程的指定线程,按指定顺序分发 Application 生命周期方法给初始化类(继承自 SimpleAppInit 并添加 AppInit 注解,低耦合)
    • 可以配置各模块间的初始化顺序,模块内部自己管理各初始化类的顺序,也可配置在其他模块的某个初始化类之前初始化(编译期间排序,运行期高性能)
    • 可以在应用启动时拉取配置信息动态修改初始化顺序,及时修复线上包初始化顺序错乱问题(高稳定)
    • 可以统计各模块和具体初始化类的初始化时间,便于优化冷启动时间

    TODO

    • 非主 dex 懒加载
    • 初始化线程池优化

    业界初始化方案对比

    在这里插入图片描述

    设计与使用文档

    使用文档和设计文档也记录到下面了。

    更新日志

    # 更新日志
    
    ## Version 1.0.8 (2020-04-05)
    
    -JCenter 迁移到 JitPackGradle 依赖由「cn.bingoogolapple:bga-appinit-plugin」变为「com.github.bingoogolapple.AppInit:buildSrc」
    
    ## Version 1.0.7 (2020-03-26)
    
    - 改由个人账号维护,Gradle 依赖由「com.sankuai.erp.component:appinit-plugin」变为「cn.bingoogolapple:bga-appinit-plugin」
    - v1.0.7 扫描 jar 包时捕获一下异常
    
    ## Version 1.0.6 (2019-10-29)
    
    - v1.0.6 fix #15 兼容 gradle 4.1
    
    ## Version 1.0.5 (2019-04-14)
    
    - v1.0.5 修复 Windows 下 rebuild 时提示无法删除 app\build\intermediates\transforms\xxx\debug\xxx.jar
    
    ## Version 1.0.4 (2019-04-13)
    
    - v1.0.4 去掉对 commons-io:2.6 的依赖
    
    ## Version 1.0.3 (2019-02-19)
    
    - Fix #10 兼容 rootProject 目录以数字开头
    
    ## Version 1.0.2 (2019-01-30)
    
    - Fix #6 minSdkVersion 改为 16
    
    ## Version 1.0.1 (2019-01-25)
    
    - Fix #1 不支持增量编译的 Transform 在消费上游文件前需要先清除输出目录的文件
    - Fix #3 Transform 里获取 Variant 的方式可以更优雅一些
    
    ## Version 1.0.0 (2019-01-23)
    
    - 第一个开源版本
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    免责声明

    AppInit 于 2019 年 1 月 21 日以公司名义在 Meituan-Dianping 账号下开源 ,并于 2020 年 1 月 13 日停止从 Meituan-Dianping 账号开源。

    由于之前开源的 1 年时间内已经有开发者将 AppInit 应用到了实际项目中,后续业务项目升级 Gradle 版本后可能也需要 AppInit 升级进行兼容,且在之前开源的 1 年时间内已经大量 Fork,因此再次 Fork 一份到个人账号下进行维护。代码包名不变,为了便于后续上传 JitPack,Gradle 依赖由「com.sankuai.erp.component:appinit-plugin」变为「com.github.bingoogolapple.AppInit:buildSrc」

    在这里插入图片描述

    二、AppInit-使用文档

    1、AppInit 接入

    • 根工程下的 build.gradle 中添加如下依赖:
    classpath "com.github.bingoogolapple.AppInit:buildSrc:最新版本号"
    
    • 1

    20220908最新版本:1.0.8

    • 对应的 module 下的 build.gradle 中应用插件(相当于引入依赖)
    apply plugin: 'bga-appinit-plugin'
    
    • 1
    • 如果 module 中有用到 kotlin,需在 module 下的 build.gradle 中应用 kapt 插件
    apply plugin: 'kotlin-kapt'
    
    • 1
    • 在 AndroidManifest.xml 中配置的 Application 中添加如下代码

    Kotlin版:

    class App : Application() {
    
        override fun onCreate() {
            super.onCreate()
            Log.d(TAG, "onCreate: ")
    
            AppInitManager.get().init(this, object : SimpleAppInitCallback() {
    
                /**
                 * 开始初始化
                 *
                 * @param isMainProcess 是否为主进程
                 * @param processName   进程名称
                 */
                override fun onInitStart(isMainProcess: Boolean, processName: String?) {
                    // TODO 在所有的初始化类之前初始化
                    AppInitLogger.d("onInitStart")
    
                }
    
                /*
                * 是否为 debug 模式
                */
                override fun isDebug(): Boolean {
                    return true
                }
    
                /**
                 * 通过 coordinate 自定义依赖关系映射,键值都是 coordinate。「仅在需要发热补的情况下才自定义,否则返回 null」
                 *
                 *  排序含义: KEY 在 VALUE 前面
                 *
                 * @return 如果返回的 map 不为空,则会在启动是检测依赖并重新排序,返回null也行
                 */
                override fun getCoordinateAheadOfMap(): MutableMap<String, String> {
                    val coordinateAheadOfMap: MutableMap<String, String> = HashMap()
    //                coordinateAheadOfMap["Demo_AppInit:module3:Module3ThreeInit"] =
    //                    "Demo_AppInit:lib_behavioural_analysis:Behavioural2Init"
    
    //                                coordinateAheadOfMap[ "Demo_AppInit:lib_behavioural_analysis:Behavioural2Init"] =
    //                                    "Demo_AppInit:module3:Module3ThreeInit"
    
                    return coordinateAheadOfMap
                }
    
                /**
                 * 同步初始化完成
                 *
                 * @param isMainProcess      是否为主进程
                 * @param processName        进程名称
                 * @param childInitTableList 初始化模块列表
                 * @param appInitItemList    初始化列表
                 */
                override fun onInitFinished(
                    isMainProcess: Boolean,
                    processName: String?,
                    childInitTableList: MutableList<ChildInitTable>?,
                    appInitItemList: MutableList<AppInitItem>?
                ) {
                    super.onInitFinished(
                        isMainProcess,
                        processName,
                        childInitTableList,
                        appInitItemList
                    )
                    // 获取运行期初始化日志信息
                    val initLogInfo: String =
                        AppInitApiUtils.getInitOrderAndTimeLog(childInitTableList, appInitItemList)
                    AppInitLogger.d("onInitFinished :$initLogInfo")
    
    
    
                }
            })
        }
    
        override fun onTerminate() {
            super.onTerminate()
            AppInitManager.get().onTerminate()
        }
    
        override fun onConfigurationChanged(newConfig: Configuration) {
            super.onConfigurationChanged(newConfig)
            AppInitManager.get().onConfigurationChanged(newConfig)
        }
    
        override fun onLowMemory() {
            super.onLowMemory()
            AppInitManager.get().onLowMemory()
        }
    
        override fun onTrimMemory(level: Int) {
            super.onTrimMemory(level)
            AppInitManager.get().onTrimMemory(level)
        }
    
        companion object {
            private const val TAG = "App"
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101

    2、AppInit 使用

    编写初始化类继承 SimpleAppInit,并添加 AppInit 注解,根据具体初始化场景重写相应方法。这里以初始化 Router 举例

    Java :

    @AppInit(
        priority = 40,//优先级
        description = "初始化路由",
        onlyForDebug = false,
        process = Process.MAIN
    )
    class RouterInit : SimpleAppInit() {
    
        override fun onCreate() {
            Log.d(TAG, "RouterInit onCreate: ")
            // SimpleAppInit 中包含了 mApplication 和 mIsDebug 属性,可以直接在子类中使用
    //        Router.initialize(mIsDebug)
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2.1、AppInit 注解属性说明

    属性描述默认值
    description初始化的描述信息“”
    aheadOf在指定初始化项之前初始化,用于整个项目范围内重新排序。生成规则为「模块唯一标识:初始化SimpleName」“”
    onlyForDebug初始化是否仅在 debug 时可用false
    process在哪个进程初始化。可选值为 Process.MAIN(主进程)、Process.ALL(所有进程)、Process.OTHER(其他进程)Process.MAIN(主进程)
    priority模块内部范围内初始化类的优先级,值越小越先初始化

    2.2、SimpleAppInit 方法说明

    所有方法都是可选的,都只会运行在你注册的进程

    方法描述
    boolean needAsyncInit()是否需要异步初始化,默认为 false
    void asyncOnCreate()Application#onCreate() 时异步调用
    void onCreate()Application#onCreate() 时同步调用
    void onConfigurationChanged(Configuration newConfig)Application#onConfigurationChanged(Configuration) 时调用
    void onLowMemory()Application#onLowMemory() 时调用
    void onTerminate()Application#onTerminate() 时调用
    void onTrimMemory(int level)Application#onTrimMemory(int) 时调用

    3、AppInit 依赖配置

    3.1、唯一标识(坐标)生成规则

    3.1.1、模块唯一标识(模块坐标)生成规则
    • 未发布到 maven 仓库时生成规则为「模块父目录名称:模块名称」,如:

    在这里插入图片描述

    • 发布到 maven 仓库后生成规则为「POM_GROUP_ID:POM_ARTIFACT_ID」「GROUP_ID:ARTIFACT_ID」,如:

    在这里插入图片描述

    在这里插入图片描述

    3.1.2、初始化类唯一标识(初始化类坐标)生成规则
    • 「模块唯一标识:初始化类的SimpleName」,如:

    Maven
    在这里插入图片描述

    本地
    在这里插入图片描述

    3.2、配置初始化顺序

    3.2.1、配置模块初始化顺序
    • 对应模块的 gradle.properties 中配置当前模块所依赖的其他模块,多个依赖用英文逗号分隔
    APP_INIT_DEPENDENCIES=com.sankuai.erp.component:appinit-test-module1,appinit:module2
    
    • 1
    • 在壳工程的 build.gradle 中配置 appInit 参数,这种方式会覆盖通过 APP_INIT_DEPENDENCIES 配置的依赖
    appInit {
        // 自定义模块间初始化的依赖关系,会覆盖通过 APP_INIT_DEPENDENCIES 配置的依赖关系
        dependency([
                'demo:app'        : ['com.sankuai.erp.component:appinit-test-module1', 'appinit:module2'],
                'appinit:module2' : 'com.sankuai.erp.component:appinit-test-module3'
        ])
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    3.2.2、配置初始化类的顺序
    • 添加 AppInit 注解时配置 priority 属性,指定模块内部范围内初始化类的优先级,值越小越先初始化
    • 添加 AppInit 注解时配置 aheadOf 属性,指定项目范围内在其他初始化类之前初始化,优先级高于 priority
    • 重写 SimpleAppInitCallback 的 getCoordinateAheadOfMap 方法,在运行期动态配置初始化顺序
    /**
     * 通过 coordinate 自定义依赖关系映射,键值都是 coordinate。「仅在需要发热补的情况下才自定义,否则返回 null」
     *
     * @return 如果返回的 map 不为空,则会在启动时检测依赖并重新排序
     */
    @Override
    public Map<String, String> getCoordinateAheadOfMap() {
        Map<String, String> coordinateAheadOfMap = new HashMap<>();
        coordinateAheadOfMap.put("appinit:module2:Module2FiveInit", "cn.bingoogolapple:appinit-test-module1:Module1FiveInit");
        return coordinateAheadOfMap;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    3.2.3、根据日志调整初始化顺序
    • 根据编译期日志调整初始化顺序。
      build 或 assemble 时会在控制台和 「壳工程/build/AppInitLog」目录下生成对应 Variant 的初始化顺序日志文件 「Variant.log」
    2018-11-20 11:43:53
    
    处理 aheadOf 前的顺序为:
        《com.sankuai.erp.component:appinit-test-module1》[priority=1]
            * [com.sankuai.erp.component:appinit-test-module1:Module1FiveInit][20][进程=MAIN][description=模块15的描述]
            * [com.sankuai.erp.component:appinit-test-module1:Module1TwoInit][90][进程=OTHER][description=模块12的描述]
            * [com.sankuai.erp.component:appinit-test-module1:Module1OneInit][100][进程=MAIN][aheadOf=com.sankuai.erp.component:appinit-test-module1:Module1TwoInit][description=模块11的描述]
            * [com.sankuai.erp.component:appinit-test-module1:Module1ThreeInit][300][进程=MAIN][description=模块13的描述]
            * [com.sankuai.erp.component:appinit-test-module1:Module1FourInit][300][进程=MAIN][description=模块14的描述]
        《com.sankuai.erp.component:appinit-test-module3》[priority=2][dependencies=com.sankuai.erp.component:appinit-test-module1]
            * [com.sankuai.erp.component:appinit-test-module3:Module3FiveInit][5][进程=MAIN][description=模块35的描述]
            * [com.sankuai.erp.component:appinit-test-module3:Module3FourInit][10][进程=MAIN][description=模块34的描述]
            * [com.sankuai.erp.component:appinit-test-module3:Module3ThreeInit][50][进程=MAIN][description=模块33的描述]
            * [com.sankuai.erp.component:appinit-test-module3:Module3OneInit][60][进程=ALL][description=模块31的描述]
            * [com.sankuai.erp.component:appinit-test-module3:Module3TwoInit][60][进程=MAIN][aheadOf=com.sankuai.erp.component:appinit-test-module1:Module1OneInit][description=模块32的描述]
        《appinit:module2》[priority=3][dependencies=com.sankuai.erp.component:appinit-test-module3]
            * [appinit:module2:Module2FiveInit][10][进程=MAIN][description=模块25的描述]
            * [appinit:module2:Module2FourInit][40][进程=MAIN][aheadOf=com.sankuai.erp.component:appinit-test-module3:Module3FourInit][description=模块24的描述]
            * [appinit:module2:Module2TwoInit][70][进程=MAIN][description=模块22的描述]
            * [appinit:module2:Module2ThreeInit][80][进程=MAIN][description=模块23的描述]
            * [appinit:module2:Module2OneInit][80][进程=ALL][description=模块21的描述]
        《demo:app》[priority=4][dependencies=appinit:module2, com.sankuai.erp.component:appinit-test-module1]
            * [demo:app:RouterInit][40][进程=MAIN][description=初始化路由]
            * [demo:app:AppFirst][1000][进程=MAIN][description=壳工程最先初始化服务]
    
    最终的初始化顺序为:
        《com.sankuai.erp.component:appinit-test-module1》[priority=1]
            * [com.sankuai.erp.component:appinit-test-module1:Module1FiveInit][20][进程=MAIN][description=模块15的描述]
        《com.sankuai.erp.component:appinit-test-module3》[priority=2][dependencies=com.sankuai.erp.component:appinit-test-module1]
            * [com.sankuai.erp.component:appinit-test-module3:Module3TwoInit][60][进程=MAIN][aheadOf=com.sankuai.erp.component:appinit-test-module1:Module1OneInit][description=模块32的描述]
        《com.sankuai.erp.component:appinit-test-module1》[priority=1]
            * [com.sankuai.erp.component:appinit-test-module1:Module1OneInit][100][进程=MAIN][aheadOf=com.sankuai.erp.component:appinit-test-module1:Module1TwoInit][description=模块11的描述]
            * [com.sankuai.erp.component:appinit-test-module1:Module1TwoInit][90][进程=OTHER][description=模块12的描述]
            * [com.sankuai.erp.component:appinit-test-module1:Module1ThreeInit][300][进程=MAIN][description=模块13的描述]
            * [com.sankuai.erp.component:appinit-test-module1:Module1FourInit][300][进程=MAIN][description=模块14的描述]
        《com.sankuai.erp.component:appinit-test-module3》[priority=2][dependencies=com.sankuai.erp.component:appinit-test-module1]
            * [com.sankuai.erp.component:appinit-test-module3:Module3FiveInit][5][进程=MAIN][description=模块35的描述]
        《appinit:module2》[priority=3][dependencies=com.sankuai.erp.component:appinit-test-module3]
            * [appinit:module2:Module2FourInit][40][进程=MAIN][aheadOf=com.sankuai.erp.component:appinit-test-module3:Module3FourInit][description=模块24的描述]
        《com.sankuai.erp.component:appinit-test-module3》[priority=2][dependencies=com.sankuai.erp.component:appinit-test-module1]
            * [com.sankuai.erp.component:appinit-test-module3:Module3FourInit][10][进程=MAIN][description=模块34的描述]
            * [com.sankuai.erp.component:appinit-test-module3:Module3ThreeInit][50][进程=MAIN][description=模块33的描述]
            * [com.sankuai.erp.component:appinit-test-module3:Module3OneInit][60][进程=ALL][description=模块31的描述]
        《appinit:module2》[priority=3][dependencies=com.sankuai.erp.component:appinit-test-module3]
            * [appinit:module2:Module2FiveInit][10][进程=MAIN][description=模块25的描述]
            * [appinit:module2:Module2TwoInit][70][进程=MAIN][description=模块22的描述]
            * [appinit:module2:Module2ThreeInit][80][进程=MAIN][description=模块23的描述]
            * [appinit:module2:Module2OneInit][80][进程=ALL][description=模块21的描述]
        《demo:app》[priority=4][dependencies=appinit:module2, com.sankuai.erp.component:appinit-test-module1]
            * [demo:app:RouterInit][40][进程=MAIN][description=初始化路由]
            * [demo:app:AppFirst][1000][进程=MAIN][description=壳工程最先初始化服务]
    
    scan 耗时:243ms
    handle 耗时:201ms
    transform 耗时:487ms
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 根据运行期日志调整初始化顺序、优化初始化耗时。
      在 AppInitCallback 的 onInitFinished 方法中可以调用「String initLogInfo = AppInitApiUtils.getInitOrderAndTimeLog(childInitTableList, appInitItemList);」获取运行期初始化顺序和各模块初始化耗时信息

    在这里插入图片描述

    4、扩展配置「可选」

    • 依赖的模块或 aheadOf 指定的初始化类不存在时不中断编译
    appInit {
        abortOnNotExist true
    }
    
    • 1
    • 2
    • 3
    • 添加以下配置后 AppInit 会自动在 Application 的 onTerminate、onConfigurationChanged、onLowMemory、onTrimMemory 方法中调用 AppInitManager 对应的方法
    appInit {
        // AndroidManifest.xml 中配置的 Application 的类全名
        applicationCanonicalName 'com.sankuai.erp.component.appinitdemo.App'
    }
    
    • 1
    • 2
    • 3
    • 4

    接入方不用再在 Application 中手动写以下代码

    public void onTerminate() {
        super.onTerminate();
        AppInitManager.get().onTerminate();
    }
    
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        AppInitManager.get().onConfigurationChanged(newConfig);
    }
    
    public void onLowMemory() {
        super.onLowMemory();
        AppInitManager.get().onLowMemory();
    }
    
    public void onTrimMemory(int level) {
        super.onTrimMemory(level);
        AppInitManager.get().onTrimMemory(level);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    三、AppInit-设计文档

    AppInit 演变史

    第一版

    • 定义一个统一的初始化接口 IAppInit,各个初始化类都实现该接口
    public interface IAppInit {
        boolean needAsyncInit();
        void onCreate();
        void asyncOnCreate();
        void onTerminate();
        void onConfigurationChanged(Configuration newConfig);
        void onLowMemory();
        void onTrimMemory(int level);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 添加所有的初始化项到 AppInitManager 中,在 Application 的生命周期方法调用 AppInitManager 对应的方法
    public final class AppInitManager {
        private List mAppInitList = new ArrayList<>();
        public static AppInitManager get() ...
        private AppInitManager() {
            mAppInitList.add(new BaseConfigInit()) // 初始化基础配置
            mAppInitList.add(new RobustInit()) // 初始化热修复
            mAppInitList.add(new PushInit()) // 初始化消息推送服务
            ...
        }
        private void onCreate() {
            // 遍历 mAppInitList 并同步分发给各个初始化类
        }
        public void onTerminate() ...
        public void onConfigurationChanged(Configuration newConfig) ...
        public void onLowMemory() ...
        public void onTrimMemory(int level) ...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    存在的问题
    • AppInitManager 和各模块的初始化类紧密的耦合在一起,当有多个模块时,AppInitManager 必须放到最上层才能保证编译通过,不便于组件化的实施
    • 上线前没发现初始化顺序错误,上线后 Crash,只能发新版本,没法动态修复初始化顺序
    • 初始化的代码统一加到一个地方,在优化冷启动时间时,不便于定位具体是哪个模块或 SDK 的初始化比较耗时

    为了解决上面的问题,需要我们的初始化框架具备以下特点:

    • 初始化代码解耦
    • 灵活配置初始化顺序、进程和线程
    • 统计各模块和具体初始化类的初始化时间

    第二版

    每个初始化类添加 AppInit 注解,注解中传递 priority 参数(整个项目范围内的初始化顺序优先级),通过 APT 收集所有添加了 AppInit 注解的初始化类和对应的 AppInit 注解参数信息,将这些信息写入到特定位置,运行期反射获取这些信息并排序。这里业界有两种方式:

    assets 方式
    • 初始化类上添加 AppInit 注解,用于生成包含该模块所有初始化信息子表类
    • 以该子表类的 canonicalName 作为文件名在「模块/build/intermediates/classes/debug/assets/AppInit/」目录下生成对应文件
    private void writeModuleCanonicalNameAssetFile(Filer filer, String canonicalName) {
        Writer writer = null;
        try {
            FileObject fileObject = filer.createResource(StandardLocation.CLASS_OUTPUT, "", "assets/AppInit/" + canonicalName);
            writer = fileObject.openWriter();
        } catch (Exception e) {
            // 处理异常
        } finally {
            // 关闭流
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 运行期通过 AssetManager 获取每个模块的初始化类列表,反射加载类,实例化完所有初始化类再进行排序,最后再由 ApplicationDispatcher 分发应用生命周期
    AssetManager assetManager = mApplication.getResources().getAssets();
    String[] childInitTableCanonicalNameArr = assetManager.list('AppInit');
    
    • 1
    • 2
    生成始化的模块子表和整个应用的初始化主表方式
    • 初始化类上添加 AppInit 注解,用于生成包含该模块所有初始化信息子表类(ModuleCanonicalNameChildInitTable)
    • 壳工程中添加 MasterAppInit 注解,用于生成生成包含了所有子表类的主表类(MasterInitTable)
    • 运行期通过反射加载 MasterInitTable,并对所有的初始化类进行排序,最后再由 ApplicationDispatcher 分发应用生命周期

    这两种方式存在的问题:

    • priority 排序是基于整个应用范围的,新增业务模块需要增加一个初始化类时,不便于插入到具体的初始化位置(可能需要修改其他初始化类的 priority),跨团队合作时维护人员更是不敢随意修改 priority 的值
    • 编译期无法查看整个应用的初始化顺序,如果开发同学经验不足、自测不够充分或者代码审查不够仔细,初始化顺序错误的 bug 很容易被带到线上
    • 运行期反射、排序影响应用冷启动时间

    第三版

    相较于前两个版本的改进点如下:

    • 通过模块下 gradle.properties 文件中的 APP_INIT_DEPENDENCIES 参数或 appInit 插件的 dependency 参数来配置应用启动时当前模块需要在哪些模块之后初始化
    • 同样是在初始化类上添加 AppInit 注解,不过 priority 属性仅用于配置模块内部各初始化类的顺序
    • 当 A 模块依赖 B 模块(A 模块需要在 B 模块之后初始化)时,如果 A 模块内部有一个初始化类需要在 B 模块某个初始化类之前初始化,可以通过配置 AppInit 注解的 aheadOf 参数来实现
    • 编译期检测依赖的模块或初始化类是否存在,将错误暴露在编译期而不是运行期
    • 编译期排序,减少冷启动时间
    • 编译期输出初始化顺序日志文件,便于开发者调试初始化顺序
    处理 aheadOf 前的顺序为:
        《com.sankuai.erp.component:appinit-test-module1》[priority=1]
            * [com.sankuai.erp.component:appinit-test-module1:Module1FiveInit][20][进程=MAIN][description=模块15的描述]
            * [com.sankuai.erp.component:appinit-test-module1:Module1TwoInit][90][进程=OTHER][description=模块12的描述]
            * [com.sankuai.erp.component:appinit-test-module1:Module1OneInit][100][进程=MAIN][aheadOf=com.sankuai.erp.component:appinit-test-module1:Module1TwoInit][description=模块11的描述]
            * [com.sankuai.erp.component:appinit-test-module1:Module1ThreeInit][300][进程=MAIN][description=模块13的描述]
            * [com.sankuai.erp.component:appinit-test-module1:Module1FourInit][300][进程=MAIN][description=模块14的描述]
        《com.sankuai.erp.component:appinit-test-module3》[priority=2][dependencies=com.sankuai.erp.component:appinit-test-module1]
            * [com.sankuai.erp.component:appinit-test-module3:Module3FiveInit][5][进程=MAIN][description=模块35的描述]
            * [com.sankuai.erp.component:appinit-test-module3:Module3FourInit][10][进程=MAIN][description=模块34的描述]
            * [com.sankuai.erp.component:appinit-test-module3:Module3ThreeInit][50][进程=MAIN][description=模块33的描述]
            * [com.sankuai.erp.component:appinit-test-module3:Module3OneInit][60][进程=ALL][description=模块31的描述]
            * [com.sankuai.erp.component:appinit-test-module3:Module3TwoInit][60][进程=MAIN][aheadOf=com.sankuai.erp.component:appinit-test-module1:Module1OneInit][description=模块32的描述]
        《appinit:module2》[priority=3][dependencies=com.sankuai.erp.component:appinit-test-module3]
            * [appinit:module2:Module2FiveInit][10][进程=MAIN][description=模块25的描述]
            * [appinit:module2:Module2FourInit][40][进程=MAIN][aheadOf=com.sankuai.erp.component:appinit-test-module3:Module3FourInit][description=模块24的描述]
            * [appinit:module2:Module2TwoInit][70][进程=MAIN][description=模块22的描述]
            * [appinit:module2:Module2ThreeInit][80][进程=MAIN][description=模块23的描述]
            * [appinit:module2:Module2OneInit][80][进程=ALL][description=模块21的描述]
        《demo:app》[priority=4][dependencies=appinit:module2, com.sankuai.erp.component:appinit-test-module1]
            * [demo:app:RouterInit][40][进程=MAIN][description=初始化路由]
            * [demo:app:AppFirst][1000][进程=MAIN][description=壳工程最先初始化服务]
    最终的初始化顺序为:
        《com.sankuai.erp.component:appinit-test-module1》[priority=1]
            * [com.sankuai.erp.component:appinit-test-module1:Module1FiveInit][20][进程=MAIN][description=模块15的描述]
        《com.sankuai.erp.component:appinit-test-module3》[priority=2][dependencies=com.sankuai.erp.component:appinit-test-module1]
            * [com.sankuai.erp.component:appinit-test-module3:Module3TwoInit][60][进程=MAIN][aheadOf=com.sankuai.erp.component:appinit-test-module1:Module1OneInit][description=模块32的描述]
        《com.sankuai.erp.component:appinit-test-module1》[priority=1]
            * [com.sankuai.erp.component:appinit-test-module1:Module1OneInit][100][进程=MAIN][aheadOf=com.sankuai.erp.component:appinit-test-module1:Module1TwoInit][description=模块11的描述]
            * [com.sankuai.erp.component:appinit-test-module1:Module1TwoInit][90][进程=OTHER][description=模块12的描述]
            * [com.sankuai.erp.component:appinit-test-module1:Module1ThreeInit][300][进程=MAIN][description=模块13的描述]
            * [com.sankuai.erp.component:appinit-test-module1:Module1FourInit][300][进程=MAIN][description=模块14的描述]
        《com.sankuai.erp.component:appinit-test-module3》[priority=2][dependencies=com.sankuai.erp.component:appinit-test-module1]
            * [com.sankuai.erp.component:appinit-test-module3:Module3FiveInit][5][进程=MAIN][description=模块35的描述]
        《appinit:module2》[priority=3][dependencies=com.sankuai.erp.component:appinit-test-module3]
            * [appinit:module2:Module2FourInit][40][进程=MAIN][aheadOf=com.sankuai.erp.component:appinit-test-module3:Module3FourInit][description=模块24的描述]
        《com.sankuai.erp.component:appinit-test-module3》[priority=2][dependencies=com.sankuai.erp.component:appinit-test-module1]
            * [com.sankuai.erp.component:appinit-test-module3:Module3FourInit][10][进程=MAIN][description=模块34的描述]
            * [com.sankuai.erp.component:appinit-test-module3:Module3ThreeInit][50][进程=MAIN][description=模块33的描述]
            * [com.sankuai.erp.component:appinit-test-module3:Module3OneInit][60][进程=ALL][description=模块31的描述]
        《appinit:module2》[priority=3][dependencies=com.sankuai.erp.component:appinit-test-module3]
            * [appinit:module2:Module2FiveInit][10][进程=MAIN][description=模块25的描述]
            * [appinit:module2:Module2TwoInit][70][进程=MAIN][description=模块22的描述]
            * [appinit:module2:Module2ThreeInit][80][进程=MAIN][description=模块23的描述]
            * [appinit:module2:Module2OneInit][80][进程=ALL][description=模块21的描述]
        《demo:app》[priority=4][dependencies=appinit:module2, com.sankuai.erp.component:appinit-test-module1]
            * [demo:app:RouterInit][40][进程=MAIN][description=初始化路由]
            * [demo:app:AppFirst][1000][进程=MAIN][description=壳工程最先初始化服务]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 运行期输出初始化顺序和初始化时间日志,便于开发者调试初始化顺序,更精准的定位导致冷启动慢的具体模块的具体初始化类

    在这里插入图片描述

    • 项目上线后,通过回调接口修改 aheadOf 来动态修复开发期间未发现的初始化顺序错误问题

    AppInit 原理

    AppInit 各模块功能说明

    在这里插入图片描述

    AppInit 执行流程

    在这里插入图片描述

    编写初始化类继承 SimpleAppInit 并 AppInit 注解

    根据实际情况重写 Application 对应的生命周期方法

    在这里插入图片描述

    APT 在固定包「com.sankuai.erp.component.appinit.generated」下生成「模块唯一标识转驼峰命名ChildInitTable.java」

    在这里插入图片描述

    生成的类在 build/generated/source/apt/debug/com/sankuai/erp/component/appinit/generated 目录下,该类中包含模块唯一标识,依赖的其他模块,以及该模块中所有的初始化条目

    在这里插入图片描述

    扫描收集字节码文件
    • 扫描收集 AppInitManager.class、AndroidManifest.xml 中注册的 Application.class
    • 扫描所有 XxxxxxChildInitTable.class 添加到集合中
    排序并修改 AppInitManager 和 Application 字节码文件
    • 实例化所有的 XxxxxxChildInitTable.class 并添加到集合中并排序,对所有的初始化条目排序
    • 修改 AppInitManager.class 将排好序的 XxxxxxChildInitTable 列表以及初始化条目注入进去

    在这里插入图片描述

    在这里插入图片描述

    • 「可选」当接入方配置了 applicationCanonicalName 时,修改 Application.class 自动派发 Application 生命周期方法给 AppInitManager「省去了每个接入方都要重写一便 Application 相应生命周期方法派发给 AppInitManager」

    在这里插入图片描述

    在这里插入图片描述

    AppInitManager 中通过 AppInitDispatcher 分发 Application 生命周期给初始化条目

    在这里插入图片描述

    四、个人框架使用和理解

    使用场景

    在这里插入图片描述

    实现原理

    在这里插入图片描述

    事例代码

    module 排序

    • module 自主设置依赖模块

    module 5 依赖 lib_behavioural_analysis,需要在module 5中添加 gradle.properties 文件,内容如下:

    在这里插入图片描述

    APP_INIT_DEPENDENCIES=Demo_AppInit:lib_behavioural_analysis
    
    • 1
    • 主工程排序

    主工程的设置会优先级高于module设置的

    修改build.gradle文件

    在这里插入图片描述

    appInit {
    
        // 自定义模块间初始化的依赖关系,会覆盖通过 APP_INIT_DEPENDENCIES 配置的依赖关系
        dependency([
                'Demo_AppInit:app'                                      : ['Demo_AppInit:module5', 'Demo_AppInit:lib_behavioural_analysis'],
        ])
    
        // 依赖的模块或 aheadOf 指定的初始化不存在时是否中断编译,默认为 true
        abortOnNotExist true
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    类排序

    • priority

    优先级最低,统筹module内部

    @AppInit(
        priority = 40,//优先级
        description = "初始化路由",
        onlyForDebug = false,
        process = Process.MAIN
    )
    class RouterInit : SimpleAppInit() {
    
        override fun onCreate() {
            AppInitLogger.demo("onCreate $TAG")
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    @AppInit(
        priority = 20,//优先级
        description = "初始化MMKV",
        onlyForDebug = false,
        process = Process.MAIN
    )
    class MMKVInit : SimpleAppInit() {
    
        override fun onCreate() {
            super.onCreate()
            AppInitLogger.demo("onCreate $TAG")
            MMKV.initialize(mApplication)
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    MMKVInit 会在RouterInit 前面执行

    • aheadOf
    @AppInit(
        priority = 2,
        description = "模块3的描述"
        aheadOf = "Demo_AppInit:lib_behavioural_analysis:Behavioural2Init"
    )
    class Module3ThreeInit : SimpleAppInit() {
    
        override fun onCreate() {
            AppInitLogger.demo("onCreate $TAG")
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    aheadOf 可以指定两个module之间的类排序

    在谁前面的意思,优先级高于priority

    • 重写 getCoordinateAheadOfMap

    优先级最高

                /**
                 * 通过 coordinate 自定义依赖关系映射,键值都是 coordinate。「仅在需要发热补的情况下才自定义,否则返回 null」
                 *
                 *  排序含义: KEY 在 VALUE 前面
                 *
                 * @return 如果返回的 map 不为空,则会在启动是检测依赖并重新排序
                 */
                override fun getCoordinateAheadOfMap(): MutableMap<String, String> {
                    val coordinateAheadOfMap: MutableMap<String, String> = HashMap()
                    coordinateAheadOfMap["Demo_AppInit:module3:Module3ThreeInit"] =
                        "Demo_AppInit:lib_behavioural_analysis:Behavioural2Init"
    
    //                                coordinateAheadOfMap[ "Demo_AppInit:lib_behavioural_analysis:Behavioural2Init"] =
    //                                    "Demo_AppInit:module3:Module3ThreeInit"
    
                    return coordinateAheadOfMap
                }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    遇见的问题

     dependency([
                'Demo_AppInit:app': [ 'Demo_AppInit:lib_behavioural_analysis','Demo_AppInit:module5'],
    
    • 1
    • 2

    写错了后面依赖的模块名,clean无法解决编译错误的问题,必须重启AS

    参考地址

    99%的内容来自Github的Demo,因为不得劲看,特意记录,地址:

    美团的AppInit: https://github.com/bingoogolapple/AppInit

    其他启动框架:
    阿里的alpha:https://github.com/alibaba/alpha

  • 相关阅读:
    网址浏览历史记录怎么查?分享四个特别管用的方法!一分钟就学会!
    科技云报道:事件响应,安全能力的关键一环
    日益趋增的Linux勒索软件
    unbuntu下安装gfortran
    Taurus.MVC-Java 版本打包上传到Maven中央仓库(详细过程):4、Maven项目转换与pom.xml配置
    MAUI Android 关联文件类型
    【KVM-6】KVM/QEMU软件栈
    如何写Go代码
    正则表达式
    MVP-3:登陆注册自定义token拦截器
  • 原文地址:https://blog.csdn.net/weixin_35691921/article/details/126751497