Android 多 Module 合并打包 AAR

通常来说,我们在项目中引入第三方 SDK 通常有下面几种方式:

  1. 添加 JAR 包文件到项目依赖(对应 Java Library);
  2. 添加 AAR 包文件到项目依赖(对应 Android Library,包含资源文件、manifest等);
  3. 通过 Maven 仓库形式添加远程依赖 (远程依赖库文件,并且可以传递依赖关系);

Android Studio 创建 module 时可以选择类型,当我们选择 Android Library 的形式构建模块,可以打包得到一个 AAR 包。通常开发一个复杂业务流程的 SDK 项目跟一般应用项目一样,会按照业务拆分成多个 module 进行构建,与开发应用的区别在于,SDK 项目在开发阶段处于可随时编译运行状态,打包时要合并多个 module 产出 AAR 形式的 SDK 包。因为每个 module 都会生成对应的 aar 文件,但是一般来说我们不可能针对每个 module 进行单独打包让开发者接入,那么这时候问题就来了,可不可以把多 Module 合并打包成一个 AAR 文件?

首先明确一点的是 Android 没有帮我们实现这一点,如果你针对单个模块打包 AAR 是不会把它依赖 module 一起打进去的。所以也延伸出下面几种尝试解决的方式:

最简单方案:把所有 module 合并成一个 module,变成单独 module 打包的形式,全部交给 Android Studio 帮我们完成,但这又走回了原先代码未拆分前的老路,最不该优先考虑的方案。

使用 maven 管理 aar 依赖方案:使用 maven 仓库进行远程依赖时,可在其 POM 文件中看到依赖关系,添加依赖时会自动导入其依赖的其他库。对于这种多个 module 的情况,我们可以把每个 module 都上传到 maven 库,因为上传时会 POM 文件中会保留 module 之间的依赖关系,最终用户添加某一库时也能正确引入其他依赖的库。缺点是使用这种方式通常需要 gradle 构建,在兼容一些旧版本的 Unity Editor 或其他场景下最好是能直接提供库文件,可作为保留方案考虑。

使用 fat-aar 方案: 开源项目一开始 android-fat-aar 就是为了解决这个问题而诞生的,可惜后续开发者也没有在维护了,但是里面一些实现思路是值得借鉴的,通过看 fat-aar.gradle 实现脚本你就知道,这是针对特地版本下的构建环境,把 module 构建 aar 时依赖的其他模块进行 copy 以及合并进来打包,这部分对开发者来说是透明的,所以整个流程足够简单又够用。但是因为项目输出结构跟构建环境有关,所以兼容性是个很大的问题,当然如果你也可以自己去兼容新版的 gradle 构建环境。

有兴趣的还可以看看这篇文章,里面提到了一些手动修改文件的失败方案: Android 多module合并打包笔记 ,通常这种非常规的方案在万不得已的情况下最好别考虑,因为就算能用但操作也会相当麻烦而且容易出错,还是那句话能自动完成的工作就不要手动操作。

使用 fat-aar 方案:

第一步:引入 fat-aar.gradle 文件

把 fat-aar.gradle 文件复制到你的打包模块根目录,再修改 module 的 build.gradle 文件:

apply from: 'fat-aar.gradle'

或者直接从 URL 中获取

apply from: 'https://raw.githubusercontent.com/adwiv/android-fat-aar/master/fat-aar.gradle'

第二步:确定模块嵌套的依赖关系

这里可以使用 compile 和 embedded 两个字段来确定嵌套依赖关系,使用 embedded 添加依赖,表示这是要打包嵌套依赖的库。比如你的打包模块 moduleA 需要把其依赖 moduleB 和 moduleC 一起打包进去,而 support 包不需要一起打包进去,那么 moduleA 的 build.gradle 文件就该是 :

dependencies {
  compile fileTree(dir: 'libs', include: ['*.jar'])

  embedded project(':moduleB')
  embedded project(':moduleC')
  
  compile 'com.android.support:appcompat-v7:22.2.0'
}

使用 fat-aar 注意点:

兼容性:目前项目最新支持的 gradle 版本是 2.3.3 版本,新版本的 gradle 编译输出结果目录因为有变动,需要有针对性地重新适配才行。所以一定要注意项目使用的 gradle 版本:

buildscript {
    
    repositories {
        google()
        jcenter()
    }
    dependencies {
        // warning: current fat-aar only support gradle 2.3.3!
        //noinspection GradleDependency
        classpath 'com.android.tools.build:gradle:2.3.3'
    }
}

R 文件合并问题:如果打包模块和依赖的模块中都存在资源文件,也就是存在 R 文件的引用,这是需要注意 R 文件合并的问题。因为一个 ARR 包只会存在一个 R 文件,使用 fat-aar 打包时会把依赖模块中的 R 文件进行合并,但是因为原先模块中导入的 R 文件包名还是之前的模块的,所以会存在找不到 R 文件的错误。这里看到有其他人说通过修改脚本文件替换 R 文件 import 导包或者把全部资源文件放到打包模块,不过我觉得这样做都不太合理,于是我自己尝试了下面这种方案:

我们知道 R 文件是根据 AndroidManifest.xml 文件的 package 字段内容来生成的对应包名的,比如 module 中 AndroidManifest.xml 的 package 指定为 com.xx.xxx 时,那么该 module 内的 R 文件导包就是:import com.xx.xxx.R 了。我们可以利用这一点,把所有需要打包的 module 的 package name 改成统一的名称,这样的话,所有 R 文件导包就变成一致的了,就算合并后的 AAR 中只存在一个 R 文件也不会存在找不到的问题。

为了避免相同模块使用相同包名导致的编译失败的问题,我们可以使用 enforceUniquePackageName = false 配置各个 module 的 build.gradle 文件,这样可以不强制各 module 使用唯一包名。

BuildConfig 合并问题:因为每个模块也会对应生成 BuildConfig.class 文件,为了在开发阶段可以正常运行,我们还需要处理相同包名时各模块的 BuildConfig.class 合并的问题,这里我们可以选择设置 packageBuildConfig(false) 这样可以让依赖的子模块不把 BuildConfig.class 打包进去,而打包模块可以保留,所以 build.gradle 文件配置如下:

android {
    enforceUniquePackageName = false
    packageBuildConfig(false)
    lintOptions {
        abortOnError false
    }
}

参考:
Android多模块构建合并aar解决方案
合并 aar 方案调研
Gradle学习笔记(四)-- fat-aar.gradle解析
解决com.android.dex.DexException: Multiple dex files define L/BuildConfig;
[Android]多module合成单一module技巧

你可能感兴趣的:(Android 多 Module 合并打包 AAR)