項(xiàng)目背景
聚合收銀臺(tái)一直在滴滴內(nèi)部使用挺庞,我們在編譯的時(shí)候需要配置滴滴內(nèi)部maven庫,編譯時(shí)必須連接公司內(nèi)網(wǎng)稼病;因雄安項(xiàng)目對(duì)外提供收銀臺(tái)SDK选侨,但是外部編譯時(shí)無法下載內(nèi)部庫,導(dǎo)致編譯失敗然走。于是思考把內(nèi)部依賴的庫全部下載到收銀臺(tái)本地項(xiàng)目中援制,一起打包提供給外部使用,經(jīng)過查找芍瑞,已有fat-aar這樣一個(gè)開源解決方案晨仑,但因長時(shí)間沒有維護(hù),實(shí)踐過程中存在一些問題
fat-aar項(xiàng)目地址:https://github.com/adwiv/android-fat-aar
方案概述
-
aar包結(jié)構(gòu)介紹
aar是Android Library Project的二進(jìn)制文件包拆檬,文件的擴(kuò)展名是aar洪己,其實(shí)文件本身就是一個(gè)簡單的Zip文件,解壓后有以下幾種類型秩仆,相信Android開發(fā)同學(xué)都不會(huì)陌生
- /AndroidManifest.xml(必須)
- /classes.jar(必須)
- /res/(必須)
- /R.txt(必須)
- /assets/(可選)
- /libs/*.jar(可選)
- /jni/<abi>/*.so(可選)
- /proguard.txt(可選)
- /lint.jar(可選)
備注:R.txt文件是aapt --output -text -symbols輸出码泛,aapt相關(guān)細(xì)節(jié)這里不再敘述
方案思路:合并aar
如上圖所示,我們把依賴的外部aar和內(nèi)部module(可以看成aar)輸出的N個(gè)aar文件進(jìn)行合并澄耍,這樣原來A模塊的調(diào)用者接入方式保持不變噪珊,而且在依賴A時(shí)不必再重新下載A內(nèi)部依賴的其他aar,可以提供給外部項(xiàng)目使用而避免訪問滴滴maven庫的場景
參考上面aar包結(jié)構(gòu)形式齐莲,fat-aar合并主要過程為:
- 合并Manifest
- 合并jar
- 合并res資源
- 合并R文件(最關(guān)鍵的一步)
- 合并assets
- 合并libs
- 合并jni
- 合并Proguard
fat-aar接入
fat-aar接入非常簡單痢站,可直接參考 fat-aar
Step 1
下載fat-aar文件到本地項(xiàng)目module下,然后在build.gradle中依賴fat-aar.gradle
apply from: 'fat-aar.gradle'
或者直接遠(yuǎn)程依賴
apply from: 'https://raw.githubusercontent.com/adwiv/android-fat-aar/master/fat-aar.gradle'
Step 2
將需要下載的內(nèi)部aar或本地lib的compile替換成embedded选酗,embedded是fat-arr內(nèi)部定義的一個(gè)屬性
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
embedded project(':librarytwo')
embedded project(':libraryone')
embedded 'com.example.internal:lib-three:1.2.3'
compile 'com.example:some-other-lib:1.0.3'
compile 'com.android.support:appcompat-v7:22.2.0'
}
Step 3
將embedded依賴的project在對(duì)外發(fā)布aar時(shí)從pop.xml文件中去掉阵难,避免外部依賴時(shí)再次下載,參考fat-aar下面的 publish.gradle芒填,當(dāng)然也可以自己實(shí)現(xiàn)
fat-aar工作原理
-
fat-aar工作原理
fat-aar主要思路就是合并aar呜叫,根據(jù)aar的文件結(jié)構(gòu)空繁,可劃分為多個(gè)子任務(wù)
首先,根據(jù)定義的embedded屬性找出需要合并的aar朱庆,并將aar解壓到相應(yīng)目錄下(注意gradle tools版本影響盛泡,建議設(shè)置為2.2.3)
def dependencies = new ArrayList(configurations.embedded.resolvedConfiguration.firstLevelModuleDependencies) dependencies.reverseEach { def aarPath; if (gradleApiVersion >= 2.3f) aarPath = "${root_dir}/${it.moduleName}/build/intermediates/bundles/default" else aarPath = "${exploded_aar_dir}/${it.moduleGroup}/${it.moduleName}/${it.moduleVersion}" it.moduleArtifacts.each { artifact -> println "ARTIFACT 3 : " println artifact if (artifact.type == 'aar') { if (!embeddedAarFiles.contains(artifact)) { embeddedAarFiles.add(artifact) } if (!embeddedAarDirs.contains(aarPath)) { if( artifact.file.isFile() ){ println artifact.file println aarPath copy { from zipTree( artifact.file ) into 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}") } } }
如果存在embedded屬性的依賴,則定義各個(gè)子task執(zhí)行的順序(注意gradle版本影響娱颊,建議gradle tools版本設(shè)置為2.2.3)
if (dependencies.size() > 0) { // 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 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"; } // 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 } }
-
fat-aar中定義的Task
前面介紹了aar的結(jié)構(gòu)以及fat-aar的工作原理傲诵,下面具體介紹幾個(gè)Task
- embedAssets
合并Assets文件,其實(shí)就是簡單的將embedded依賴的assets路徑直接添加到當(dāng)前project的assets目錄下
task embedAssets << { println "Running FAT-AAR Task :embedAssets" embeddedAarDirs.each { aarPath -> // Merge Assets android.sourceSets.main.assets.srcDirs += file("$aarPath/assets") } }
- embedLibraryResources
合并Res文件箱硕,通過getMergedInputResourceSets獲取所有aar的res資源路徑拴竹,然后添加到當(dāng)前project的res資源路徑
task embedLibraryResources << { println "Running FAT-AAR Task :embedLibraryResources" def oldInputResourceSet = packageReleaseResources.inputResourceSets packageReleaseResources.conventionMapping.map("inputResourceSets") { getMergedInputResourceSets(oldInputResourceSet) } }
- embedManifests
合并Manifest,因代碼片段過長剧罩,這里不粘貼代碼了栓拜,主要思路就是通過XmlDocument操作Manifest節(jié)點(diǎn)將所有aar的Manifest文件合并
- embedProguard
合并Proguard,讀取embedded依賴的aar中proguard混淆代碼斑响,直接追加在project的proguard后面
task embedProguard << { println "Running FAT-AAR Task :embedProguard" def proguardRelease = file("$bundle_release_dir/proguard.txt") embeddedAarDirs.each { aarPath -> try { def proguardLibFile = file("$aarPath/proguard.txt") if (proguardLibFile.exists()) proguardRelease.append("\n" + proguardLibFile.text) } catch (Exception e) { e.printStackTrace(); throw e; } } }
- embedJniLibs
合并jni中so文件菱属,將embedded的aar中jni目錄下所有文件拷貝到當(dāng)前project的jni目錄下
task embedJniLibs << { println "Running FAT-AAR Task :embedJniLibs" embeddedAarDirs.each { aarPath -> println "======= Copying JNI from $aarPath/jni" // Copy JNI Folders copy { from fileTree(dir: "$aarPath/jni") into file("$bundle_release_dir/jni") } } }
- generateRJava
根據(jù)aar的R.txt文件生成相對(duì)應(yīng)的R文件,首先通過Manifest文件獲取相應(yīng)的包名舰罚,然后通過遍歷embeddedAarDirs查找每個(gè)aar中是否存在R.txt文件纽门,根據(jù)R.txt生成相應(yīng)的R文件,所有的id指向project的id
task generateRJava << { println "Running FAT-AAR Task :generateRJava" // Now generate the R.java file for each embedded dependency def mainManifestFile = android.sourceSets.main.manifest.srcFile; def libPackageName = ""; if(mainManifestFile.exists()) { libPackageName = new XmlParser().parse(mainManifestFile).@package } embeddedAarDirs.each { aarPath -> 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('.', '/') // Generate the R.java file and map to current project's R.java // This will recreate the class file def rTxt = file("$aarPath/R.txt") def rMap = new ConfigObject() if (rTxt.exists()) { rTxt.eachLine { line -> //noinspection GroovyUnusedAssignment 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 -> sb << " public static final class $subclass {" << '\n' values.each { name, type -> sb << " public static $type $name = ${libPackageName}.R.${subclass}.${name};" << '\n' } sb << " }" << '\n' } sb << '}' << '\n' mkdir("$generated_rsrc_dir/$packagePath") file("$generated_rsrc_dir/$packagePath/R.java").write(sb.toString()) embeddedRClasses += "$packagePath/R.class" embeddedRClasses += "$packagePath/R\$*.class" } } }
- collectRClass
將generateRClass生成的R文件拷貝到'$build_dir/fat-aar/release/'目錄下
task collectRClass << { println "COLLECTRCLASS" delete base_r2x_dir mkdir base_r2x_dir copy { from classs_release_dir include embeddedRClasses into base_r2x_dir } }
- embedJavaJars
將'$build_dir/fat-aar/release/'路徑中R文件打包進(jìn)同一個(gè)jar包营罢,放在'$bundle_release_dir/libs/'目錄下蝙搔,在collecRClass后執(zhí)行
task embedRClass(type: org.gradle.jvm.tasks.Jar, dependsOn: collectRClass) { println "EMBED R CLASS" destinationDir file("$bundle_release_dir/libs/") println destinationDir from base_r2x_dir println base_r2x_dir }
使用fat-aar遇到的一些問題
-
generateRJava生成的R文件中id找不到
修改generateRJava,在project生成R文件之后執(zhí)行考传,可根據(jù)project的R文件來過濾aar中R.txt中的id(aar和project依賴的v7吃型、v4版本不同),如果R.txt中的id在project的R.class文件中找不到僚楞,則過濾掉
def rClassFile = file("$generated_rsrc_dir/com/didi/unified/pay/R.java") def rClassMap = new ConfigObject() def subClassName = null if (rClassFile.exists()) { rClassFile.eachLine { line -> line = line.trim() if(line.contains("public static final class ")) { def subline = line.substring(("public static final class").length()) subClassName = subline.substring(0, subline.indexOf("{")).trim() } else if (line.contains("public static final int[] ")) { def subline = line.substring(("public static final int[]").length()) def name = subline.substring(0, subline.indexOf("=")).trim() rClassMap[subClassName].putAt(name, 1) } else if (line.contains("public static int ")) { def subline = line.substring(("public static int").length()) def name = subline.substring(0, subline.indexOf("=")).trim() rClassMap[subClassName].putAt(name, 1) } } } ... if (rTxt.exists()) { rTxt.eachLine { line -> //noinspection GroovyUnusedAssignment def (type, subclass, name, value) = line.tokenize(' ') if (rClassMap[subclass].containsKey(name)) { rMap[subclass].putAt(name, type) } } }
-
自定義style,找不到相對(duì)應(yīng)的id
修改generateRJava,自定義Style在R.txt中存在形式為style夕膀,但是在class文件引用中為styleable,可以直接將style改為styleable
if (rTxt.exists()) { rTxt.eachLine { line -> //noinspection GroovyUnusedAssignment def (type, subclass, name, value) = line.tokenize(' ') try { if (subclass.equals("style")) { subclass = "styleable" } if (rClassMap[subclass].containsKey(name)) { rMap[subclass].putAt(name, type) } } catch (Exception e) { e.printStackTrace() } } }
發(fā)布aar打包時(shí)需要去掉pop.xml中embedded依賴的aar
fat-aar使用注意事項(xiàng)
project目錄下gradle tools版本配置為3.1.0時(shí)編譯出錯(cuò)攘已,建議使用2.2.3
gradle目錄下gradle-wrapper.properties中建議配置distributionUrl=https://services.gradle.org/distributions/gradle-3.3-all.zip 參考issue
-
fat-aar合并aar時(shí)注意不要把一些公共庫合并進(jìn)去(比如v7、v4)俊庇,如果模塊中有重復(fù)的依賴止后,fat-aar會(huì)報(bào)錯(cuò)提示你某些類或資源文件沖突歉糜,解決方案有:
- 打包aar時(shí)配置相關(guān)依賴transitive false
compile ('com.example.internal:lib: x.x.x') { // Notice the parentheses around project transitive false }
- 外部項(xiàng)目中忽略掉遠(yuǎn)程的依賴
configurations { all*.exclude group: 'com.example.internal', module: 'lib' }
fat-aar最好只用來合并aar使用,embedded屬性不等同于compile,開發(fā)和調(diào)試模塊時(shí)最好使用compile垫言,打包時(shí)使用embedded(建議開發(fā)和發(fā)布兩個(gè)分支笑陈,需要打包時(shí)發(fā)布分支合并開發(fā)分支代碼)