OkBuck, underneath the hood

dttg5783 10年前發布 | 9K 次閱讀 Java Android Gradle Android開發 移動開發

來自: http://blog.piasy.com/OkBuck-Underneath-the-hood/

本文對我目前在github上收獲star最多的開源項目 OkBuck 的工作原理進行了深度解析,并在本文寫作過程中完成了對OkBuck的第三輪重構,作為OkBuck 1.0版本發布的基礎。

淵源

15年9月份開始了解到快速打包相關的技術,此時已經飽受gradle打包龜速的痛苦。

首先了解到的是 LayoutCast ,但由于其只支持Android 5.0 以上(ART)的手機,雖然5.0的測試機肯定有,但還有大多數測試機不是5.0,所以還是有很多時候會比較慢,所以沒有采用。這時候BUCK進入了視野。

國慶期間嘗試了一下BUCK,深深覺得下載依賴的jar/aar文件,編寫BUCK腳本特別麻煩,尤其是一旦加了新的依賴,還需要維護BUCK腳本以及依賴文件,是一件持續的麻煩事。恰好當時想要趁著國慶期間做點東西,而BUCK配置的編寫與維護也有可能自動化,所以萌生了OkBuck的想法,OkBuck的命名受到了Okio和OkHttp的啟發。

OkBuck的目標,是通過讀取工程的gradle配置,自動生成BUCK腳本,免去開發者下載依賴的jar/aar文件,編寫、維護BUCK腳本,處理依賴之間的沖突等繁瑣又容易出錯的工作。

面臨的問題

  • 獲取每個module的依賴,包括從maven等服務器獲取的依賴、本地libs目錄下的jar/aar依賴、工程內的其他module;
  • 獲取apt依賴,并解析出annotation processor;
  • 避免依賴沖突:同一個庫,在同一個BUCK的rule中,不能出現多個不同版本;
  • 獲取工程的各種配置:build config,sign config,是否開啟multidex,是否debuggable等;
  • BUCK rule的生成,BUCK文件的生成;
  • multi product flavor支持;
  • exopackage支持;

由于經驗有限,很多問題都是通過摸索的方式解決的,沒有查閱gradle的API文檔,代碼也比較原始。在0.3.0版本期間,進行了第二次代碼的重構,一定程度上優化了代碼,但是解決問題的方式,依然不太優雅,而且對于gradle版本,以及Android gradle插件的版本,兼容性也存在問題。

農歷新年之前,正好公司項目進度沒那么吃緊了(浮云 :joy:),趁此機會好好總結一下OkBuck的工作原理,分析解決問題的方式、架構、代碼的不優雅之處,準備進行第三輪重構,另外完善部分功能,整理文檔之后,發布1.0版本。

獲取sub module的依賴

gradle api內部定義了 Dependency 系統,提供了接口獲取,但它并不完整,主要是缺乏libs目錄下的jar/aar依賴。

下面的方式可以獲取到最終所有的依賴的本地文件:

考慮到后續buck編譯以及依賴沖突檢測的需求,OkBuck依賴獲取的方式同時使用了上面這兩種方式。

依賴的分類參照gradle api,按照configuration分類,每個sub project都會有多個configuration,最終的flavorVariant組合的依賴,將合并該組合下所有configuration的依賴,這個合并的過程需要去除完全一樣的依賴記錄(本地依賴文件相同),但對沖突的依賴,應該進行檢測與警告。

而對于本地的android library module,如果它有多個flavor,對于其依賴者的某種flavorVariant組合中,只可能依賴它的一種flavorVariant組合,所以如果一個library module有多flavor,那么它的不同flavorVariant組合將是不同的依賴。

最終獲取到的所有依賴,使用自定義的 Dependency 類型進行封裝,它需要提供的接口包括:

  • 類型,用于區分后續buck的處理;
  • 本地jar/aar文件,非sub module類,需要拷貝文件;
  • src/flavor/res ... rule對應的名字,用于聲明buck依賴;
  • 上述rule的存在性判斷;

sub module依賴的分析工作分拆如下:

  • 依賴獲取分離為 DependencyExtractor ,專門負責獲取各種configuration的依賴,以及annotation processor;
    • 輸出的依賴就是每個sub module的每個flavor/variant下的依賴集合;
  • 類型判斷、重復檢測邏輯,分離到 Dependency 抽象類中;
  • DependencyAnalyzer 只負責分析工作,包括把所有間接依賴都集中起來,沖突檢測,設置本地文件保存路徑等;
    • 輸出的依賴就是每個sub module在每種flavor + variant組合下的依賴集合,進行了去重與沖突檢測,分配好了本地保存路徑,這份輸出將直接用于后續每個sub module的buck rule的依賴;
    • 組合flavor/variant;
    • 重復和沖突檢測;
    • 分配本地保存路徑;
  • DependencyProcessor 進行依賴的處理工作,把依賴文件拷貝到okbuck目錄,并為它們生成一個BUCK配置文件,以及解析Android application module的簽名配置;

annotation processor相關

annotation processor依賴和module的普通依賴類似,只不過configuration是 apt 和 provided ,這兩種是目前通用的慣例,Android module涉及到注解處理的,基本都是用的apt插件,而Java module,常用的做法也是聲明一個 provided configuration,并添加到classpath中,就像這樣:

而annotation processor類的提取,則可以提取對應jar文件中的 META-INF/services/javax.annotation.processing.Processor 文件部分。需要注意的是,一個jar包里面可能會有多個annotation processor,需要全部解析出來。

避免依賴沖突

依賴沖突是引入BUCK過程中最痛苦的一件事,執行BUCK打包命令時,經常報形如 Multiple dex files define*** 的錯誤,就是因為發生了依賴沖突。

OkBuck的做法是,任何rule都不要使用 exported_deps 參數,然后把整個工程所有的依賴集中進行分析,每個module rule的 deps 部分,就是該module的所有依賴,包括直接和間接。此外,除了annotation processor的依賴,其他的module rule的依賴,也沒有使用BUCK官方樣例中的 all_jars 方式,避免同一依賴文件同時存在多份造成依賴沖突。

同時OkBuck也采取了不同版本依賴沖突檢測的機制,可以通過配置控制是否在這種情況下失敗并提示。

  • 遍歷所有sub module(project),利用第一種方法,獲取到所有的maven依賴以及sub module,用于檢測依賴沖突,對gradle的合理使用情況下,這些依賴將是整個工程依賴的大部分;
  • 同時利用第二種方式,獲取到所有的依賴本地文件,兩者求差集,即可得到本地libs目錄下的依賴,這部分依賴就只能使用文件名來進行重復檢測了;
  • 此外,也需要檢測maven依賴和這部分依賴的重復,這一步檢測也只能使用文件名進行;
  • 以example工程為例,如果 javalibrary 在libs文件夾中引入了gson-2.4.jar,但是又在 app 中通過聲明maven依賴引入了gson 2.3,此時就會提示: in app, gson-2.4.jar is duplicated with gson-2.3.jar ;當然這個提示其實并不能直接找到根本原因,后續會改進;
  • 如果提示依賴沖突,可以先通過 ./gradlew dependencies 命令來查看整個工程的依賴列表,進行排查;

在此次重構過程中,避免依賴沖突的時候,遇到了一個問題:最初我是直接把每個module的每個flavor + variant組合下的依賴單獨放在一個目錄中,這就造成了同一個依賴文件會存在多份,導致了依賴沖突。因此需要把公共依賴集中起來,保證同一個依賴只會在okbuck目錄下存在一份(apt依賴可以不考慮)。

解決辦法很簡單,只需要為每個dep分配好合適的dstDir即可,將依賴其的module名字按字典序排列拼接,然后計算hash之后,作為目標目錄(之所以要hash,是為了避免出現目錄名太長的問題)。這一個改變基本是在重構完成之后進行的,但是代碼上的改動非常小,只需改變為每個依賴設置dstDir的策略即可,這也從側面上反映出來此次重構的成功性。

獲取工程的各種配置

以 minSdkVersion 為例:

multi product flavor支持

BUCK原生不支持multi product flavor,OkBuck通過解析每個flavor的配置,同時為每種flavor + variant組合生成一套BUCK配置,達到支持multi product flavor的效果。

對于flavor較多的情況,生成的BUCK文件會比較復雜,解析時間會達到1s以上,所以OkBuck也加上了一個flavor filter的選項,可以只生成指定flavor的BUCK文件。之所以不通過gradle task運行參數控制,是由于可能存在OkBuck無法滿足的需求,仍需手動稍微修改生成的BUCK文件,這種情況下,每次切換flavor都需要重新編輯,會很麻煩。

BUCK rule、BUCK文件的生成

OkBuck為buck的每種rule對應建立了一個類, AndroidBinaryRule , AndroidLibraryRule 等,它們繼承自公共基類 AbstractBuckRule ,提供 print 接口,把rule的內容打印到 PrintStream 中,并且按照它們的組成(參數),分為幾大類型,將打印工作分攤開來,簡化了每個類的大小,提高可讀性與可擴展性。

上述依賴分析,以及工程配置的問題解決之后,就可以為每個sub module生成各類buck rule,并最終組成buck腳本了。步驟如下:

  • java library module
    • 目前gradle并未支持flavor與variant,也不支持build config,所以只需要考慮main目錄下的代碼,compile選項的依賴,以及注解處理
    • 只能依賴jar包,不能依賴aar包
    • 只需要生成java library和project config兩個rule
  • android library module
    • 需要支持flavor和variant,包括:java, res, assets, build config, jniLibs, aidl
    • java代碼,每種flavor + variant組合將對應一個android_library rule,它包括了該種組合下所有源碼目錄內的代碼
    • res和assets,同屬于android resource rule,每個源碼目錄下均可能存在,每個源碼目錄將生成一個rule,而每種flavor + variant的組合,將需要使用該組合下所有源碼目錄對應的android resource rule
    • build config, aidl和java代碼類似,每種flavor + variant組合對應一個相應的rule
    • jniLib和res類似,每個源碼目錄需要一個rule
    • 上述差異主要是由buck rule參數的類型造成的
    • 由于manifest比較特殊,暫時不支持manifest的多flavor,只允許在main目錄下存在一個manifest,它將作為android library rule的參數被設置在每個android library rule中
  • android application module
    • 需要支持flavor和variant,范圍和android library module相同
    • 不同之處在于android library rule不包括manifest,manifest將有單獨的rule,供android binary使用
    • 此外還需要為每種flavor + variant組合生成一個android_binary rule
    • manifest比較特殊,先使用一個 genrule ,以main源碼目錄下的manifest文件生成一個skeleton,再合并依賴的manifest,最終為每種flavor + variant組合生成一個android_manifest rule
    • key store也需要為每種flavor + variant組合(是否可以為每種flavor配置sign config?)生成一個keystore rule
  • 其他buck文件
    • .buckconfig,配置整個buck項目
    • manifest生成有一個genrule,需要一個buck文件描述
    • .okbuck目錄下保存的是所有的依賴本地文件,每個目錄都需要一個buck文件進行描述
  • 整個過程
    • 遍歷每個sub project
    • 判斷類型,不同類型生成不同的buck文件(由BuckFile進行封裝),且它們的生成邏輯分離到各個實現類中
    • BuckFile生成后,統一進行打印、依賴文件處理
  • Windows兼容的部分暫時移除

exopackage支持

支持exopackage時,multidex的 primary_dex_patterns ,以及app lib的依賴,目前都是交給用戶配置的,目前還沒有好的方案自動化獲取。

而用戶配置的具體值,只能依靠用戶逐步測試,根據buck打包/app運行報錯信息,逐步完善。不過這一步也還是值得的,一旦完成之后,后面基本不用修改。

</div>

 本文由用戶 dttg5783 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!