在 .NET MAUI 中集成原生 Android SDK,如何管理传递依赖?
可以沿用现有的 Kotlin/Java wrapper,但还需要将原生 SDK 的运行时依赖完整交付给 .NET for Android。对 net9.0-android 来说,生成 C# binding 或成功构建 Gradle 项目,都不能证明最终应用已经包含全部依赖。
生产环境中可以保留小范围的 wrapper binding,复用兼容的 NuGet 绑定包,再通过 Gradle 自动导出剩余原生依赖及版本清单,统一纳入内部 NuGet 交付流程。通常不建议一开始就把所有库合并成一个 fat AAR,也不需要人工逐行搬运整个 dependencies 报告。
为什么 wrapper 能调用,应用仍然报错?
你提供的情况说明,wrapper 已经解决了部分 API 绑定问题,但依赖交付仍有缺口。这涉及三项工作:
- 生成 binding:让 C# 能访问 Java/Kotlin 类型。
- 解析依赖:确定 SDK 需要哪些库以及使用哪个版本。
- 打包应用:把所需类、资源、Manifest 内容和原生库纳入最终应用。
.NET 9 的 AndroidGradleProject 可以构建 Gradle 项目,并将输出的 AAR 作为 AndroidLibrary 引入。但 Gradle 解析到的传递依赖不一定会随普通 AAR 一起进入 MAUI 应用。Native library interop 文档
AndroidMavenLibrary 会下载指定 artifact,并获取用于依赖验证的 POM,但不会自动加入该 artifact 依赖的所有二进制库。因此,使用这个标签后仍可能出现缺失依赖。AndroidMavenLibrary 文档
普通 AAR 可以携带自身的类、资源、Manifest 和 .so;通过 Maven 分发时,依赖关系通常由外部发布元数据表达。单独拿到 wrapper AAR,不能假定它已经包含所有 SDK 模块。Android library 文档
先确认真正需要交付的依赖集合
gradlew :dependencies 输出超过 1000 项,不足以证明最终应用需要打包 1000 个独立文件。报告可能覆盖多个 configuration,同一个依赖也可能在多条路径中重复出现。
应针对实际模块和构建变体查看运行时依赖。假设模块名是 wrapper,且存在 releaseRuntimeClasspath:
./gradlew :wrapper:dependencies --configuration releaseRuntimeClasspath
./gradlew :wrapper:dependencyInsight \
--configuration releaseRuntimeClasspath \
--dependency androidx.core:core
模块名和 configuration 需要按项目调整;有 product flavors 时,应使用对应变体。dependencyInsight 可用于查看某个依赖由谁引入、最终选择了哪个版本。Gradle 依赖报告文档
建立交付清单时,记录去重后的 Maven 坐标、最终版本和实际 AAR/JAR,并区分构建插件、测试依赖、注解处理器等构建输入。不要根据报告行数直接批量下载所有条目。
在 MAUI 侧如何组织这些库?
1. 保留窄接口的 wrapper
继续让 wrapper 负责初始化、启动 SDK UI 和处理结果回调。尽量避免在公开方法签名中暴露大量 SDK 内部类型,否则也会增加 C# binding 的工作量。
对于仅在 wrapper 内部使用的依赖,可以将其设为 Bind="false",保留原生库供应用使用,同时减少不必要的 C# 类型生成。若某个类型出现在 wrapper 的公开签名或继承关系中,则需要单独处理,不能一律关闭绑定。依赖处理文档
下面仅演示绑定项目中的组织方式,文件名为示意,不代表完整依赖清单:
<ItemGroup>
<AndroidLibrary Include="Native/wrapper.aar" Bind="true" />
<AndroidLibrary Include="Native/vendor-core.aar" Bind="false" />
<AndroidLibrary Include="Native/vendor-ui.aar" Bind="false" />
</ItemGroup>
如果这些文件已经被项目的默认规则收录,应使用 Update 修改元数据,避免重复 Include。Bind="false" 也不能替代依赖交付:被关闭绑定的库仍须正确进入应用构建。
2. 优先复用兼容的 NuGet 绑定包
对于 AndroidX、Kotlin 等公共依赖,先检查是否存在满足 SDK 要求的 NuGet 绑定包。通过 NuGet 管理已有的依赖关系,可以减少需要自行维护的 AAR/JAR。依赖处理文档
比较时要看 NuGet 包实际携带的 Java artifact 及其版本,不能只比较 NuGet 版本号。还应检查 MAUI 和其他插件是否已经提供同一个库,避免再手工加入另一份。
3. 自动导出剩余依赖,发布内部 NuGet
对于没有合适绑定包的私有 SDK 模块,建议在 CI 中完成以下步骤:
- 通过已配置认证的 Gradle 构建解析指定变体的依赖。
- 导出所需 AAR/JAR,以及坐标、版本和来源清单。
- 与已有 NuGet 提供的原生库比对,处理重复和版本不一致。
- 将剩余库纳入内部绑定项目,生成可重复构建的 NuGet 包。
- 用独立 MAUI 示例应用验证该包,再交付业务应用。
导出和比对流程需要自行实现,AndroidGradleProject 本身不会完成这些步骤。涉及资源的依赖应保留 AAR,不能只提取 classes.jar。
若私有 Maven 仓库要求认证,也应提前确认下载方案。AndroidMavenLibrary 文档说明其支持不需要认证的 Maven 仓库,不能直接假定它能复用 Gradle 的认证配置。AndroidMavenLibrary 文档
三类错误应该分别排查
NoClassDefFoundError
先查看完整异常链,确认具体缺少哪个类。检查该类所属 artifact 是否已被解析、是否纳入 .NET 构建,以及最终 DEX 中是否包含它。
若异常链显示类初始化失败,还要追查更早的初始化异常,不能只看到 NoClassDefFoundError 就认定一定少了 JAR。若问题只在启用 Java 压缩的 Release 构建中出现,再检查反射访问和保留规则。
关闭依赖验证或忽略依赖,不会补齐运行时类。依赖确实必需却未提供时,应用仍可能崩溃。依赖处理文档
AAPT 缺少 themes、attrs 等资源
AAPT 错误发生在资源处理或链接阶段,应从报错中的资源名称查找提供它的 AAR,并检查资源库是否缺失、版本是否匹配。
如果资源链接已经通过,启动 SDK 页面时才出现主题相关异常,再检查宿主 Activity 的主题配置。缺少资源定义和宿主主题配置不满足 SDK 要求,需要分别处理。具体主题名称必须以该 SDK 的接入要求为准。
依赖版本冲突
将 Gradle 最终选择的依赖版本,与 MAUI、NuGet 和手工引入的原生库逐项比较。特别留意同一个 Java 类是否由多个输入提供。
Gradle 的版本选择只作用于它参与解析的依赖图,不能自动协调 .NET 侧额外加入的所有原生库。确定版本组合后,应固定并验证这套组合,避免每次构建重新漂移。
目前没有具体缺失类名、资源名、完整异常链及依赖清单,因此无法直接判断应该补哪个包或调整哪个版本。
是否应该使用 fat AAR?
对于包含 UI、资源和公共依赖的 SDK,不建议把 fat AAR 作为默认方案。它可以减少交付文件数量,但合并流程仍需处理资源、Manifest、原生库、保留规则,以及与宿主已有 AndroidX/Kotlin 依赖的冲突。
普通 Android library 构建不会因为声明了传递依赖,就自动生成包含全部依赖的 fat AAR。选择这种方式前,需要确认具体打包工具能正确处理 SDK 的内容,并符合依赖的再分发条件。
如果供应商已经提供经过验证的独立分发包,可以优先评估;自行合并时,至少需要在真实 MAUI 宿主的 Debug 和 Release 构建中验证初始化、SDK UI 和结果回调。对于当前场景,保留独立依赖、自动导出清单、统一发布内部 NuGet,通常更便于定位问题和升级。
备注:内容仅供参考。
