You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

多模块Android项目合并生成单个AAR并上传Maven遇阻求助

解决Android多模块合并为单个AAR并上传Maven的方案

我完全懂你现在的困扰——想把嵌套依赖的三个Library模块(LibraryOne依赖LibraryTwo和LibraryThree)打包成一个独立的AAR上传Maven,但之前用的fat-aar脚本没起作用。大概率是因为旧脚本跟不上AGP(Android Gradle Plugin)的版本更新了,下面给你一套适配新版AGP的、经过验证的解决方案:

一、核心思路

我们会通过自定义Gradle任务,完成以下步骤:

  1. 先构建所有子模块的Release版AAR
  2. 解压所有AAR到临时目录
  3. 合并其中的代码、资源、清单、JNI库等核心文件
  4. 重新打包成一个完整的Fat AAR
  5. 配置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

火山引擎 最新活动