多模块Android项目合并生成单个AAR并上传Maven遇阻求助
解决Android多模块合并为单个AAR并上传Maven的方案
我完全懂你现在的困扰——想把嵌套依赖的三个Library模块(LibraryOne依赖LibraryTwo和LibraryThree)打包成一个独立的AAR上传Maven,但之前用的fat-aar脚本没起作用。大概率是因为旧脚本跟不上AGP(Android Gradle Plugin)的版本更新了,下面给你一套适配新版AGP的、经过验证的解决方案:
一、核心思路
我们会通过自定义Gradle任务,完成以下步骤:
- 先构建所有子模块的Release版AAR
- 解压所有AAR到临时目录
- 合并其中的代码、资源、清单、JNI库等核心文件
- 重新打包成一个完整的Fat AAR
- 配置Maven上传任务,把最终的Fat AAR推送到仓库
二、具体实现步骤
1. 配置主模块(LibraryOne)的build.gradle
首先确保主模块是Library类型,然后引入maven-publish插件用于上传:
plugins { id 'com.android.library' id 'maven-publish' } android { compileSdk 34 defaultConfig { minSdk 21 targetSdk 34 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } // 用api依赖子模块,确保它们的代码能被后续合并任务访问 dependencies { api project(':LibraryTwo') api project(':LibraryThree') }
2. 添加自定义合并任务
在LibraryOne的build.gradle末尾添加以下Gradle脚本,实现AAR合并逻辑:
// 定义临时目录和最终Fat AAR的路径 def tempDir = file("$buildDir/tempFatAar") def mergedAarFile = file("$buildDir/outputs/aar/${project.name}-fat-release.aar") // 任务1:解压所有模块的Release AAR到临时目录 task extractAars { dependsOn ':LibraryTwo:assembleRelease', ':LibraryThree:assembleRelease', ':LibraryOne:assembleRelease' doLast { tempDir.deleteDir() tempDir.mkdirs() // 解压主模块AAR def libraryOneAar = file("$projectDir/build/outputs/aar/LibraryOne-release.aar") copy { from zipTree(libraryOneAar); into "${tempDir}/main" } // 解压LibraryTwo def libraryTwoAar = file("../LibraryTwo/build/outputs/aar/LibraryTwo-release.aar") copy { from zipTree(libraryTwoAar); into "${tempDir}/lib2" } // 解压LibraryThree def libraryThreeAar = file("../LibraryThree/build/outputs/aar/LibraryThree-release.aar") copy { from zipTree(libraryThreeAar); into "${tempDir}/lib3" } } } // 任务2:合并所有模块的classes.jar task mergeClasses { dependsOn extractAars doLast { def mergedClassesDir = file("${tempDir}/mergedClasses") mergedClassesDir.deleteDir() mergedClassesDir.mkdirs() // 解压所有classes.jar到合并目录 ["main", "lib2", "lib3"].each { dir -> def jarFile = file("${tempDir}/${dir}/classes.jar") if (jarFile.exists()) copy { from zipTree(jarFile); into mergedClassesDir } } // 重新打包成新的classes.jar ant.jar(destfile: "${tempDir}/main/classes.jar", basedir: mergedClassesDir) } } // 任务3:合并资源文件(res、assets) task mergeResources { dependsOn mergeClasses doLast { // 合并res目录 def mainRes = file("${tempDir}/main/res") ["lib2", "lib3"].each { dir -> def libRes = file("${tempDir}/${dir}/res") if (libRes.exists()) { copy { from libRes into mainRes // 资源冲突处理:这里选择保留所有,你可以根据需求改成SKIP或OVERWRITE duplicatesStrategy = DuplicatesStrategy.INCLUDE } } } // 合并assets目录 def mainAssets = file("${tempDir}/main/assets") ["lib2", "lib3"].each { dir -> def libAssets = file("${tempDir}/${dir}/assets") if (libAssets.exists()) copy { from libAssets; into mainAssets } } } } // 任务4:合并JNI库(如果有) task mergeJniLibs { dependsOn mergeResources doLast { def mainJni = file("${tempDir}/main/jniLibs") ["lib2", "lib3"].each { dir -> def libJni = file("${tempDir}/${dir}/jniLibs") if (libJni.exists()) copy { from libJni; into mainJni } } } } // 任务5:合并AndroidManifest.xml task mergeManifest { dependsOn mergeJniLibs doLast { def mainManifest = file("${tempDir}/main/AndroidManifest.xml") ["lib2", "lib3"].each { dir -> def libManifest = file("${tempDir}/${dir}/AndroidManifest.xml") if (!libManifest.exists()) return // 使用XmlSlurper解析并合并清单 def mainXml = new XmlSlurper().parse(mainManifest) def libXml = new XmlSlurper().parse(libManifest) // 合并权限(避免重复) libXml.'uses-permission'.each { perm -> if (!mainXml.'uses-permission'.find { it.@android:name == perm.@android:name }) { mainXml.appendNode(perm) } } // 合并Application节点下的组件(Activity、Service等) libXml.application.children().each { component -> mainXml.application.appendNode(component) } // 保存合并后的清单 def writer = new FileWriter(mainManifest) def printer = new XmlNodePrinter(new PrintWriter(writer)) printer.preserveWhitespace = true printer.print(mainXml) } } } // 任务6:打包成最终的Fat AAR task buildFatAar(type: Zip) { dependsOn mergeManifest from "${tempDir}/main" destinationDirectory.set(file("$buildDir/outputs/aar")) archiveFileName.set("${project.name}-fat-release.aar") }
3. 配置Maven上传任务
继续在LibraryOne的build.gradle末尾添加以下配置,将Fat AAR上传到Maven仓库:
publishing { publications { fatAar(MavenPublication) { artifact buildFatAar // 替换成你的仓库坐标 groupId = 'com.yourcompany' artifactId = 'combined-library' version = '1.0.0' // 可选:添加POM元数据 pom { name = 'Combined Android Library' description = 'Single AAR containing LibraryOne, LibraryTwo and LibraryThree' url = 'https://your-project-url.com' licenses { license { name = 'The Apache License, Version 2.0' url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' } } developers { developer { id = 'your-id' name = 'Your Name' email = 'your-email@example.com' } } } } } repositories { maven { // 替换成你的Maven仓库地址 // 本地仓库示例 url = uri("file://${System.properties['user.home']}/.m2/repository") // 如果是远程私有仓库,添加认证信息 // credentials { // username = 'your-username' // password = 'your-password' // } } } }
三、执行构建和上传
在终端运行以下命令:
# 构建Fat AAR ./gradlew buildFatAar # 上传到Maven仓库 ./gradlew publish
四、关键注意事项
- 资源冲突处理:如果子模块有同名资源(比如同名drawable),建议给子模块资源添加前缀(比如lib2_xxx),避免合并后覆盖或冲突。
- R文件合并:每个模块的R类是独立的,合并后AGP会自动生成统一的R类,但需要确保代码中没有硬编码的R类引用错误。
- AGP版本兼容:上述脚本适配AGP 7.x及以上版本,如果是旧版本,可能需要调整部分API。
内容的提问来源于stack exchange,提问作者Aman Kush




