capt 全稱 Class Annotation Processor Tool闽晦,是作者基于 ASM 和 Android Transform API 打造的 Android 平臺(tái)的字節(jié)碼的注解處理工具溯捆。
起源
apt
capt 的靈感有很大一部分來自于 apt。作為一個(gè) Android 開發(fā)者,雖然 apt 已經(jīng)足夠強(qiáng)大哈蝇,但是有個(gè)缺陷一直是我們的痛點(diǎn):只支持源碼圃郊。
Android 日常開發(fā)中,大量的依賴了第三方庫如 AAR / JAR且改,而他們是已經(jīng)編譯成了 class 字節(jié)碼验烧,而 apt 僅支持源碼級(jí)別的注解處理,因?yàn)槲覀儾坏貌煌ㄟ^很多曲折的方式來實(shí)現(xiàn)原本簡單的邏輯又跛。
舉個(gè)例子:阿里開源的 ARouter碍拆,在早期只有 apt 的版本中對(duì)模塊化的處理是通過顯式的添加模塊參數(shù)來實(shí)現(xiàn)的,在新版中也通過注冊 Android Transform API 在打包過程中修改部分字節(jié)碼打到目的。
Lancet
Lancet 是作者在早些時(shí)候在 eleme 開源的 Android AOP 框架感混,就是基于 Transform API 來實(shí)現(xiàn)的端幼。由于 GitHub eleme 組織的廢棄,在幾個(gè)月前作者創(chuàng)建了獨(dú)立的 Lancet 項(xiàng)目并規(guī)劃 Lancet2 的開發(fā)弧满。在思考的過程中婆跑,覺得僅僅是 Lancet2 還不夠酷,有以下幾個(gè)原因:
- 注解是固定的幾種庭呜,使用很受限制
- 無論是 Lancet2 還是 Lancet1 都有大量重復(fù)的代碼滑进,浪費(fèi)在主流程的無關(guān)邏輯上
于是作者萌生了一個(gè)想法,我們能不能做一個(gè)工具募谎,只做注解處理和代碼轉(zhuǎn)換的分發(fā)邏輯扶关,具體的業(yè)務(wù)邏輯由插件完成。
說干就干数冬,于是 capt 誕生了节槐。
capt
相比 apt,capt 除了提供注解處理之外拐纱,還允許多個(gè)插件鏈?zhǔn)降匦薷拿總€(gè)類的字節(jié)碼铜异。
同時(shí) capt 有以下幾大特性:
完全同步的 Variant
和annotationProcessor
類似, capt 會(huì)為每個(gè)SourceSet / BuildType / ProductFlavor
創(chuàng)建對(duì)應(yīng)的Configuration
,你可以用如下的方式戳玫,根據(jù)不同的構(gòu)建類型熙掺,來使用不同的 capt 插件:
dependencies {
capt project(":xx")
capt "xx:xx:1.0"
capt files("...")
releaseCapt ...
androidTestCapt ...
}
Application & Library
capt 同時(shí)支持 Android Application 和 Library 的注解處理和注入代碼:
- Application: 所有的 runtime class
- Library: 僅該 Library 自身
APK & AndroidTest
capt 支持打普通 APK 和 AndroidTest 時(shí)注入:
- APK: 打普通 APK 時(shí)會(huì)傳遞所有的 APK 中的類
- AndroidTest: AndroidTest 打包時(shí)只會(huì)傳遞所有 androidTest 目錄下的類
靈活的參數(shù)
capt 會(huì)為每個(gè)注冊的插件創(chuàng)建一個(gè) Gradle Extension 對(duì)象,可以傳入任意形式的參數(shù)咕宿,在插件的生命周期傳遞給插件币绩,同時(shí)每個(gè) Extension 也會(huì)內(nèi)置插件的公共參數(shù),如優(yōu)先級(jí)(可覆蓋插件中聲明的默認(rèn)優(yōu)先級(jí))府阀、作用域(Assemble | AndroidTest)等缆镣。
極致的增量更新
capt 會(huì)針對(duì)每個(gè) Variant 有自身的緩存,記錄類圖试浙、插件和插件的修改的類等元信息董瞻。
注解處理
capt 會(huì)解析類圖,分析類圖中類的的變化(添加田巴、修改钠糊、刪除),因此 capt 要求所有的插件注解處理器都要支持增量解析壹哺,因此 capt 對(duì)打包時(shí)間的影響很小抄伍。并且性能一直是 cpat 持續(xù)追求的目標(biāo)
當(dāng)然有得有失,因?yàn)樵谠隽磕J较鹿芟凶⒔獾念悰]有發(fā)生變化是不會(huì)傳遞到注解處理器中的截珍,因此需要每個(gè)插件自己去實(shí)現(xiàn)本地緩存的邏輯攀甚。
類轉(zhuǎn)換
capt 遵循最小原則,因此類轉(zhuǎn)換有3步:
- capt 會(huì)詢問每個(gè)插件需要哪些類(全量岗喉、增量秋度、無),以及是否解析額外的類
- 如果上次構(gòu)建過程中的插件被移除(移除插件不會(huì)打斷增量構(gòu)建流程钱床,增加會(huì))荚斯,capt 會(huì)額外添加被移除插件修改過的類
- 最后 capt 會(huì)匯總上面的信息,并據(jù)此來分發(fā)類的轉(zhuǎn)換流程诞丽。
豐富的 API
capt 在插件的生命周期會(huì)提供豐富的 API鲸拥,如類結(jié)構(gòu)圖、上下文僧免、甚至于ClassLoader,讓每個(gè)插件的奇思妙想都能夠?qū)崿F(xiàn)捏浊。如果有更多更好的想法也可以提 Issue & PR 哦懂衩。
對(duì) ASM 的高度支持
capt 允許在類轉(zhuǎn)換前對(duì) ClassReader 和 ClassWriter 請求額外的標(biāo)志位,從而提供了最大的靈活性金踪,同時(shí)默認(rèn)都為 0 以期最佳性能浊洞。
并且 capt 為 ClassWriter.COMPUTE_FRAME 生成了獨(dú)特的 ClassLoader,開發(fā)者不再需要糾結(jié)于代碼中的計(jì)算棧幀信息的問題胡岔。
未來規(guī)劃
- 基于 capt 開發(fā) Lancet2
- 持續(xù)優(yōu)化性能
- 更多豐富的 API
開源地址
https://github.com/CoffeePartner/capt
如果覺得 capt 還不錯(cuò)法希,請給 capt 一個(gè) Star!
歡迎 Issue & PR靶瘸。