App Bundle 优势:体积缩减、动态交付、按需模块的底层逻辑

聊 App Bundle 之前,我们先想一个问题:

为什么传统的 APK 分发方式越来越吃力?

你想想看,一个 APK 包里塞了所有屏幕密度、所有 cpu 架构、所有语言资源。用户下载时,60% 的内容其实用不上。设备是 arm64-v8a,包里却还装着 armeabi-v7a 的 so;用户只用中文,包里却有几十种语言的字符串。这不浪费流量和存储吗?

App Bundle 就是来解决这个问题的。它把应用拆成「基础包」和「配置资源包」,Google Play 在安装时只给你需要的部分。嗯,这句话说起来简单,但底层逻辑值得深挖。今天我就把三个核心优势拆开来讲。


1. 体积缩减:不是压缩,是「按需裁剪」

很多人以为 App Bundle 用了什么黑科技压缩算法。其实没有。

它的本质是:你上传一个「素材仓库」(aab),Google Play 对每台设备重新组装一个只包含必要资源的 APK。

关键数据对比:

维度传统 APKApp Bundle 拆分 APK缩减比例
so 库arm64-v8a + armeabi-v7a + x86仅 arm64-v8a约 40%
资源图片xxhdpi + xhdpi + hdpi + mdpi仅 xxhdpi约 30%
语言50+ 种语言仅设备语言视项目而定
总下载量约 50 MB约 28 MB44%

我在项目中遇到过最夸张的情况:一个新闻 App,之前打全量 APK 有 62 MB。切成 App Bundle 后,主流中文用户下载的拆分 APK 只有 29 MB。用户安装转化率直接提升了 8%。

避坑指南: 我曾经因为没配置 android:extractNativeLibs="false",导致 so 库没有被正确拆分。建议你在 build.gradle 里显式设置:

android {
    bundle {
        abi {
            enableSplit = true
        }
        language {
            enableSplit = true
        }
        density {
            enableSplit = true
        }
    }
    packagingOptions {
        jniLibs { useLegacyPackaging = false }
    }
}

说白了,App Bundle 不是压缩,而是「不生成你不需要的东西」。这个思路用在大型 App 上效果尤其明显。


2. 动态交付:安装时拆分 vs 运行时按需加载

体积缩减只是第一步。App Bundle 第二个核心能力是 动态交付(Dynamic Delivery)。这玩意儿分两层:

层级触发时机举例
基础拆分安装时由 Google Play 完成abi、语言、屏幕密度
按需模块运行时由应用触发特色功能模块、付费内容

我的个人习惯是:把不常用的功能(比如 AR 扫描模块、高级滤镜包)做成 on-demand 模块。这样用户第一次安装就只有核心功能,等真正用到时再下载。

实现要点:

  • 在 build.gradle 里用 dist:install-time 表示安装时交付,dist:on-demand 表示按需交付
  • 运行时通过 SplitInstallManager 发起下载和安装
  • 模块下载后会被合并到应用的包名空间,像普通代码一样调用

举个例子,我之前做一个摄影 App。高级滤镜模块有 20 MB,但只有付费用户才用。我把它做成 on-demand 模块,用户购买后实时下载。效果很好——免费用户安装包直接少了 20 MB,付费转化率没受影响。

⚠️ 注意: on-demand 模块下载需要网络。如果用户处于弱网环境,下载可能失败。记得处理回调中的 SESSION_NOT_AVAILABLE 和 INSUFFICIENT_STORAGE 状态。

// 伪代码示意
SplitInstallManager manager = SplitInstallManagerFactory.create(context);
SplitInstallRequest request = SplitInstallRequest
    .newBuilder()
    .addModule("filter_module")
    .build();

manager.startInstall(request)
    .addOnSuccessListener(sessionId -> {
        // 模块下载安装成功
    })
    .addOnFailureListener(e -> {
        // 处理错误:重试或降级
    });

3. 按需模块的底层逻辑:Play 怎么做到的?

你可能会问:运行时下载的代码,怎么动态加到 App 里?

这就要说到 Play Core 库和 Android 的 Split APK 机制了。

3.1 模块其实是一个独立的 APK 壳

每个 on-demand 模块在打包时,实际上是一个不完整的「split APK」。它没有 AndroidManifest,没有 application id,只有自己那部分代码和资源。Google Play 下载后,系统通过 SessionFileProvider 把 split APK 复制到应用的私有目录,然后由 Play Core 库调用 PackageInstaller 进行安装。

3.2 资源合并原理

安装完成后,split APK 中的资源会合并到主 APK 的 Resources 中。通过 AssetManager.addAssetPath() 的方式动态加入。所以你在代码里使用 R.id.something 时,如果资源来自模块,也能正常访问。

一个小细节: 模块里的 R 类会和主模块的 R 合并,不会冲突。但如果你在模块里用了 R.layout,确保主模块没有同名 ID,否则可能引用错误。


4. 知识框架图

下面这张图可以帮你快速回顾 App Bundle 三大优势和它们的底层关系:

App Bundle 三大优势底层逻辑 App Bundle (AAB) ① 体积缩减 拆分 ABI / 密度 / 语言 安装时按需裁剪资源 ② 动态交付 安装时拆分 运行时按需 ③ 按需模块底层 Split APK + Play Core 最终效果:每个设备只得到最精简的运行 APK 减少下载流量 → 提升转化率 → 节省磁盘空间 → 支持模块化开发

5. 实战决策建议

你现在应该对底层逻辑清楚了。但什么时候用哪种拆分?我列个对照表帮你做决策:

场景推荐策略为什么
普通信息流 App只用 ABI / 语言 / 密度拆分功能单一,按需模块收益不大
有付费功能的大型 App主模块 + on-demand 付费功能减少免费用户安装包,转化后按需下载
游戏 App(含资源包)基础 + 纹理/关卡 on-demand首包尽量小,关卡资源边玩边下
企业/内部应用全量 APK(不推荐 Bundle)内部分发渠道不支持 Play 拆分

我的经验: 别为了炫技把所有功能都做成 on-demand。模块间如果有大量共享代码,反而会增加包大小。我一般只看两个指标:

  • 模块体积是否大于 5 MB
  • 该功能是否只有 30% 以下的用户用

同时满足,再考虑做 on-demand。否则就留在主包里,省心。


好了,这一章的核心就这些。记住三个关键词:裁剪、拆分、按需。底层逻辑懂了,后面实践的时候你就知道每一步在干什么了。