天天看點

Gradle Builds Everything —— 處理依賴(aar)

我們使用 gradle 的時候,會使用

implementation

,

compile

等方式加入一些依賴,比如,aar 是個最經典的例子。那麼 aar 到底經過 gradle 怎樣的處理使得它能輕松的應用這個産物呢?

認識 aar

首先,

aar

是一個

zip

檔案,這句話應該不難了解,意思是,aar 是 zip 檔案改了字尾名來的,它的二進制格式和 zip 沒有啥兩樣,是以我們完全可以把字尾名改成 zip 再解壓一下。我們以

androidx.recyclerview:recyclerview:1.0.0

這個 GAV 下過來的 aar 為例子。

我們看下解壓了 aar 裡面的檔案是怎麼樣的

Gradle Builds Everything —— 處理依賴(aar)

我們可以看到,資源部分是完好無損的儲存下來的,我們打開裡面的資源檔案,是可以直接檢視的。唯一有變化的是,多了

R.txt

classes.jar

,一個是記錄 aar 打包時候生成的

R.java

裡面的

id

資訊,另外一個就是從 java 源代碼編譯好的 jar 檔案(位元組碼)啦。

通過 聊聊 APK —— 脫離 AS 手工創造 APK 檔案 等系列文章我們可以了解到,apk 的打包過程中,其實是不存在 aar 這個檔案實體的,那麼看完了 aar 的檔案目錄結構,我們可以近似得出結論:aar 是一個有特定格式的 zip 檔案,且必須解壓後才能使用。

事實上後續的一些驗證能證明這個結論。

引入和下載下傳依賴

Gradle 引入依賴我們可能再熟悉不過了:在

Dependencies

這個節點中,加入

implementation/api/compileOnly

等指令即可。這些指令在 gradle 的世界中被稱之為

Configurations

一開始我并不知道為什麼要用這個名字來命名他們。直到我後來看了 gradle 相關的源碼之後,才确定,一個指令真的是代表了一種配置 —— 你使用

implementation

compileOnly

引入相同的依賴,他們的行為是不一緻的,這就是這兩者被定義成不同配置的原因。至于裡面的差別,我們後續再講。想提前了解的同學可以移步官網傳送門:https://docs.gradle.org/current/userguide/managingdependencyconfigurations.html

比如我們引入剛剛的

recyclerview

,隻用這樣就行

dependencies {
   implementation 'androidx.recyclerview:recyclerview:1.0.0'
}
           

如果你指定的是 maven 倉庫的話,其實這裡一開始下的一個 pom 檔案,pom 檔案會指定裡面預設産物,你可以自行下載下傳一個過來看一下,位址是:

https://dl.google.com/dl/android/maven2/androidx/recyclerview/recyclerview/1.0.0/recyclerview-1.0.0.pom

Gradle Builds Everything —— 處理依賴(aar)

我們從這個 pom 檔案裡可以看見這個産物的資訊,以及它的依賴資訊,以及它這些依賴是怎麼被引入到項目中的。這個 pom 在“傳遞依賴”的流程中起了非常重要的作用,如果沒有這個 pom ,那麼我們就沒有辦法進行傳遞依賴了,這也很好的解釋了以下的代碼為什麼是沒有傳遞依賴的:

dependencies {
   implementation 'androidx.recyclerview:recyclerview:[email protected]'
}
           

因為你如果這樣聲明的話,gradle 會直接去尋找 https://dl.google.com/dl/android/maven2/androidx/recyclerview/recyclerview/1.0.0/recyclerview-1.0.0.aar 這個位址而不是 pom 檔案的位址。

gradle 對聲明依賴的下載下傳使用的是 lazy load 的政策,在你真正擷取這個依賴的時候,gradle 才會開始下載下傳這個依賴,不過 android 是在所有任務開始之前有一個

AppPreBuildTask

,它會先把所有的依賴拿出來檢查一遍,是以 android 依賴的下載下傳在這個任務開始的時候就開始了

Gradle Builds Everything —— 處理依賴(aar)

但是這個任務實質上是檢查下對 aar 的配置是否正确,比如在這裡檢查了是否使用了

provided/compileOnly

的方式引入 aar,這樣做是非法的。但是沒有對 aar 進行解包提取裡面的産物。有興趣的人可以自己建立一個 gradle 插件項目,看一看這個類。當然也有更偷懶的方式,比如在 點選下載下傳 一個源碼包,然後解壓,找到相應的類即可。

産物的轉換與轉換器

什麼是産物的轉換(Artifact Transform)呢?這個很好了解。現在,你從 maven 倉庫裡面下過來是一個 aar(zip)包,但是我想要裡面的

classes.jar

拿出來編譯,這怎麼辦到呢?那麼我們需要一個從

aar

classes.jar

的轉換。這個過程就被稱為産物轉換。

産物的轉換涉及的問題其實比較多,我可能使用修改文章或者新開文章的方式盡力把這一節的内容講的更明白一點。

transformer 有兩個版本,v1和v2,其實差别不大,不過 v1 已經被棄用了,但是至少在最新的版本中(agp 3.5.1)還是能使用的,我們來看一個最重要的類:

com.android.build.gradle.internal.dependency.AarTransform

Gradle Builds Everything —— 處理依賴(aar)

這幅圖其實很好了解,就是把一個檔案轉成一個檔案清單,這個動作如果用「解壓」兩個字解釋大家就懂了。一般來說,我們注冊 transformer 很簡單,隻要告訴 gradle,我的 transformer 能接受什麼屬性的檔案,能生産什麼樣屬性的檔案就可以了。這個「屬性」是複合屬性,可以是檔案字尾名,以及其他認為定義的一些屬性。我們來看下注冊的地方:

Gradle Builds Everything —— 處理依賴(aar)

這裡的

AarTransform.getTransformTargets()

是一個 aar 轉換後産物類型的集合,那麼一個 for 循環就是把 aar 裡面所有的檔案解壓到特定的目錄,然後把檔案路徑再傳回給 gradle,後續 gradle 在拉取特定類型的檔案(比如 TYPE_CLASSES 檔案)的時候,就能直接找到轉換後的檔案了(classes.jar),轉換後的結果會緩存,一般路徑是 ~/.gradle/caches/transforms-2 或者 ~/.gradle/caches/transforms-1

我們同時注意到轉換器注冊中有兩句代碼

reg.getFrom().attribute(ARTIFACT_FORMAT, EXPLODED_AAR.getType());
   reg.getTo().attribute(ARTIFACT_FORMAT, transformTarget.getType());
           

這個檔案格式轉換之後需要定義的附帶屬性轉換,我們要提取某個特定屬性(格式)的産物的時候,gradle 會根據注冊的這個

from

to

的組合,一直找到下載下傳的 aar (或者其他依賴)為止

轉換後産物的擷取

前面的幾節我們講了 aar 的檔案結構,引入 aar,下載下傳 aar,并從 aar 裡面解壓(轉換)出我們要的最終産物。那麼我們現在怎麼拿到最終産物呢?比如我們需要擷取所有 aar 裡面的

classes.jar

用于編譯最終項目裡的 java 源碼,那麼,我們需要擷取所有依賴裡面的

classes.jar

。我們就以代碼為例吧。

首先需要用到的類是:ConfigurationContainer, 這個類可以使用 project.getConfigurations() 拿到。然後調用

ConfigurationContainer#getByName()

擷取到指定 Configuration 的依賴清單。一開始我們已經定義了 Configuration 了,那麼我們可以擷取

implementation

為例:

project.getConfigurations().getByName("implementation")

這樣就擷取到所有以 implementation 配置的相關資訊了。這樣我們拿到了一個

Configuration

,調用

getIncoming()

方法,因為是外部依賴,是以是往内傳,那麼

getOutgoing()

就是這個項目的産物了,這個我們以後再講。我們這時候拿到了

ResolvableDependencies

對象,注意單詞

Resolvable

意思是:可解析的。也就是說這個依賴還沒有解析完成,隻有調用了相關函數才能解析。然後調用這個函數的

artifactView()

方法,這是傳入一個視圖,我們可以這樣了解視圖:

我們知道一開始使用

implementation'xxxx'

下載下傳來的産物是一個 aar,那麼這時候我要的是 aar 裡面的 classes.jar 用來編譯 java 源代碼,是以我們需要配置一個視圖把 aar 裡面别的産物過濾掉。這個

ArtifactView

就是這樣的概念。回到

AarTransform

裡面的

switch

流程,我們找到了

caseJAR

的結點,這個 JAR 就是我們需要配置視圖用的東西了,我們把完整代碼列出來:

void showArtifactJars() {
       Configuration implementation = project.getConfigurations().getByName("implementation");
       ResolvableDependencies resolvableDeps = implementation.getIncoming();
       ArtifactView view = resolvableDeps.artifactView(conf -> 
            conf.attributes(
                  attr -> attr.attribute(AndroidArtifacts.ARTIFACT_TYPE, AndroidArtifacts.ArtifactType.JAR.type); //配置 view 過濾的屬性,隻需要jar
            );
        );
        // 以下這一步一般在 Task Execution 階段做,上面的這些在 Task Configuration 階段配置
        Set<File> jars = view.getFiles().getFiles();
        //......
   }
           

最後我們調用

view.getFiles().getFiles();

即可擷取到這些 jar 檔案,當然如果調用

view.getArtifacts().getArtifacts();

你還能擷取到這個 jar 原先是屬于坐标依賴(或者 project 依賴等)的一些資訊。

屬性過濾檔案的資訊我們以後再講,如果有興趣的朋友可以先看官方文檔,這篇文檔值得細讀:

https://docs.gradle.org/current/userguide/dependencymanagementattributebasedmatching.html