2. Split Install 工作机制:APK拆分原理、按需下载与加载流程、与App Bundle的关系

咱们先弄清楚一件事:Split Install 到底在解决什么问题?说白了,就是把一个巨大的 APK 拆成若干小块,用户需要什么就下载什么。你想想看,用户 A 只用到基础功能,用户 B 可能还需要 AI 翻译模块,要是大家都下载同一个几百兆的完整包,对存储和流量都是浪费。我自己的项目里就遇到过这种情况——原本一个 200MB 的安装包,拆完后基础包只有 30MB,剩下的模块按需加载,用户体验提升非常明显。

2.1 APK 拆分原理

在 Android App Bundle 架构下,一个 APK 不再是一个整体文件。它会按照维度拆成多个 Split APK。这些维度包括:

  • base:基础包,包含核心代码和资源,必须安装
  • config splits:按屏幕密度(mdpi、hdpi、xhdpi…)、CPU 架构(armeabi-v7a、arm64-v8a、x86…)、语言拆分的配置包
  • feature splits:按功能模块拆分的动态交付包,可延迟下载

核心概念:每个 Split APK 都有自己的 AndroidManifest.xml,但所有 Split APK 共享同一个 Application 实例、同一个进程空间。这就像一个大楼里每个房间有自己的钥匙,但水电管网是共通的。

我记得最初接触这个机制时,最让我困惑的是:既然拆成这么多文件,安装时会不会出问题?其实 Google 从 Android 5.0(API 21)开始就在 PackageManager 中原生支持了 Split APK。系统会在安装阶段把所有属于同一个 App 的 Split APK 合并成一个逻辑单元,用户看到的还是一个 App 图标。

2.2 按需下载与加载流程

咱们来看一张流程图,把整个过程串起来:

Split Install 按需下载与加载流程 用户点击功能入口 splitInstallManager.startInstall() 已安装? 否 下载 Split APK 安装 Split APK 是 加载模块(ClassLoader) 显示功能界面

流程其实不复杂,但有两个关键点我要强调:

  1. 先检查后下载:SDK 内部会先通过 getSessionState() 检查该模块的安装状态,如果已经下载过,直接进入加载阶段,避免重复下载。
  2. 加载时机:下载并安装完成后,系统会把 Split APK 中的类加载到当前 App 的 ClassLoader 中。这时候你才能安全地调用模块里的代码。我在项目中踩过一个坑——下载成功后就立刻跳转到模块界面,结果因为类还没加载完就崩溃了。后来加了 Installing 状态的轮询才解决。

避坑指南:我曾经遇到一个诡异问题,部分用户下载完模块后点击打开却闪退。排查半天发现是 Split APK 的 versionCode 和 base APK 不一致导致的。记住了,所有 Split APK 的 versionCode 必须与 base 完全一致,否则系统拒绝安装。

2.3 与 App Bundle 的关系

很多人分不清 App Bundle(AAB) 和 Split APK 到底有什么区别。其实很简单:

角色说明
App Bundle是一种上传格式(.aab),包含你所有的代码和资源,但不生成最终 APK。Google Play 用它来为每台设备生成 专属的 Split APK 集合。
Split APK是运行时真正下载安装的文件。它们由 App Bundle 在分发阶段动态组装而成。

换句话说,你上传的是 .aab,用户拿到的是一组 .apks。而我提到的 Split Install API 就是让「按需下载」这件事在运行时发生。举个例子:一个新闻 App,基础包只有 15MB,多语言包 5MB,看视频用的播放器模块 30MB。如果你只浏览新闻,根本不会下载播放器模块。

⚠️ 重要限制:Split Install 只支持 从 Google Play 安装的应用。如果你通过侧载(adb install)或者企业分发,模块将无法按需下载。我遇到过客户非要用内部渠道分发,结果模块永远下载失败,最后还是改回了传统架构。

2.4 代码示例:发起按需下载

看一段实际代码,你就知道怎么调用了:

// 获取 SplitInstallManager 实例
SplitInstallManager manager = SplitInstallManagerFactory.create(this);

// 构造请求,指定要下载的模块名
SplitInstallRequest request = SplitInstallRequest.newBuilder()
        .addModule("video_player")
        .addModule("ar_core")  // 可以同时请求多个模块
        .build();

// 发起异步下载
manager.startInstall(request)
        .addOnSuccessListener(sessionId -> {
            // 下载加安装成功,可以加载模块了
            Log.d("SplitInstall", "模块已安装,sessionId: " + sessionId);
        })
        .addOnFailureListener(exception -> {
            // 处理失败情况,比如用户网络差、存储不足
            handleError(exception);
        });

注意:addOnFailureListener() 里返回的错误码有很多种,比如 SESSION_NOT_FOUND、INVALID_REQUEST、ACCESS_DENIED 等。我建议你在代码里针对 SplitInstallError 的 getErrorCode() 做详细处理,尤其要处理 INSUFFICIENT_STORAGE —— 提示用户清理空间比直接弹框“下载失败”要好得多。

2.5 加载时机与模块注册

下载安装完后,模块的 entry activity 怎么启动?其实很简单——就像启动同一个进程里的其他 Activity 一样:

// 假设 module 里有一个 Activity com.example.video.VideoActivity
Intent intent = new Intent();
intent.setClassName(getPackageName(), "com.example.video.VideoActivity");
startActivity(intent);

这里有一个隐含前提:你需要在 base 模块的 Manifest 中 声明这些动态模块的 Activity,但不需要设置 android:exported 等属性。实际上,你什么都不用写——系统在安装 Split APK 时会自动合并 Manifest。嗯,这也是我之前踩过的坑之一,我以为需要在 base 里写个占位,结果多写了反而冲突了。

2.6 小结一下

Split Install 工作机制的核心就三句话:

  • APK 按 base + config + feature 维度拆分,每个 Split 是独立但共享进程的安装单元。
  • 按需下载通过 SplitInstallManager 发起,系统自动处理状态机:Pending → Downloading → Installing → Installed。
  • App Bundle 是上游的打包格式,Split APK 是下游的分发产物,二者通过 Google Play 的签名和分发服务串联。