1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        厲害了!自己寫個App 啟動任務(wù)框架

        共 18604字,需瀏覽 38分鐘

         ·

        2022-06-15 18:33



         安卓進階漲薪訓(xùn)練營,讓一部分人先進大廠


        大家好,我是皇叔,最近開了一個安卓進階漲薪訓(xùn)練營,可以幫助大家突破技術(shù)&職場瓶頸,從而度過難關(guān),進入心儀的公司。


        詳情見文章:沒錯!皇叔開了個訓(xùn)練營


        作者:王晨彥
        https://juejin.cn/post/7042201901399539748

        1.前言


        我們在開發(fā)應(yīng)用的時候,一般都會引入 SDK,而大部分 SDK 都要求我們在 Application 中初始化,當我們引入的 SDK 越來越多,就會出現(xiàn) Application 越來越長,如果 SDK 的初始化任務(wù)相互依賴,還要處理很多條件判斷,這時,如果再來個異步初始化,相信大家都會崩潰。


        有人可能會說,我都在主線程按順序初始化不就行了,當然行,只要老板不來找你麻煩。


        「小王啊,咱們的 APP 啟動時間怎么這么久?」


        開個玩笑,可見,一個優(yōu)秀的啟動框架對于 APP 啟動性能而言,是多么的重要!


        2.為什么不用 Google 的 StartUp?

        說到啟動框架,就不得不提 StartUp,畢竟是 Google 官方出品,現(xiàn)有的啟動框架,或多或少都有參考 StartUp,這里不再詳細介紹,如果對 StartUp 還不了解,可以參考這篇文章 Jetpack系列之App Startup從入門到出家。

        https://juejin.cn/post/7023643365048582174


        StartUp 提供了簡便的依賴任務(wù)初始化功能,但是對于一個復(fù)雜項目來說,StartUp 有以下不足:


        1. 不支持異步任務(wù)


        如果通過 ContentProvider 啟動,所有任務(wù)都在主線程執(zhí)行,如果通過接口啟動,所有任務(wù)都在同一個線程執(zhí)行。


        2. 不支持組件化


        通過 Class 指定依賴任務(wù),需要引用依賴的模塊。


        3. 不支持多進程


        無法單獨配置任務(wù)需要執(zhí)行的進程。


        4. 不支持啟動優(yōu)先級


        雖然可以通過指定依賴來設(shè)置優(yōu)先級,但是過于復(fù)雜。

        3.一個合格的啟動框架是怎么樣的?

        1. 支持異步任務(wù)


        減少啟動時間的有效手段。


        2. 支持組件化


        其實就是解耦,一方面是解耦任務(wù)依賴,另一方面是解耦 app 和 module 的依賴。


        3. 支持任務(wù)依賴


        可以簡化我們的任務(wù)調(diào)度。


        4. 支持優(yōu)先級


        在沒有依賴的情況下,允許任務(wù)優(yōu)先執(zhí)行。


        5. 支持多進程


        只在需要的進程中執(zhí)行初始化任務(wù),可以減輕系統(tǒng)負載,側(cè)面提升 APP 啟動速度。

        4.收集任務(wù)

        如果要做到完全解耦,我們可以使用 APT 收集任務(wù)。


        首先定義注解,即任務(wù)的一些屬性。



        @Target(AnnotationTarget.CLASS)
        @Retention(AnnotationRetention.RUNTIME)
        annotation class InitTask(
            /**
             * 任務(wù)名稱,需唯一
             */

            val name: String,
            /**
             * 是否在后臺線程執(zhí)行
             */

            val background: Boolean = false,
            /**
             * 優(yōu)先級,越小優(yōu)先級越高
             */

            val priority: Int = PRIORITY_NORM,
            /**
             * 任務(wù)執(zhí)行進程,支持主進程、非主進程、所有進程、:xxx、特定進程名
             */

            val process: Array<String> = [PROCESS_ALL],
            /**
             * 依賴的任務(wù)
             */

            val depends: Array<String> = []
        )



        name 作為任務(wù)唯一標識,類型為 String 主要是解耦任務(wù)依賴。


        background 即是否后臺執(zhí)行。


        priority 是在主線程、無依賴場景下的執(zhí)行順序。


        process 指定了任務(wù)執(zhí)行的進程,支持主進程、非主進程、所有進程、:xxx、特定進程名。


        depends 指定依賴的任務(wù)。


        任務(wù)的屬性定義好,還需要一個執(zhí)行任務(wù)的接口:



        interface IInitTask {
            fun execute(application: Application)
        }


        任務(wù)需要收集的信息已經(jīng)定義好了,那么看一下一個真正的任務(wù)長什么樣。


        @InitTask(
            name = "main",
            process = [InitTask.PROCESS_MAIN],
            depends = ["lib"]
        )

        class MainTask : IInitTask {
            override fun execute(application: Application) {
                SystemClock.sleep(1000)
                Log.e("WCY""main1 execute")
            }
        }

        還是比較簡潔清晰的。


        接下來需要通過 Annotation Processor 收集任務(wù),然后通過 kotlin poet 寫入文件。


        class TaskProcessor : AbstractProcessor() {

            override fun process(annotations: MutableSet<out TypeElement>?, roundEnv: RoundEnvironment)Boolean {
                val taskElements = roundEnv.getElementsAnnotatedWith(InitTask::class.java)
                val taskType = elementUtil.getTypeElement("me.wcy.init.api.IInitTask")

                /**
                 * Param type: MutableList<TaskInfo>
                 *
                 * There's no such type as MutableList at runtime so the library only sees the runtime type.
                 * If you need MutableList then you'll need to use a ClassName to create it.
                 * [https://github.com/square/kotlinpoet/issues/482]
                 */

                val inputMapTypeName =
                    ClassName("kotlin.collections""MutableList").parameterizedBy(TaskInfo::class.asTypeName())

                /**
                 * Param name: taskList: MutableList<TaskInfo>
                 */

                val groupParamSpec = ParameterSpec.builder(ProcessorUtils.PARAM_NAME, inputMapTypeName).build()

                /**
                 * Method: override fun register(taskList: MutableList<TaskInfo>)
                 */

                val loadTaskMethodBuilder = FunSpec.builder(ProcessorUtils.METHOD_NAME)
                    .addModifiers(KModifier.OVERRIDE)
                    .addParameter(groupParamSpec)

                for (element in taskElements) {
                    val typeMirror = element.asType()
                    val task = element.getAnnotation(InitTask::class.java)
                    if (typeUtil.isSubtype(typeMirror, taskType.asType())) {
                        val taskCn = (element as TypeElement).asClassName()

                        /**
                         * Statement: taskList.add(TaskInfo(name, background, priority, process, depends, task));
                         */

                        loadTaskMethodBuilder.addStatement(
                            "%N.add(%T(%S, %L, %L, %L, %L, %T()))",
                            ProcessorUtils.PARAM_NAME,
                            TaskInfo::class.java,
                            task.name,
                            task.background,
                            task.priority,
                            ProcessorUtils.formatArray
        (task.process),
                            ProcessorUtils.formatArray(task.depends),
                            taskCn
                        )
                    }
                }

                /**
                 * Write to file
                 */

                FileSpec.builder(ProcessorUtils.PACKAGE_NAME, "TaskRegister\$$moduleName")
                    .addType(
                        TypeSpec.classBuilder("TaskRegister\$$moduleName")
                            .addKdoc(ProcessorUtils.JAVADOC)
                            .addSuperinterface(ModuleTaskRegister::class.java)
                            .addFunction(loadTaskMethodBuilder.build())
                            .build()
                    )
                    .build()
                    .writeTo(filer)

                return true
            }
        }


        看一下生成的文件長什么樣。


        public class TaskRegister$sample : ModuleTaskRegister {
          public override fun register(taskList: MutableList<TaskInfo>)Unit {
            taskList.add(TaskInfo("main2"true0, arrayOf("PROCESS_ALL"), arrayOf("main1","lib1"),MainTask2()))
            taskList.add(TaskInfo("main3"false-1000, arrayOf("PROCESS_ALL"), arrayOf(), MainTask3()))
            taskList.add(TaskInfo("main1"false0, arrayOf("PROCESS_MAIN"), arrayOf("lib1"), MainTask()))
          }
        }



        sample 模塊收集到了3個任務(wù),TaskInfo 對任務(wù)信息做了聚合。


        我們知道 APT 可以生成代碼,但是無法修改字節(jié)碼,也就是說我們在運行時想到拿到注入的任務(wù),還需要將收集的任務(wù)注入到源碼中。


        這里可以借助 AutoRegister 幫我們完成注入。

        https://github.com/luckybilly/AutoRegister


        注入前:


        internal class FinalTaskRegister {
            val taskList: MutableList<TaskInfo> = mutableListOf()

            init {
                init()
            }

            private fun init() {}

            fun register(register: ModuleTaskRegister) {
                register.register(taskList)
            }
        }



        將收集到的任務(wù)注入到 init 方法中,注入后的字節(jié)碼:


        /* compiled from: FinalTaskRegister.kt */
        public final class FinalTaskRegister {
            private final List<TaskInfo> taskList = new ArrayList();

            public FinalTaskRegister() {
                init();
            }

            public final List<TaskInfo> getTaskList() {
                return this.taskList;
            }

            private final void init() {
                register(new TaskRegister$sample_lib());
                register(new TaskRegister$sample());
            }

            public final void register(ModuleTaskRegister register) {
                Intrinsics.checkNotNullParameter(register"register");
                register.register(this.taskList);
            }
        }



        我們通過 APT 生成的類已經(jīng)成功的注入到代碼中。


        小結(jié)


        至此,我們已經(jīng)完成了任務(wù)的收集,通過 APT 和字節(jié)碼修改是常見的類收集方案,相比反射,字節(jié)碼修改沒有任何性能的損失。


        后來發(fā)現(xiàn) Google 已經(jīng)推出了新的注解處理框架 ksp,處理速度更快,于是果斷嘗試了一把,所以有兩種注解處理可以選擇,GitHub 上有詳細介紹。

        5.任務(wù)調(diào)度

        任務(wù)調(diào)度是啟動框架的核心,大家可能聽到過。


        處理依賴任務(wù)首先要構(gòu)建一個「有向無環(huán)圖」。


        什么是有向無環(huán)圖,看下維基百科的介紹:


        在圖論中,如果一個有向圖從任意頂點出發(fā)無法經(jīng)過若干條邊回到該點,則這個圖是一個有向無環(huán)圖(DAG, Directed Acyclic Graph)。


        聽起來好像很簡單,那么具體怎么實現(xiàn)呢,今天我們拋開高級概念不談,用代碼帶大家實現(xiàn)任務(wù)的調(diào)度。


        首先,需要把任務(wù)分為兩類,有依賴的任務(wù)和無依賴的任務(wù)。


        有依賴的首先檢查是否有環(huán),如果有循環(huán)依賴,直接 throw,這個可以套用公式 —— 如何判斷鏈表是否有環(huán)。


        如果沒有循環(huán)依賴,則收集每個任務(wù)的被依賴任務(wù),我們稱之為子任務(wù),用于當前任務(wù)執(zhí)行完成后,繼續(xù)執(zhí)行子任務(wù)。


        無依賴的最簡單,直接按照優(yōu)先級執(zhí)行即可。


        不知道大家是否有疑問:有依賴的任務(wù)什么時候啟動?


        有依賴的任務(wù),依賴鏈的葉子端點一定是一個無依賴的任務(wù),因此無依賴的任務(wù)執(zhí)行完成后,就可以開始執(zhí)行有依賴的任務(wù)。


        下面用一個小例子來介紹:


        ?  A 依賴 B、C


        ?  B 依賴 C


        ?  C 無依賴


        樹形結(jié)構(gòu):



        1. 分組并梳理子任務(wù)。


        ?  有依賴:

            A: 無子任務(wù)

            B: 子任務(wù): [A]


        ?  無依賴:

            C: 子任務(wù): [A, B]




        2. 執(zhí)行無依賴的任務(wù)C。


        3. 更新已完成的任務(wù): [C]。


        4. 檢查 C 的子任務(wù)是否可以執(zhí)行。


            A: 依賴 [B, C],已完成任務(wù)中不包含 B,無法啟動

            B: 依賴 [C],已完成任務(wù)中包含 C,可以執(zhí)行


        5. 執(zhí)行任務(wù) B。


        6. 重復(fù)步驟 3,直到所有任務(wù)執(zhí)行完成。


        下面我們就用代碼來實現(xiàn):


        使用遞歸檢查循環(huán)依賴:


        private fun checkCircularDependency(
            chain: List<String>,
            depends: Set<String>,
            taskMap: Map<String, TaskInfo>
        )
         {
            depends.forEach { depend ->
                check(chain.contains(depend).not()) {
                    "Found circular dependency chain: $chain -> $depend"
                }
                taskMap[depend]?.let { task ->
                    checkCircularDependency(chain + depend, task.depends, taskMap)
                }
            }
        }



        梳理子任務(wù):


        task.depends.forEach {
            val depend = taskMap[it]
            checkNotNull(depend) {
                "Can not find task [$it] which depend by task [${task.name}]"
            }
            depend.children.add(task)
        }



        執(zhí)行任務(wù):


        private fun execute(task: TaskInfo) {
            if (isMatchProgress(task)) {
                val cost = measureTimeMillis {
                    kotlin.runCatching {
                        (task.task as IInitTask).execute(app)
                    }.onFailure {
                        Log.e(TAG, "executing task [${task.name}] error", it)
                    }
                }
                Log.d(
                    TAG, "Execute task [${task.name}] complete in process [$processName] " +
                            "thread [${Thread.currentThread().name}], cost: ${cost}ms"
                )
            } else {
                Log.w( TAG, "Skip task [${task.name}] cause the process [$processName] not match")
            }
            afterExecute(task.name, task.children)
        }



        如果進程不匹配直接跳過。


        繼續(xù)執(zhí)行下一個任務(wù):


        private fun afterExecute(name: String, children: Set<TaskInfo>) {
            val allowTasks = synchronized(completedTasks) {
                completedTasks.add(name)
                children.filter { completedTasks.containsAll(it.depends) }
            }
            if (ThreadUtils.isInMainThread()) {
                // 如果是主線程,先將異步任務(wù)放入隊列,再執(zhí)行同步任務(wù)
                allowTasks.filter { it.background }.forEach {
                    launch(Dispatchers.Default) { execute(it) }
                }
                allowTasks.filter { it.background.not() }.forEach { execute(it) }
            } else {
                allowTasks.forEach {
                    val dispatcher = if (it.background) Dispatchers.Default else Dispatchers.Main
                    launch(dispatcher) { execute(it) }
                }
            }
        }


        如果子任務(wù)的依賴任務(wù)都已經(jīng)執(zhí)行完畢,就可以執(zhí)行了。


        最后還需要提供一個啟動任務(wù)的接口,為了支持多進程,這里不能使用 ContentProvider。


        小結(jié)


        通過層層拆解,將復(fù)雜的依賴梳理清楚,用通俗易懂的方法,實現(xiàn)任務(wù)調(diào)度。


        源碼


        https://github.com/wangchenyan/init


        另外,我也在 JitPack 上發(fā)布了 alpha 版本,歡迎大家嘗試:



        kapt "com.github.wangchenyan.init:init-compiler:1-alpha.1"
        implementation "com.github.wangchenyan.init:init-api:1-alpha.1"



        詳細使用請移步 GitHub

        https://github.com/wangchenyan/init


        總結(jié)


        本文以 StartUp 作為引子,闡述依賴任務(wù)啟動框架還需要具備哪些能力,通過 APT + 字節(jié)碼注入進行解耦,支持模塊化,通過一個簡單的模型來表述任務(wù)調(diào)度具體的實現(xiàn)方式。


        希望本文能夠讓大家了解依賴任務(wù)啟動框架的核心思想,如果你有好的建議,歡迎評論。


        參考


        Kotlin + Flow 實現(xiàn)的 Android 應(yīng)用初始化任務(wù)啟動庫

        https://juejin.cn/post/6938229049462358047






        為了失聯(lián),歡迎關(guān)注我防備的小號



         

                                                 微信改了推送機制,真愛請星標本公號??

        瀏覽 27
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            国产另类视频 | 少妇春情耸动雪白大腿 | 另类小说亚洲区欧美动图无码第页 | 男女午夜视频 | 婷婷国产成人精品一区二 | 丁香五婷 | 亚洲无码不卡 | 91黄色成人 | 男人插女人b | 中文字幕国内自拍 |