Android Flutter 插件第三方 aar 库依赖方式对比分析
背景说明
在开发 Flutter 插件时,我们经常需要集成第三方 Android 库。然而,将本地 AAR 文件直接添加到 Flutter 插件模块中,是无法自动将 AAR 的依赖关系和内容正确传递给宿主 App 的。
尤其需要注意的是:
- AGP 7.0+ 的明确限制:Android Gradle Plugin (AGP) 从 7.0 版本开始,明确限制库模块(Library Module,即 Flutter 插件的
android
目录)不能直接通过implementation files('xxx.aar')
的方式依赖本地 AAR 文件。 - 错误信息:如果尝试这样做,在尝试构建插件 AAR 时,AGP 会抛出错误:
Direct local .aar file dependencies are not supported when building an AAR.
- 核心技术原因:AGP 在构建插件 AAR 的过程中,无法可靠地将这些本地 AAR 中的资源(
res/
)、assets 和代码(.class
)完整地合并到最终的插件产物中,从而导致插件 AAR 不完整。
因此,开发者需要采用以下两种解决方案来集成第三方库,以确保依赖的正确传递:
- 方式一:多模块项目依赖(传统方案)
- 方式二:直接 AAR 依赖 + FlatDir 传递(简化方案)
本文基于 flutter_library
插件的重构实践,详细对比这两种方式的优劣和适用场景。
方式一:多模块项目依赖(重构前)
这种方式的核心思想是为每一个本地 AAR 创建一个独立的 Gradle 子模块,从而将本地 AAR 依赖转换为 Gradle Project 依赖,绕开 AGP 的限制。
flutter_library 插件实现
插件项目结构
flutter_library/
├── android/
│ ├── src/main/ # 主模块 (flutter_library)
│ │ └── build.gradle
│ ├── third_party_1/ # 子模块1
│ │ ├── build.gradle
│ │ └── third_party_1-1.0.0.aar
// ... (其他子模块)
│ └── settings.gradle # 包含所有子模块
├── lib/
└── pubspec.yaml
插件配置文件
android/settings.gradle:
include ':flutter_library'
include ':third_party_1'
include ':third_party_2'
include ':third_party_3'
android/third_party_1/build.gradle: (以子模块 1 为例,将 AAR 作为工件输出)
configurations.maybeCreate("default")
artifacts.add("default",file("third_party_1-1.0.0.aar"))
configurations.maybeCreate("debug")
artifacts.add("debug",file("third_party_1-1.0.0.aar"))
configurations.maybeCreate("release")
artifacts.add("release",file("third_party_1-1.0.0.aar"))
android/flutter_library/build.gradle: (主模块依赖子模块)
dependencies {
// ...
implementation project(':third_party_2')
implementation project(':third_party_1')
implementation project(':third_party_3')
}
宿主 App 使用方式
宿主 App 必须在其自身的 settings.gradle
中 include
插件的所有子模块。
宿主 App 配置文件
android/settings.gradle:
include ':app'
// ⚠️ 必须手动 include 插件及其所有子依赖模块
include ':flutter_library'
include ':third_party_1'
include ':third_party_2'
include ':third_party_3'
// ... (Flutter 插件加载逻辑)
android/app/build.gradle:
dependencies {
// ...
// 在旧 Flutter 版本中,这里可能还需要手动依赖子模块,
// 但在新的 Flutter app_plugin_loader 机制下,主要依赖通过 settings.gradle 完成。
}
方式二:直接 AAR 依赖 + FlatDir 传递(重构后)
这种方式的核心思想是:插件模块使用 FlatDir 方式依赖本地 AAR,并通过在宿主 App 的 build.gradle
中配置额外的 FlatDir 仓库路径,让宿主 App 也能找到这些 AAR。
flutter_library 插件实现
插件项目结构
flutter_library/
├── android/
│ ├── libs/ # 统一存放 AAR 文件
│ │ ├── third_party_1-1.1.0.aar
// ...
│ ├── build.gradle # 主构建文件
│ └── settings.gradle # 简化配置
├── lib/
└── pubspec.yaml
插件配置文件
android/settings.gradle:
rootProject.name = 'flutter_library'
android/build.gradle: (插件使用 FlatDir 依赖 AAR)
repositories {
flatDir {
dirs 'libs' // 告诉插件模块到本地 libs 目录查找
}
}
dependencies {
implementation(name: 'third_party_2-2.1.0', ext: 'aar')
implementation(name: 'third_party_1-1.1.0', ext: 'aar')
implementation(name: 'third_party_3-3.1.0', ext: 'aar')
}
宿主 App 使用方式
宿主 App 无需在 settings.gradle
中添加额外的 include
,但必须手动配置指向插件内部 libs
目录的仓库路径。
宿主 App 配置文件
android/settings.gradle: (非常简洁,只需 include ':app' 和加载 Flutter 插件)
// ... (标准的 Flutter settings.gradle 模板)
include ':app'
android/app/build.gradle:
// ... (标准配置)
// 添加仓库配置
repositories {
/// 💡 最关键的一步:告诉宿主 App 的 Gradle,要到 :flutter_library 插件的 libs 目录下查找依赖的 AAR 文件。
flatDir {
dirs "${project(':flutter_library').projectDir}/libs"
}
flatDir {
dirs 'libs'
}
}
dependencies {
implementation "androidx.appcompat:appcompat:1.3.0"
// 通过项目依赖方式引入插件
implementation project(':flutter_library')
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
}
flutter {
source '../..'
}
为什么必须手动添加 flatDir
仓库配置?
这是为了解决 Gradle 的依赖查找可见性问题。 尽管插件模块(:flutter_library
)内部声明了 flatDir { dirs 'libs' }
并成功编译,但宿主 App 在依赖插件时,并不会自动继承插件模块的本地仓库配置。
如果宿主 App 不手动添加这个 flatDir
,它在解析插件依赖时,就会因找不到插件内部声明的 AAR 文件而导致编译失败。
可能发生的错误(宿主 App 找不到依赖):
当宿主 App 尝试构建时,如果没有配置正确的 flatDir
仓库路径,Gradle 会报告无法找到 AAR 工件(Artifact),而不是 AGP 的打包错误。
总结:宿主 App 需要手动添加的关键配置
这种方案要求用户手动修改宿主 App 的 android/app/build.gradle
:
- 添加仓库配置 (
repositories
):repositories { flatDir { dirs "${project(':flutter_library').projectDir}/libs" // 指定查找路径 } // ... }
- 确保依赖插件 (
dependencies
):dependencies { implementation project(':flutter_library') // ... }
重构前后对比总结
方面 | 重构前(多模块依赖) | 重构后(直接 AAR + FlatDir) |
---|---|---|
插件项目结构 | 4 个子模块(主模块 + 3个第三方库模块) | 1 个主模块 + libs 目录统一管理 AAR 文件 |
宿主 App settings.gradle | 需要 include 4 个模块 | 只需 include 1 个主模块 (简洁) |
宿主 App 集成 | 需要复制 4 个子模块到宿主 App 项目中 | 无需复制子模块,只需配置 1 个仓库路径 |
依赖配置 | 宿主 App 间接依赖插件的多个子模块 | 宿主 App 只依赖 1 个主模块 |
AAR 文件管理 | 分散在各个子模块中 | 统一放在 libs 目录下 |
构建复杂度 | 需要构建 4 个模块 | 只需构建 1 个模块 |
维护成本 | 高(需要维护多个子模块的 build.gradle) | 低(只需维护主模块) |
两种方式的技术特点与建议
多模块项目依赖(方式一)
- 特点:遵循 Android 官方推荐的模块化架构,每个第三方库都有独立的模块,职责明确。
- 适用场景:
- 需要对不同第三方库进行不同的定制配置。
- 项目规模较大,对模块化架构有严格要求。
- 宿主 App 可以接受复制多个子模块和修改
settings.gradle
的复杂度。
直接 AAR 依赖 + FlatDir 传递(方式二)
- 特点:结构简洁,将第三方库统一管理,降低了 Gradle 配置的复杂度和构建模块数量。
- 适用场景:
- 第三方库相对稳定,不经常更新。
- 项目追求简洁的架构和快速的集成。
- 插件主要用于 Flutter 项目,构建效率要求较高。
- 宿主 App 希望简化集成过程(只需要在
app/build.gradle
中添加一行 FlatDir 配置)。
重构带来的具体改进
本次重构从多模块项目依赖改为直接 AAR 依赖,带来的改进是全方位的:
- 简化项目结构:插件从 $4$ 个模块简化为 $1$ 个主模块 $+$
libs
目录。 - 简化宿主集成:宿主 App 无需在
settings.gradle
中添加多个include
语句。 - 降低耦合性:宿主 App 不再直接依赖插件的多个子模块,只需要依赖
:flutter_library
这一个模块。 - 提升构建效率:减少了需要 Gradle 解析和构建的模块数量,从 $4$ 个减少到 $1$ 个。
- 统一依赖管理:所有第三方库 AAR 文件统一放在
libs
目录下,管理维护更集中。
这种重构方式特别适合 Flutter 插件这种结构相对单一的项目,既保持了功能的完整性,又大大简化了项目结构和维护成本。对于宿主 App 来说,集成过程更加简单,只需要配置仓库路径即可,无需复制多个子模块。
附录:关于 AGP 构建 AAR 失败的错误补充说明
方式一和方式二都是为了绕过 AGP 7.0+ 强制禁止的本地 AAR 依赖限制。如果您在插件模块中错误地使用了传统的 implementation files('libs/xxx.aar')
方式,就会触发 AGP 抛出如下错误:
> Task :ali_auth:bundleReleaseLocalLintAar FAILED
...
Execution failed for task ':ali_auth:bundleReleaseLocalLintAar'.
> Error while evaluating property 'hasLocalAarDeps' of task ':ali_auth:bundleReleaseLocalLintAar'
> Direct local .aar file dependencies are not supported when building an AAR. The resulting AAR would be broken because the classes and Android resources from any local .aar file dependencies would not be packaged in the resulting AAR.
...
错误核心点: 这个错误发生在 插件自身打包 AAR 时(bundleReleaseLocalLintAar
任务),旨在防止生成的插件 AAR 产物残缺。这与方式二中宿主 App 需要手动添加 flatDir
来解决的依赖查找失败问题是两个不同阶段的问题。只有成功绕过 AGP 的打包限制(采用方式一或方式二),才需要关注宿主 App 的依赖查找配置。