使用Android Archive进行打包

0 阅读9分钟

本文译自「Packaging with Android Archive」,原文链接medium.com/gitconnecte…,由Chirani Rajapaksha发布于2026年6月20日。

captionless image

当 Android 开发者构建库模块时,输出的并非标准的 JAR 文件,而是 AAR 文件。Android 引入 AAR 格式的初衷是,JAR 文件基于 JVM 构建,并不包含 Android 特有的组件。JAR 文件可以包含编译后的字节码,但无法包含清单文件、资源文件、布局文件或原生 .so 库。

AAR 是一种 ZIP 压缩的归档文件,它将 Android 库所需的所有内容打包到一个可分发的文件中。根据Android开发者文档,AAR文件可以包含:

  • AndroidManifest.xml — 在构建时合并到使用该库的应用的清单文件中

  • classes.jar — 已编译的Kotlin/Java字节码

  • res/ — Android资源,例如布局、可绘制对象和字符串

  • assets/ — 原始资源文件

  • jni/ — 已编译的本地.so

  • libs/ — 其他JAR文件

  • Consumer ProGuard规则

  • 资源引用的R.txt符号

这与JAR文件有着本质区别,JAR文件仅包含已编译的类文件和元数据。当你的库需要声明权限、注册组件或发布本地代码时,这种区别就显得尤为重要,而这些都是封装硬件API(例如ARCore)的SDK的常见需求。

AAR 与 JAR:选择合适的格式

Android Studio 项目文档明确区分了这两种格式:

Android 库生成 AAR 文件。Kotlin 或 Java 库生成 JAR 文件。

实际规则很简单:

场景 格式 纯业务逻辑,不包含 Android API JAR 共享工具、数据模型 JAR Android UI 组件 AAR 相机或传感器访问 AAR AR/位置/地图功能 AAR 原生 C/C++ 库 AAR SDK 分发给客户端应用 AAR

ARCore 完全属于 AAR 范畴。它需要在 AndroidManifest.xml 中声明相机权限,需要为 Google Play 服务添加特定的 meta-data 条目,并且内部包含原生 .so 库。这些都不需要 JAR 文件。

ARCore 的使用案例:隔离的重要性

考虑一个使用 Flutter 开发的应用,其中包含一个用 Kotlin 编写的原生 Android 层。该应用程序使用 Google ARCore 进行平面检测和表面识别,例如识别摄像头在现实世界中看到的水平或垂直平面。

在典型的初始版本中,开发者会在整个 Android 代码库中直接导入 ARCore:

flutter_app/
  └── android/
        └── app/
              ├── MainActivity.kt         ← ARCore imports here
              ├── ArSessionManager.kt     ← ARCore imports here
              └── PlaneDetectionHelper.kt ← ARCore imports here

每个涉及 AR 功能的文件都会与 ARCore 的内部类耦合,例如 SessionFramePlaneConfigTrackable 等等。这会带来一些实际问题:

升级 ARCore 存在风险。 ARCore API 的任何重大变更都意味着所有导入它的文件都需要修改,而且没有统一的边界来控制影响范围。

客户端会获取过多信息。 如果你将此 Android 模块交付给客户,他们可以查看和修改所有 ARCore 集成代码。这可能会暴露你希望保护的实现细节。

测试难度加大。 由于调用应用程序模块了解 ARCore 类,因此无法像使用桩代码那样轻松地将 AR 后端替换为单元测试用的桩代码。

解决方案是划定一个明确的边界。ARCore 成为一个实现细节,隐藏在一个接口之后,应用程序的其他部分无需了解底层 SDK 即可使用该接口。

模块结构设计

针对此场景,一个简洁的架构使用了三个模块:

android/
  ├── app/                 ← Flutter host, platform channels, UI
  ├── ar-facade/           ← Public interfaces and data classes only
  └── ar-core-impl/        ← ARCore SDK + implementation → compiled as AAR

模块:ar-facade

此模块仅包含契约。此处未声明任何 ARCore 依赖项。

// ar-facade/src/main/kotlin/com/example/ar/ArSurfaceDetector.kt

interface ArSurfaceDetector {
    fun startSession()
    fun stopSession()
    fun getDetectedPlanes(): List<DetectedPlane>
}

data class DetectedPlane(
    val id: String,
    val type: PlaneType,
    val centerX: Float,
    val centerY: Float
)

enum class PlaneType { HORIZONTAL_UP, HORIZONTAL_DOWN, VERTICAL }

app 模块依赖于 ar-facade。它调用 detector.startSession() 并接收 List<DetectedPlane>。它不会访问 com.google.ar.core.Plane 或任何其他 ARCore 类。

模块:ar-core-impl

这是生成 AAR 文件的库模块。所有 ARCore 导入都仅存在于此模块中。

// ar-core-impl/src/main/kotlin/com/example/ar/impl/ArCoreDetector.kt

import com.google.ar.core.ArCoreApk
import com.google.ar.core.Config
import com.google.ar.core.Frame
import com.google.ar.core.Plane
import com.google.ar.core.Sessionclass 

ArCoreDetector(private val context: Context) : ArSurfaceDetector {
    private var session: Session? = null
    
    override fun startSession() {
        session = Session(context).also { s ->
            s.configure(Config(s))
            s.resume()
        }
    }    
    
    override fun stopSession() {
        session?.pause()
        session?.close()
        session = null
    }    
    
    override fun getDetectedPlanes(): List<DetectedPlane> {
        val frame = session?.update() ?: return emptyList()
        return frame.getUpdatedTrackables(Plane::class.java).map { plane ->
            DetectedPlane(
                id = plane.hashCode().toString(),
                type = plane.type.toDetectedPlaneType(),
                centerX = plane.centerPose.tx(),
                centerY = plane.centerPose.tz()
            )
        }
    }    
    
    private fun Plane.Type.toDetectedPlaneType(): PlaneType = when (this) {
        Plane.Type.HORIZONTAL_UPWARD_FACING  -> PlaneType.HORIZONTAL_UP
        Plane.Type.HORIZONTAL_DOWNWARD_FACING -> PlaneType.HORIZONTAL_DOWN
        Plane.Type.VERTICAL                  -> PlaneType.VERTICAL
    }
}

ar-core-impl 模块之外的任何文件都不会访问这些 ARCore 类型。

构建 AAR:分步指南

步骤 1 — 将模块声明为 Android 库

ar-core-impl/build.gradle.kts 文件中,应用库插件而不是应用程序插件:

plugins {
    id("com.android.library")
    id("org.jetbrains.kotlin.android")
}
android {
    namespace = "com.example.ar.impl"
    compileSdk = 35    defaultConfig {
        minSdk = 24
        consumerProguardFiles("consumer-rules.pro")
    }
}

com.android.library 插件使 Gradle 生成 AAR 文件而不是 APK 文件。正如 Android Studio 文档 所述,应用此插件后,构建过程会创建 AAR 文件,而不是 APK 文件。

步骤 2 — 添加 ARCore 依赖项

dependencies {
    implementation("com.google.ar:core:1.45.0")
    implementation(project(":ar-facade"))
}

步骤 3 — 添加 ARCore 所需的清单条目

AAR 的 AndroidManifest.xml 文件会自动包含其所需的条目。当使用 AAR 的应用包含该 AAR 时,这些清单条目会被合并到最终的应用清单中:

<!-- ar-core-impl/src/main/AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.CAMERA" />    
    <uses-feature
        android:name="android.hardware.camera.ar"
        android:required="true" />    <application>
        <meta-data
            android:name="com.google.ar.core"
            android:value="required" />
    </application></manifest>

根据 ARCore NDK 快速入门文档uses-feature 标签会将应用在 Google Play 商店中的可见性限制在支持 ARCore 的设备上,而 meta-data 标签会将应用标记为 AR 必需,这意味着必须安装 Google Play 服务 AR 版。

由于这些条目存在于 AAR 的清单中,因此使用 AAR 的 app 模块无需声明任何 ARCore 特有的清单配置。它会继承库中的所有配置。

步骤 4 — 添加消费者 ProGuard 规则

# consumer-rules.pro
-keep class com.example.ar.impl.** { *; }

库的 Gradle 配置中的 consumerProguardFiles 设置可确保在使用库的应用运行 R8 或 ProGuard 时自动应用这些规则。Android 文档 指出,消费者 ProGuard 规则会随 AAR 文件一起打包,并应用于消费者的构建;SDK 作者控制哪些规则会被保留,而无需消费者手动管理这些规则。

步骤 5 — 构建 AAR 文件

./gradlew :ar-core-impl:assembleRelease

输出文件位于:

ar-core-impl/build/outputs/aar/ar-core-impl-release.aar

在应用模块中使用 AAR 文件

选项 A:本地文件依赖

将 AAR 文件放在 app/libs/ 目录下,并在 app/build.gradle.kts 文件中声明它:

dependencies {
    implementation(files("libs/ar-core-impl-release.aar"))
    implementation(project(":ar-facade"))
}

选项 B:本地 Maven 仓库(推荐用于客户端交付)

Android 开发者文档 解释说,直接分发原始 AAR 文件会缺少重要的元数据——使用者无法获得版本、标识或传递依赖项等信息。通过 Maven 仓库发布可以解决这个问题:

./gradlew :ar-core-impl:publishToMavenLocal

然后,使用者声明一个标准依赖项:

repositories {
    mavenLocal()
}
dependencies {
    implementation("com.example.ar:ar-core-impl:1.0.0")
}

这实现了语义化版本控制、自动传递依赖项解析以及清晰的升级路径——使用者只需更新版本字符串,而无需替换文件。

隔离后的应用程序模块是什么样子

边界到位后,“app”模块永远不会引用 ARCore。 Flutter 平台通道处理程序可能如下所示:

// app/src/main/kotlin/com/example/app/ArPlatformChannel.kt
import com.example.ar.ArSurfaceDetector   // facade only
import com.example.ar.DetectedPlane       // facade onlyclass

ArPlatformChannel(private val detector: ArSurfaceDetector) :
    MethodChannel.MethodCallHandler {    
    
    override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
        when (call.method) {
            "startSession"       -> { detector.startSession(); result.success(null) }
            "stopSession"        -> { detector.stopSession();  result.success(null) }
            "getDetectedPlanes"  -> result.success(detector.getDetectedPlanes().map { it.toMap() })
            else                 -> result.notImplemented()
        }
    }
}

“app/”中的任何位置都不存在“import com.google.ar.core.*”行。

向客户端演示隔离

当将 AAR 连同“app”模块一起交给客户时,有具体的方法来证明边界成立。

在源代码级别进行验证

在应用程序模块中运行 ARCore 导入搜索:

grep -r "com.google.ar" app/src

空结果证明“app”模块没有直接的 ARCore 依赖项。客户可以在收货前自行审核。

显示公共合约

展示“ar-facade”模块接口。这些是客户代码将与之交互的唯一类型:“ArSurfaceDetector”、“DetectedPlane”和“PlaneType”。 ARCore SDK 不在此列表中。

展示可替换性

解释架构结果:如果 ARCore 的 API 在未来版本中发生变化,或者如果首选替代 AR 引擎,则可以完全替换“ar-core-impl”模块。 app 模块和 Flutter 层需要零更改,因为它们只知道外观。 AAR 被交换;边界以上的一切都未受影响。

运行应用程序

在支持的设备上启动应用程序。相机激活、飞机被检测、Flutter UI 更新——所有这些都不需要应用程序模块了解检测的内部工作原理。

值得了解的局限性

可以进行逆向工程。 AAR 是编译代码,而不是加密代码。 JADX 等工具可以反编译部分实现。为了提供更强大的 IP 保护,R8 混淆与激进的缩小和重命名提供了有意义的抵抗,尽管它并不能阻止坚定的逆向工程师。 C/C++ 中的本机实现更难反编译,并且可能适合特别敏感的逻辑。

原始 AAR 没有依赖项元数据。 正如 Android 文档说明,原始 AAR 文件不会声明其身份、版本或传递依赖项。对于超出简单本地集成的任何内容,建议通过 Maven 发布。

清单合并需要注意。 当 AAR 的清单合并到使用应用程序时,可能会出现冲突 - 例如,如果应用程序已经声明了具有不同设置的“uses-feature”。 Android 的清单合并工具 会自动处理大多数情况,但冲突需要使用合并规则手动解决。

参考文献

  1. 创建Android库——Android Studio | Android 开发者
  2. 上传你的库——Android Studio | Android 开发者
  3. 准备发布库——Android Studio | Android 开发人员
  4. 使用 Fused Library 将多个 Android 库合二为一发布 — Android 开发人员
  5. 在你的 Android NDK 应用程序中启用 ARCore — ARCore | Google 开发人员
  6. ARCore Android SDK 示例 — GitHub
  7. Flutter 平台通道文档
  8. 使用 Gradle 管理清单 - Android 开发人员

欢迎搜索并关注 公众号「稀有猿诉」 获取更多的优质文章!

保护原创,请勿转载!