Gradle學習筆記(四)-- fat-aar.gradle解析

fat-aar.gradle是什么?

在做android應用程序開發時,我們一般都會構建多個模塊,來達到解耦的目的,但是有的需求是需要我們提供一個依賴庫給外部使用,這時候就遇到一個問題:多個module確實達到了解耦的目的,同時也意味著對外提供依賴庫時要提供多個aar,一個依賴module對應一個aar。fat-aar 的功能簡單來說就是讓你能夠合并和插入各種依賴到一個aar中

實現原理

首先,我們來看打包的一個aar的結構:

它的文件后綴名是.aar,它本身是一個zip文件,強制包含以下文件:
/AndroidManifest.xml
/classes.jar
/res/
/R.txt

另外,AAR文件可以包括以下可選條目中的一個或多個:
/assets/
/libs/name.jar
/jni/abi_name/name.so (where abi_name is one of the Android supported ABIs)
/proguard.txt
/lint.jar

這里假設有兩個libs,分別為module1和module2。此時,為了合成一個aar,需要將module2中的.jar,res,assets中的文件資源插入到module1中,將AndroidManifest.xml和R.txt相互合并Merge。
沒錯,核心思想就是將一個file下的文檔拷貝到另一個下面,合并名稱相同的兩個文件,當然,這些都是在恰當的Task前或后插入的

這里就引申出下面一個問題:

  1. 如何確認需要合并的module?
  2. 確定需要合并的module后,如何找到相應的文件路徑?
  3. 不同的文件在Gradle Tasks中的哪個位置插入?

腳本解構

帶著上面的問題,我們來看下包含兩個libs的這個例子:


module1 和 module2

來看下module1下的build.gradle,引入了fat-aar.gradle:

apply plugin: 'com.android.library'
apply from: 'fat-aar.gradle'
apply from: 'debug.gradle'
android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"

    defaultConfig {
        minSdkVersion 10
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.1.1'
    testCompile 'junit:junit:4.12'
    embedded project(':module2') //注意這里的embedded 
}

在第一次使用fat-aar時,對這個embedded 特別的不解,為什么不是compile或者provided ?然后,我看了下fat-aar.gradle的代碼,發現了這個:

configurations { 
    embedded  //這里是定義了一個configuration,而這個configurations 映射的則是一個 ConfigurationContainer對象
}

dependencies {
    compile configurations.embedded
}

configurations

embedded 實際上是一個 configuration,上面的embedded project(':module2') 則是將這個project作為dependency add 進了這個configuration,同時返回這個Dependency對象。

調用embedded project(':module2')

而這個Configuration對象包含哪些屬性呢?當然,肯定是有dependencies,另外一個就是artifacts。

這里回到第一個問題, 如何確認需要合并的module?
通過自定義configuration embedded將這個module2關聯起來。

而第二個問題,確定需要合并的module后,如何找到相應的文件路徑?
我們知道在build一個aar的時候,都會在各個module下生成一個build文件夾,看下圖:

fat-aar中不同文件的路徑定義

我們生成aar相關聯的文件夾是這個exploded-aar,當我看了下這個文件夾的結果時,發現了一個有趣的事情:除了support和test,多出了一個MyApplication/module2。哈哈,這個其實在complie project(':module2')時也是有的。辣么,我們便可以通過這個路徑獲取下面編譯階段生成的不同類型文件合并就可以了

module1下的exploded-aar

這里有個大前提,獲取路徑,這里就要先獲取路徑名稱:

//定義一個包含Dependencies的list
 def dependencies = new ArrayList(configurations.embedded.resolvedConfiguration.firstLevelModuleDependencies)
    //遍歷依賴將確定的路徑放到另一個list embeddedAarDirs 中。
    dependencies.reverseEach { 
        def aarPath = "${exploded_aar_dir}/${it.moduleGroup}/${it.moduleName}/${it.moduleVersion}"
        println("shang--->" + aarPath)
        println("shang--->" + configurations.embedded.getName())
        it.moduleArtifacts.each {
            artifact ->
                if (artifact.type == 'aar') {
                    if (!embeddedAarDirs.contains(aarPath)) {
                        embeddedAarDirs.add(aarPath)
                    }

                } else if (artifact.type == 'jar') {
                    def artifactPath = artifact.file
                    if (!embeddedJars.contains(artifactPath))
                        embeddedJars.add(artifactPath)
                } else {
                    throw new Exception("Unhandled Artifact of type ${artifact.type}")
                }
                println("shang--->artifact:" + artifact)
        }
    }

我就好奇的打印了aar和artifact:

shang--->D:/work/MyApplication/module1/build/intermediates/exploded-aar/MyApplication/module2/unspecified
shang--->embedded
shang--->artifact:[ResolvedArtifact dependency:org.gradle.api.internal.artifacts.ivyservice.dynamicversions.DefaultResolvedModuleVersion@b5eb5da name:module2 classifier:null extension:aar type:aar]

可以看到路徑的問題也得到了解決。

繼續下一個問題,不同的文件在Gradle Tasks中的哪個位置插入?
fat-aar是這樣做的,通過不同的task之間的dependsOn ,mustRunAfter 這種方式來插入自定義的task,如下:

// Merge Assets
generateReleaseAssets.dependsOn embedAssets
embedAssets.dependsOn prepareReleaseDependencies

// Embed Resources by overwriting the inputResourceSets
packageReleaseResources.dependsOn embedLibraryResources
embedLibraryResources.dependsOn prepareReleaseDependencies

// Embed JNI Libraries
bundleRelease.dependsOn embedJniLibs
embedJniLibs.dependsOn transformNative_libsWithSyncJniLibsForRelease

// Merge Embedded Manifests
bundleRelease.dependsOn embedManifests
embedManifests.dependsOn processReleaseManifest

// Merge proguard files
embedLibraryResources.dependsOn embedProguard
embedProguard.dependsOn prepareReleaseDependencies

// Generate R.java files
compileReleaseJavaWithJavac.dependsOn generateRJava
generateRJava.dependsOn processReleaseResources

// Bundle the java classes
bundleRelease.dependsOn embedJavaJars
embedJavaJars.dependsOn compileReleaseJavaWithJavac

// If proguard is enabled, run the tasks that bundleRelease should depend on before proguard
if (tasks.findByPath('proguardRelease') != null) {
   proguardRelease.dependsOn embedJavaJars
} else if (tasks.findByPath('transformClassesAndResourcesWithProguardForRelease') != null) {
   transformClassesAndResourcesWithProguardForRelease.dependsOn embedJavaJars
}

看到這里,有些地方還是不明白,那就是android app 編譯過程中task都有哪些?預定義的task都是什么?每個task的功能有是什么?
這里在build.gradle里調用一下腳本便可以打印該模塊下所有的task:

afterEvaluate {
    tasks.each {
        task ->
            task << {
                //checkNewFiles()
            }
            println task
    }
}

在console下鍵入:

gradlew clean assembleDebug > log.txt

再這里截取module1里執行的task,以及自定義task執行的位置,順序執行,關注有顏色的行,我們便大致知道了執行的順序,包括預定義的task執行:

:module1:prepareComAndroidSupportAnimatedVectorDrawable2511Library
:module1:prepareComAndroidSupportAppcompatV72511Library
:module1:prepareComAndroidSupportSupportCompat2511Library
:module1:prepareComAndroidSupportSupportCoreUi2511Library
:module1:prepareComAndroidSupportSupportCoreUtils2511Library
:module1:prepareComAndroidSupportSupportFragment2511Library
:module1:prepareComAndroidSupportSupportMediaCompat2511Library
:module1:prepareComAndroidSupportSupportV42511Library
:module1:prepareComAndroidSupportSupportVectorDrawable2511Library
:module1:prepareMyApplicationModule2UnspecifiedLibrary
:module1:prepareReleaseDependencies
:module1:compileReleaseAidl
:module1:compileReleaseNdk UP-TO-DATE
:module1:compileLint
:module1:copyReleaseLint UP-TO-DATE
:module1:compileReleaseRenderscript
:module1:generateReleaseResValues
:module1:generateReleaseResources
:module1:mergeReleaseResources
:module1:processReleaseManifest
:module1:processReleaseResources
:module1:generateRJava

<Running FAT-AAR Task :generateRJava>

:module1:generateReleaseBuildConfig
:module1:generateReleaseSources
:module1:incrementalReleaseJavaCompilationSafeguard
:module1:compileReleaseJavaWithJavac
:module1:compileReleaseJavaWithJavac - is not incremental (e.g. outputs have changed, no previous execution, etc.).
:module1:collectRClass
:module1:embedRClass
:module1:embedJavaJars

<Running FAT-AAR Task :embedJavaJars>

:module1:mergeReleaseShaders
:module1:compileReleaseShaders
:module1:embedAssets

<Running FAT-AAR Task :embedAssets>

:module1:generateReleaseAssets
:module1:mergeReleaseJniLibFolders
:module1:transformNative_libsWithMergeJniLibsForRelease
:module1:transformNative_libsWithSyncJniLibsForRelease
:module1:embedJniLibs

<Running FAT-AAR Task :embedJniLibs>

======= Copying JNI from D:/work/MyApplication/module1/build/intermediates/exploded-aar/MyApplication/module2/unspecified/jni
:module1:embedManifests

<Running FAT-AAR Task :embedManifests>

========== INFO : Loading library manifest D:\work\MyApplication\module1\build\intermediates\exploded-aar\MyApplication\module2\unspecified\AndroidManifest.xml
========== INFO : Merging main manifest D:\work\MyApplication\module1\build\intermediates\bundles\release\AndroidManifest.orig.xml

========== INFO : Merging library manifest D:\work\MyApplication\module1\build\intermediates\exploded-aar\MyApplication\module2\unspecified\AndroidManifest.xml
========== INFO : Merging manifest with lower AndroidManifest.xml:2:1-17:12
========== INFO : Merging uses-sdk with lower AndroidManifest.xml:7:5-9:41
========== INFO : Merging application with lower AndroidManifest.xml:11:5-15:19
========== INFO : Merging result:SUCCESS
========== INFO : Merged manifest saved to D:\work\MyApplication\module1\build\intermediates\bundles\release\AndroidManifest.xml
========== INFO : Merged aapt safe manifest saved to D:\work\MyApplication\module1\build\intermediates\manifests\aapt\release\AndroidManifest.xml
:module1:extractReleaseAnnotations
:module1:mergeReleaseAssets
:module1:mergeReleaseProguardFiles UP-TO-DATE
:module1:packageReleaseRenderscript UP-TO-DATE
:module1:embedProguard

<Running FAT-AAR Task :embedProguard>

:module1:embedLibraryResources

<Running FAT-AAR Task :embedLibraryResources>

:module1:packageReleaseResources
:module1:processReleaseJavaRes UP-TO-DATE
:module1:transformResourcesWithMergeJavaResForRelease
:module1:transformClassesAndResourcesWithSyncLibJarsForRelease
:module1:bundleRelease

而各個task的功能大家可以關注下文末鏈接android-fat-aar,在這里就不過多分析,基本是文件的操作。

總結反思

在閱讀fat-aar腳本的時候,開始有些地方似懂非懂,就接著往下看,結果看到最后再回頭看前面就更不懂了,在此反思,其實不是“悟性”不夠的問題,而是解決問題的方式的問題,對于這類“腳本”,首先看下文檔,理清大致的結構,在分析的時候,要步步為營,不懂的地方要查看文檔,合理的猜測驗證;再找一些實際的例子一一分析,最后自己嘗試寫一些自定義的東西,這也是寫這一系列文字的原因。

參考

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容