Android開發(fā)注解排列組合出啟動(dòng)任務(wù)ksp
背景
之前我不想用注解來(lái)寫啟動(dòng)框架,因?yàn)閱?dòng)框架需要的參數(shù)太多了。將參數(shù)都定義在注解內(nèi)和寫一個(gè)task就沒(méi)有本質(zhì)上的差別,所以一直覺(jué)得沒(méi)必要用注解來(lái)搞。
但是之前和另外一個(gè)同事聊了下,如果注解也可以進(jìn)行排列組合,那么貌似就可以用注解來(lái)解決這個(gè)問(wèn)題咯,感覺(jué)這樣用起來(lái)就會(huì)很好玩了。
開卷開卷
首先要做的事情就是定義出我們想要的注解,可以基于我們之前對(duì)于task
的定義來(lái)進(jìn)行注解的定義,比如是不是主線程,是否需要等待完成,task的依賴任務(wù),是否是錨點(diǎn)任務(wù)等等。
// 是否異步 @Async // 是否等待 @Await // 錨點(diǎn)任務(wù) @MustAfter // 依賴關(guān)系 @DependOn( dependOn = [AsyncTask1Provider::class, SimpleTask2Provider::class], dependOnTag = ["taskB"] )
注解呢上面的這些就是我定義出來(lái)的新增的注解的,我后續(xù)會(huì)通過(guò)這些注解來(lái)組合出我所想要的啟動(dòng)的Task。
Ksp解析注解
這里我定義了一個(gè)Startup
的注解,這個(gè)注解的目的就是標(biāo)識(shí)當(dāng)前的類是一個(gè)啟動(dòng)的Task。因?yàn)樵?code>ksp或者apt
的compiler
環(huán)節(jié)上,都會(huì)先嘗試獲取到當(dāng)前語(yǔ)法樹的所有注解的類。
package com.kronos.startup.annotation.startup import com.kronos.startup.annotation.Process /** * * @Author LiABao * @Since 2021/12/31 * */ @Target( AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS ) @Retention annotation class Startup( // 進(jìn)程策略 val strategy: Process = Process.ALL, //進(jìn)程名 val processName: Array<String> = [] )
從demo
開始逐步推導(dǎo)我打算咋寫這些東西。下面是我定義的一個(gè)簡(jiǎn)單的啟動(dòng)任務(wù),Task
具體內(nèi)容應(yīng)該有apt來(lái)生成。
@Async @Await @MustAfter @DependOn( dependOn = [AsyncTask1Provider::class, SimpleTask2Provider::class], dependOnTag = ["taskB"] ) // 執(zhí)行的進(jìn)程名 @Startup(strategy = Process.MAIN) class SampleGenerate1Task : TaskRunner { override fun run(context: Context) { info("SampleGenerate1Task") } }
還是我一開始的說(shuō)法,我們的第一個(gè)切入點(diǎn)是Startup
注解,然后獲取到SampleGenerate1Task
的抽象語(yǔ)法樹信息,之后再進(jìn)行下一步操作。
private fun addStartUp(type: KSAnnotated) { logger.check(type is KSClassDeclaration && type.origin == Origin.KOTLIN, type) { "@JsonClass can't be applied to $type: must be a Kotlin class" } if (type !is KSClassDeclaration) return val startupAnnotation = type.findAnnotationWithType(startupType) ?: return taskMap.add(StartupTaskBuilder(type, startupAnnotation)) }
基于KSClassDeclaration
語(yǔ)法樹的信息,我們可以獲取到當(dāng)前類上的注解,然后在收集完成之后再來(lái)生成對(duì)應(yīng)的啟動(dòng)任務(wù)。首先我們先要獲取到類上的所有的注解,然后進(jìn)行遍歷,當(dāng)當(dāng)前注解符合我們所需要的類型情況下,調(diào)整數(shù)據(jù)結(jié)構(gòu)信息就可以了。
class StartupTaskBuilder(type: KSClassDeclaration, startupAnnotation: KSAnnotation?) { val className = type.toClassName() var isAsync = false var isAwait = false var strategy: String var processList: ArrayList<String> = arrayListOf() val dependOnClassList = mutableListOf<ClassName>() val dependOnStringList = mutableListOf<String>() var mustAfter: Boolean = false var lifecycle: Lifecycle = Lifecycle.OnApplicationCrate init { type.annotations.forEach { val annotation = it.annotationType.resolve().toClassName() if (annotation.canonicalName == "com.kronos.startup.annotation.startup.Async") { isAsync = true } if (annotation.canonicalName == "com.kronos.startup.annotation.startup.Await") { isAwait = true } if (annotation.canonicalName == "com.kronos.startup.annotation.startup.MustAfter") { mustAfter = true } if (annotation.canonicalName == "com.kronos.startup.annotation.startup.DependOn") { val value = it.getMember<ArrayList<ClassName>>("dependOn") dependOnClassList.addAll(value) val dependOnTag = it.getMember<ArrayList<String>>("dependOnTag") dependOnStringList.addAll(dependOnTag) } if (annotation.canonicalName == "com.kronos.startup.annotation.Step") { val value = it.arguments.firstOrNull { it.name?.asString() == "lifecycle" }?.value.toString().nameToLifeCycle() lifecycle = value mLogger?.warn("stage:$value") } } type.getAllSuperTypes().forEach { it.toClassName() } strategy = startupAnnotation?.arguments?.firstOrNull { it.name?.asString() == "strategy" }?.value.toString().toValue() val list = startupAnnotation?.getMember<ArrayList<String>>("processName") list?.let { processList.addAll(it) } } xxxxxxx }
接下來(lái)我們用了一個(gè)數(shù)據(jù)結(jié)構(gòu)來(lái)收集這些注解信息,然后和上篇文章說(shuō)的一樣。我們會(huì)在注解信息收集完畢之后在finish
方法進(jìn)行代碼的生成邏輯。有興趣的同學(xué)可以自己看下GenerateTaskKt
,邏輯相對(duì)來(lái)說(shuō)比較簡(jiǎn)單,基于數(shù)據(jù)結(jié)構(gòu)插入不同的kt代碼邏輯。
//SymbolProcessor override fun finish() { super.finish() try { val taskGenerate = GenerateTaskKt(taskMap, codeGenerator) taskGenerate.procTaskGroupMap.forEach { val list = procTaskGroupMap.getValueByDefault(it.key) { mutableListOf() } list.addAll(it.value) } } }
Task生成還需要結(jié)合TaskGroup概念
因?yàn)槲覀冎暗脑O(shè)想是回生成一個(gè)任務(wù)的分組StartupTaskProcessGroup
,所以這部分代碼上傳的Task
也需要保持一致。
我們需要做的就是將這些由ksp
生成的Task類信息也帶到TaskGroup的生成邏輯中去。
由于我們之前的底子比較好,所以我們只要在將這些類生成的信息插入到原來(lái)的list中去則就可以完成這個(gè)操作了。
private val procTaskGroupMap = hashMapOf<Lifecycle, MutableList<TaskBuilder>>() val taskGenerate = GenerateTaskKt(taskMap, codeGenerator) taskGenerate.procTaskGroupMap.forEach { val list = procTaskGroupMap.getValueByDefault(it.key) { mutableListOf() } list.addAll(it.value) }
其實(shí)我們?cè)谏厦娴腡ask遍歷的時(shí)候就已經(jīng)對(duì)于這個(gè)list進(jìn)行了代碼插入的操作了。這樣就能做到后續(xù)的插入邏輯了。
拆分啟動(dòng)步驟
接下來(lái)我想說(shuō)的就是另外一個(gè)概念了, 因?yàn)楝F(xiàn)在有很多隱私合規(guī)的訴求,所以大部分的公司都需要做一件事,就是把隱私前的初始化邏輯和隱私后的初始化邏輯進(jìn)行拆分。
這也就有了我想說(shuō)的分步驟的事情了,所以我們需要在重新定義一個(gè)新的注解出來(lái)。
@Target( AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS ) @Retention annotation class Step(val lifecycle: Lifecycle = Lifecycle.OnApplicationCrate) enum class Lifecycle(val value: String) { AttachApplication("AttachApplication"), OnApplicationCrate("OnApplicationCrate"), AfterUserPrivacy("AfterUserPrivacy") }
這個(gè)就是我設(shè)想的分階段分步驟的概念,我在demo中設(shè)置了三個(gè)不同的階段,分別對(duì)應(yīng)application的attach和create,還有隱私同意之后的代碼。
這次呢,我在上述task的基礎(chǔ)上又再次加了點(diǎn)東西進(jìn)去,我希望一個(gè)module
對(duì)外輸出的是一個(gè)包含了所有階段的StartupTaskProcessGroup
的數(shù)組,我把它叫做StepTaskBuilder
。
之后我們只要將所有模塊的StepTaskBuilder
收集到一起,則就可以完成自動(dòng)的初始化任務(wù),這樣做的一個(gè)好處就是后續(xù)這種依賴關(guān)系就可以在編譯打包階段都完成了,代碼內(nèi)只需要加入調(diào)用就可以了。
val stageGenerator = StageGenerateKt( "${moduleName.upCaseKeyFirstChar()}StepBuilder", nameList, codeGenerator ) stageGenerator.generateKt()
我在finish
方法的最后面加入了這段,就是拿來(lái)生成StepTaskBuilder
用的。邏輯也相對(duì)比較簡(jiǎn)單,大家自取看看就好了。
依賴注入
看到這個(gè)小標(biāo)題,我知道各位都會(huì)有迷惑。為什么一個(gè)破啟動(dòng)框架還需要依賴注入的邏輯?
正常情況下,我們?cè)趯憇dk的時(shí)候,會(huì)有很多的初始化參數(shù)都需要使用方來(lái)定義的,比如okhttp的超時(shí)時(shí)間,緩存路徑,線程大小這類的變更的參數(shù)。
那么同樣的情況也會(huì)出現(xiàn)在啟動(dòng)框架內(nèi),我們想做的是一個(gè)能自動(dòng)的初始化框架,那么這部分變更的參數(shù)就需要被注入。
demo中使用koin
來(lái)完成的依賴注入,將依賴翻轉(zhuǎn)到最外層,將變化的部分由app來(lái)設(shè)置,基本就能滿足我的訴求了。
application
內(nèi)的實(shí)現(xiàn)類設(shè)置具體的實(shí)現(xiàn)如下。
val appModule = module { single<ReportInitDelegate> { ReportInitDelegateImp() } } private class ReportInitDelegateImp : ReportInitDelegate { override fun getAppName(): String { return "123444556" } }
在sdk
模塊初始化的時(shí)候通過(guò)依賴注入抽象接口的實(shí)現(xiàn),這樣就可以再SDK直接進(jìn)行初始化代碼的編寫了??梢砸?guī)避掉一些sdk
的初始化順序問(wèn)題。
@Async @Await @DependOn(dependOn = [NetworkSdkTaskProvider::class]) @Startup class ReportSdkTask : KoinComponent, TaskRunner { private val initDelegate: ReportInitDelegate by inject() override fun run(context: Context) { info("ReportSdkTask appName is:${initDelegate.getAppName()}") } }
TODO
還有個(gè)收集所有module
內(nèi)的StepTaskBuilder
功能沒(méi)寫,這部分需要增加一個(gè)Plugin
,在編譯階段收集好所有,基本就可以完成對(duì)應(yīng)的功能了。
總結(jié)
這部分我覺(jué)得還是挺有意思的,我們?cè)瓉?lái)的設(shè)計(jì)上就是通過(guò)這種自動(dòng)化的啟動(dòng)框架,后續(xù)來(lái)完成所有殼工程的建設(shè),讓開發(fā)同學(xué)盡量少感知到啟動(dòng)流程相關(guān)的邏輯。
以上就是Android開發(fā)注解排列組合出啟動(dòng)任務(wù)ksp的詳細(xì)內(nèi)容,更多關(guān)于Android注解排列組合啟動(dòng)任務(wù)ksp的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Android文字基線Baseline算法的使用講解
- Android Studio使用Profiler來(lái)完成內(nèi)存泄漏的定位
- Android優(yōu)化提升應(yīng)用啟動(dòng)速度及Splash頁(yè)面的設(shè)計(jì)
- Android 分析實(shí)現(xiàn)性能優(yōu)化之啟動(dòng)速度優(yōu)化
- Android10 App 啟動(dòng)分析進(jìn)程創(chuàng)建源碼解析
- Android 10 啟動(dòng)Init進(jìn)程解析
- Android基準(zhǔn)配置文件Baseline?Profile方案提升啟動(dòng)速度
相關(guān)文章
解決android6.0以上不能讀取外部存儲(chǔ)權(quán)限的問(wèn)題
今天小編就為大家分享一篇解決android6.0以上不能讀取外部存儲(chǔ)權(quán)限的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-08-08Android中退出確認(rèn)框的實(shí)現(xiàn)代碼
這篇文章主要介紹了Android中退出確認(rèn)框的實(shí)現(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下2017-05-05Android讀取手機(jī)通訊錄聯(lián)系人到自己項(xiàng)目
這篇文章主要為大家詳細(xì)介紹了Android讀取手機(jī)通訊錄聯(lián)系人到自己項(xiàng)目,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07Android手機(jī)號(hào)注冊(cè)、綁定手機(jī)號(hào)獲取短信驗(yàn)證碼實(shí)例
這篇文章主要為大家詳細(xì)介紹了Android手機(jī)號(hào)注冊(cè)、綁定手機(jī)號(hào)獲取短信驗(yàn)證碼實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-04-04Android淺析viewBinding和DataBinding
這篇文章主要介紹了Android淺析viewBinding和DataBinding,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09Android封裝實(shí)現(xiàn)短信驗(yàn)證碼的獲取倒計(jì)時(shí)
這篇文章主要介紹了Android封裝實(shí)現(xiàn)短信驗(yàn)證碼的獲取倒計(jì)時(shí),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2023-03-03Android 簡(jiǎn)易手勢(shì)密碼開源庫(kù)詳解
本文主要介紹Android 簡(jiǎn)易手勢(shì)密碼,這里主要介紹手勢(shì)密碼如何實(shí)現(xiàn)及簡(jiǎn)單的示例代碼,有需要的同學(xué)可以參考下2016-08-08Android Build類的詳解及簡(jiǎn)單實(shí)例
這篇文章主要介紹了Android Build類的詳解及簡(jiǎn)單實(shí)例的相關(guān)資料,希望通過(guò)本文大家能夠理解掌握這部分內(nèi)容,需要的朋友可以參考下2017-08-08