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

咱們使用 gradle 的時候,會使用implementation, compile等方式加入一些依賴,好比,aar 是個最經典的例子。那麼 aar 到底通過 gradle 怎樣的處理使得它能輕鬆的應用這個產物呢?html

認識 aar

首先,aar是一個zip文件,這句話應該不難理解,意思是,aar 是 zip 文件改了後綴名來的,它的二進制格式和 zip 沒有啥兩樣,因此咱們徹底能夠把後綴名改爲 zip 再解壓一下。咱們以androidx.recyclerview:recyclerview:1.0.0這個 GAV 下過來的 aar 爲例子。java

咱們看下解壓了 aar 裏面的文件是怎麼樣的 android

解壓aar.jpg
咱們能夠看到,資源部分是無缺無損的保存下來的,咱們打開裏面的資源文件,是能夠直接查看的。惟一有變化的是,多了 R.txtclasses.jar,一個是記錄 aar 打包時候生成的 R.java裏面的 id信息,另一個就是從 java 源代碼編譯好的 jar 文件(字節碼)啦。

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

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

引入和下載依賴

Gradle 引入依賴咱們可能再熟悉不過了:在Dependencies這個節點中,加入implementation/api/compileOnly等指令便可。這些指令在 gradle 的世界中被稱之爲Configurations一開始我並不知道爲何要用這個名字來命名他們。直到我後來看了 gradle 相關的源碼以後,才肯定,一個指令真的是表明了一種配置 —— 你使用implementationcompileOnly 引入相同的依賴,他們的行爲是不一致的,這就是這二者被定義成不一樣配置的緣由。至於裏面的區別,咱們後續再講。想提早了解的同窗能夠移步官網傳送門:docs.gradle.org/current/use…maven

好比咱們引入剛剛的recyclerview,只用這樣就行ide

dependencies {
   implementation 'androidx.recyclerview:recyclerview:1.0.0'
}
複製代碼

若是你指定的是 maven 倉庫的話,其實這裏一開始下的一個 pom 文件,pom 文件會指定裏面默認產物,你能夠自行下載一個過來看一下,地址是:函數

dl.google.com/dl/android/…gradle

POM

咱們從這個 pom 文件裏能夠看見這個產物的信息,以及它的依賴信息,以及它這些依賴是怎麼被引入到項目中的。這個 pom 在「傳遞依賴」的流程中起了很是重要的做用,若是沒有這個 pom ,那麼咱們就沒有辦法進行傳遞依賴了,這也很好的解釋瞭如下的代碼爲何是沒有傳遞依賴的:ui

dependencies {
   implementation 'androidx.recyclerview:recyclerview:1.0.0@aar'
}
複製代碼

由於你若是這樣聲明的話,gradle 會直接去尋找 dl.google.com/dl/android/… 這個地址而不是 pom 文件的地址。

gradle 對聲明依賴的下載使用的是 lazy load 的策略,在你真正獲取這個依賴的時候,gradle 纔會開始下載這個依賴,不過 android 是在全部任務開始以前有一個AppPreBuildTask ,它會先把全部的依賴拿出來檢查一遍,所以 android 依賴的下載在這個任務開始的時候就開始了

AppPreBuildTask

可是這個任務實質上是檢查下對 aar 的配置是否正確,好比在這裏檢查了是否使用了provided/compileOnly的方式引入 aar,這樣作是非法的。可是沒有對 aar 進行解包提取裏面的產物。有興趣的人能夠本身創建一個 gradle 插件項目,看一看這個類。固然也有更偷懶的方式,好比在 點擊下載 一個源碼包,而後解壓,找到相應的類便可。

產物的轉換與轉換器

什麼是產物的轉換(Artifact Transform)呢?這個很好理解。如今,你從 maven 倉庫裏面下過來是一個 aar(zip)包,可是我想要裏面的classes.jar拿出來編譯,這怎麼辦到呢?那麼咱們須要一個從aarclasses.jar的轉換。這個過程就被稱爲產物轉換。

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

transformer 有兩個版本,v1和v2,其實差異不大,不過 v1 已經被棄用了,可是至少在最新的版本中(agp 3.5.1)仍是能使用的,咱們來看一個最重要的類:com.android.build.gradle.internal.dependency.AarTransform

transform

這幅圖其實很好理解,就是把一個文件轉成一個文件列表,這個動做若是用「解壓」兩個字解釋你們就懂了。通常來講,咱們註冊 transformer 很簡單,只要告訴 gradle,個人 transformer 能接受什麼屬性的文件,能生產什麼樣屬性的文件就能夠了。這個「屬性」是複合屬性,能夠是文件後綴名,以及其餘認爲定義的一些屬性。咱們來看下注冊的地方:

轉換器註冊

這裏的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 會根據註冊的這個fromto的組合,一直找到下載的 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流程,咱們找到了case JAR的結點,這個 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 依賴等)的一些信息。

屬性過濾文件的信息咱們之後再講,若是有興趣的朋友能夠先看官方文檔,這篇文檔值得細讀:

docs.gradle.org/current/use…

歡迎關注個人公衆號「TalkWithMobile」

公衆號
相關文章
相關標籤/搜索