fat-aar原理分析

fat-aar介紹

fat-aar地址:github.com/adwiv/andro…java

Gradle script that allows you to merge and embed dependencies in generated aar file.
複製代碼

根據官方介紹,fat-aar是一個容許你合併和嵌入依賴關係到生成的aar文件的gradle腳本,通俗點說,fat-aar的主要工做就是將project中內部所依賴的aar下載到本地,而後一塊兒發佈aar到maven庫,外部項目依賴aar時不用去下載project內部依賴的aarandroid

爲何引入fat-aar

咱們發佈aar時,會帶有一個aarName-x.x.x.pom文件(aarName是aar的名字,x.x.x是版本號),這個pom文件裏面定義了咱們aar內部依賴的aar,外部project依賴該aar會在編譯時去下載其內部依賴的aar;對外發布的aar中若是有依賴內網maven庫中的aar,會因沒法鏈接內部maven庫致使下載內部aar失敗,最終編譯失敗。git

因此在這裏引入fat-aar解決方案,在對外發布aar時將其內部依賴的aar所有下載到本地,發佈時一塊兒打包進去,外部project在編譯時不用去內部maven庫下載github

fat-aar解決思路

  1. 首先了解下aar包結構bash

    aar是Android Library Project的二進制文件包,文件的擴展名是aar,其實文件自己就是一個簡單的Zip文件,解壓後有如下幾種類型,相信Android開發同窗都不會陌生maven

    • /AndroidManifest.xml(必須)
    • /classes.jar(必須)
    • /res/(必須)
    • /R.txt(必須)
    • /assets/(可選)
    • /libs/*.jar(可選)
    • /jni//*.so(可選)
    • /proguard.txt(可選)
    • /lint.jar(可選)

    備註:R.txt文件是aapt --output -text -symbols輸出,aapt相關細節這裏再也不敘述學習

  2. 解決思路:合併aargradle

    如上圖所示,將A依賴的B.aar、C.aar、D.aar合併到A,生成新的A.aarui

    將依賴的外部aar和內部module(能夠當作aar)輸出的N個aar文件進行合併,這樣原來A模塊的調用者接入方式保持不變,並且在依賴A時沒必要再從新下載A內部依賴的其餘aar,能夠提供給外部項目使用而避免訪問內部maven庫的場景lua

    參考上面aar包結構形式,fat-aar合併主要過程爲:

    • 合併Manifest
    • 合併jar
    • 合併res資源
    • 合併R文件(最關鍵的一步)
    • 合併assets
    • 合併libs
    • 合併jni
    • 合併Proguard

fat-aar工做流程

首先了解一下gradle構建流程,gradle執行主要分爲三個過程:

  • Initialization(初始化階段):分析有哪些module將要被構建,爲每一個module建立對應的project實例,settings.gradle文件會被解析
  • Configuration(配置階段):處理全部模塊的build腳本,處理依賴、屬性等,配置執行完後,有一個很重要的回調afterEvaluate,表示全部的模塊都配置完了,能夠執行task了
  • Execution(執行階段):根據task鏈表來執行某一個特定的task,這個task所依賴的其餘task都將會被提早執行

fat-aar就是在配置階段完成進入afterEvaluate回調中,而後執行合併內部依賴aar的操做,咱們在對外發布aar時須要定義Task uploadArchives(如何上傳aar至maven請自行了解),而uploadArchives內部依賴assembleRelease執行結果

fat-aar中定義各子Task的執行流程以下:

// 合併Assets
generateReleaseAssets.dependsOn embedAssets
embedAssets.dependsOn prepareReleaseDependencies

// 合併Resources經過重寫inputResourceSets
packageReleaseResources.dependsOn embedLibraryResources
embedLibraryResources.dependsOn prepareReleaseDependencies

// 合併JNI So庫
bundleRelease.dependsOn embedJniLibs

if (gradleApiVersion >= 2.3f) {
    embedJniLibs.dependsOn transformNativeLibsWithSyncJniLibsForRelease
    ext.bundle_release_dir = "$build_dir/intermediates/bundles/default"
} else {
    embedJniLibs.dependsOn transformNative_libsWithSyncJniLibsForRelease
    ext.bundle_release_dir = "$build_dir/intermediates/bundles/release";
}

// 合併Manifests
bundleRelease.dependsOn embedManifests
embedManifests.dependsOn processReleaseManifest

// 合併proguard
embedLibraryResources.dependsOn embedProguard
embedProguard.dependsOn prepareReleaseDependencies

// 生成R.java
compileReleaseJavaWithJavac.dependsOn generateRJava
generateRJava.dependsOn processReleaseResources

// 把R.java打包進Jar包
bundleRelease.dependsOn embedJavaJars
embedJavaJars.dependsOn compileReleaseJavaWithJavac

// 若是使用混淆, bundleRelease必須在混淆以前執行
if (tasks.findByPath('proguardRelease') != null) {
    proguardRelease.dependsOn embedJavaJars
} else if (tasks.findByPath('transformClassesAndResourcesWithProguardForRelease') != null) {
    transformClassesAndResourcesWithProguardForRelease.dependsOn embedJavaJars
}
複製代碼

想要理解fat-aar執行流程,首先須要知道dependsOn是什麼,dependsOn用來指定Task之間的依賴關係,好比TaskA.depensOn TaskB表示TaskA在TaskB以後執行

與dependsOn相反的mustRunAfter,有興趣的同窗能夠自行了解下

fat-aar執行過程依賴assembleRelease任務,定義了embedAssets、embedLibraryResources、embedJniLibs、embedManifests、embedProguard、generateRJava、embedJavaJars多個Task,而這些Task的執行順序與assembleRelease內部執行順序相關

先來了解一下assembleRelease的執行順序:

:preBuild
:preReleaseBuild
:checkReleaseManifest
:prepareReleaseDependencies
:compileReleaseAidl
:compileReleaseNdk
:compileReleaseRenderscript
:generateReleaseBuildConfig
:generateReleaseResValues
:generateReleaseResources
:mergeReleaseResources
:processReleaseManifest
:processReleaseResources
:generateReleaseSources
:incrementalReleaseJavaCompilationSafeguard
:javaPreCompileRelease
:compileReleaseJavaWithJavac
:extractReleaseAnnotations
:mergeReleaseShaders
:compileReleaseShaders
:generateReleaseAssets
:mergeReleaseAssets
:mergeReleaseProguardFiles
:packageReleaseRenderscript
:packageReleaseResources
:processReleaseJavaRes
:transformResourcesWithMergeJavaResForRelease
:transformClassesAndResourcesWithSyncLibJarsForRelease
:mergeReleaseJniLibFolders
:transformNativeLibsWithMergeJniLibsForRelease
:transformNativeLibsWithSyncJniLibsForRelease
:bundleRelease
:compileReleaseSources
:assembleRelease
複製代碼

注:上面代碼基本上列舉了Gradle構建Android項目時執行assembleRelease的全部Task,Lint、Test等非必需Task除外

根據上面assembleRelease的執行順序,下面逐個分析fat-aar中各Task的執行順序:

  • embedAssets: 合併Assets文件,與Assets文件編譯相關,必須在build的generateReleaseAssets以前執行
  • embedLibraryResources: 合併Res資源文件,與Res資源編譯相關必須在build的packageReleaseResource以前執行
  • embedJniLibs: 合併jni so文件,在build轉換JniLibs以後執行
  • embedManifests: 合併Manifest文件,在processReleaseManifest以後執行
  • embedProguard: 合併Proguard文件,在embedLibraryResources以前執行
  • generateRJava: 生成R.java文件,在build編譯Java文件以前執行
  • embedJavaJars: 打包Jar文件,在build編譯Java文件以後執行

綜上畫出下面的流程圖,能夠幫助咱們更好的理解fat-aar的工做流程(各子Task的執行順序)

備註:

  1. 右邊藍色框內是fat-aar定義的子Task,而後將各子Task插入到assembleRelease相應流程中
  2. fat-aar各子Task之間沒有固定執行順序

fat-aar關鍵流程

理解了fat-aar工做流程以後,其實就是在gradle構建過程當中插入不一樣的Task來實現embedded依賴的aar下載到本地,而後一塊兒發佈到maven庫,embedAssets、embedLibraryResources、embededJniLibs、embedProguard、embedManifests都是進行資源的合併,主要思想是資源路徑的追加或者拷貝,比較簡單,這裏主要分析aar的R.java生成和打包過程

下面咱們來介紹一下generateRJava這個Task,generateRJava的目的是根據aar中的R.txt文件生成對應的R.java文件;項目build過程當中會在/generated/source/r生成R.java文件,generateRJava其實就是模擬這個過程,而build過程當中id的值是根據資源編譯過程當中有序生成的,那麼aar的R.java文件中id的值應該怎麼生成呢?其實很簡單,fat-aar生成R.java過程當中將aar的id值指向了當前project的id,外部項目依賴project時會從新編譯資源文件,生成project的R.java文件,前面已經將資源拷貝到了project中,天然也會生成aar的資源id

貼出代碼以下:

def manifestFile = file("$aarPath/AndroidManifest.xml");
if (!manifestFile.exists()) {
    manifestFile = file("./src/main/AndroidManifest.xml");
}

if (manifestFile.exists()) {
    def aarManifest = new XmlParser().parse(manifestFile);
    def aarPackageName = aarManifest.@package

    String packagePath = aarPackageName.replace('.', '/')

    // 根據R.txt生成R.java文件,全部id指向當前project的R.java
    def rTxt = file("$aarPath/R.txt")
    def rMap = new ConfigObject()

    if (rTxt.exists()) {
        rTxt.eachLine {
            line ->
                // R.txt文件行存儲格式爲type, subclass, name, value
                // 將subclass做爲key,(name, type)做爲value
                def (type, subclass, name, value) = line.tokenize(' ')
                		rMap[subclass].putAt(name, type)
        }
    }
    
    // 寫入包名
    def sb = "package $aarPackageName;" << '\n' << '\n'
    // 寫入類名
    sb << 'public final class R {' << '\n'

    rMap.each {
        subclass, values ->
            // 遍歷rMap,寫入資源類型subclass
            sb << " public static final class $subclass {" << '\n'
            values.each {
                name, type ->
                    // 寫入資源id
                    sb << " public static $type $name = ${libPackageName}.R.${subclass}.${name};" << '\n'
            }
            sb << " }" << '\n'
    }

    // 結束
    sb << '}' << '\n'

    // 建立R.java文件,將sb寫入文件
    mkdir("$generated_rsrc_dir/$packagePath")
    file("$generated_rsrc_dir/$packagePath/R.java").write(sb.toString())

    embeddedRClasses += "$packagePath/R.class"
    embeddedRClasses += "$packagePath/R\$*.class"
}
複製代碼

而後分析embedJavaJars的依賴關係

embedJavaJars.dependsOn embedRClass
embedRClass.dependsOn collectRClass
複製代碼

結合embedJavaJars和generateRJava的執行順序,能夠理解R.java文件生成以及打包R.class的整個流程:首先根據aar的R.txt文件生成各自的R.java文件,經編譯後生成R.class文件,而後將全部R.class文件拷貝到指定目錄下並打包成Jar(以當前project命名,個R.class文件的包名保持不變),最後將包含全部R.class文件的Jar包拷貝到project打包的路徑和project一塊兒對外發布。以下圖

注:$build_dir/intermediates/bundles/release是project最終打包成aar的路徑

fat-aar小結

fat-arr已經好久沒有維護了,可是fat-aar的學習不管是對Android項目構建流程的理解,仍是學習gralde grovvy(DSL,動態描述語言)都有很大的幫助

遇到的問題:

  • gradle插件版本有限制,高版本可能不兼容(不一樣gradle插件版本中包含的Task可能不一樣)
  • generateRJava執行完後可能會報某些id找不到,由於將aar中R.txt全部id所有指向當前project的id,可能由於v7或v4版本差別致使某些id在project中找不到;解決方法generateRJava過程當中根據project的R.java過濾掉不存在的id
  • 發佈project時須要過濾掉embeded依賴的aar,不然外部項目仍是會依賴內部的aar;解決方法在uploadArchives中經過pom.xml去掉embeded依賴的aar
  • 自定義的style找不到對應的id;解決方法generateRJava過程當中將style修改成styleable
  • embeded依賴的aar內部可能還依賴其餘aar,對外發布時須要將內部嵌套的aar所有embeded進來

擴展方向:

  • 學習自定義Task, 瞭解gradle執行流程後,能夠在合適的時機插入自定義Task
  • 學習groovy語言,讀懂fat-aar,基本上也就掌握了groovy語言
相關文章
相關標籤/搜索