4. SplitInstall SDK 集成:依赖、初始化与回调
SplitInstall SDK 是 Google Play Core 库中负责按需加载功能的核心组件。本章完整演示如何从零集成 SDK:添加 Maven 依赖、初始化 SplitInstallManager 单例、以及注册安装状态回调监听器。只有正确完成这三步,后续的模块请求、下载、安装和异常处理才能正常工作。
4.1 原理概述
Google Play Core 库是 Google Play 服务提供的客户端 SDK,负责与 Play 商店的异步交付管道通信。集成分三层:
- Maven 声明层:在 Gradle 构建中引入
play-core依赖,SDK 的 AAR 及 transit 依赖被拉入编译类路径; - 管理器初始化层:通过
SplitInstallManagerFactory.create(Context)获得管理器单例,该对象内持PackageManager、ServiceConnection和文件 I/O 线程池; - 回调注册层:调用
manager.registerListener()注册监听器,当模块的下载、安装、失败等状态变更时,SDK 通过 Binder 回调客户端进程。
关键约束:
SplitInstallManager 必须在 Application.onCreate() 中完成初始化,且回调注册应在 Activity 的 onCreate() 中尽早执行,避免错过初始状态通知。
4.2 添加 Google Play Core Maven 依赖
在 app 层级的 build.gradle 中添加以下依赖。建议锁定明确版本号,避免构建不确定性。
// app/build.gradle
dependencies {
// SplitInstall 按需加载核心库
implementation 'com.google.android.play:core:1.10.3'
// 可选:如果需要在 Android 12+ 上兼容 deferred 卸载回调
implementation 'com.google.android.play:core-ktx:1.8.1'
}
添加后执行 Sync Now。验证依赖是否解析成功:
// 在 Terminal 中运行
./gradlew app:dependencies --configuration releaseRuntimeClasspath | grep play-core
// 应输出类似:com.google.android.play:core:1.10.3 (resolved)
版本冲突风险:如果项目同时使用 Firebase 或其他 Play 服务库,确保 play-core 版本不低于 1.10.0,否则可能因老版本缺少
SplitInstallManagerFactory 类导致 NoClassDefFoundError。
4.3 初始化 SplitInstallManager
在 Application 子类中初始化管理器,保证整个进程内只有一个实例。
// MyApplication.kt
import com.google.android.play.core.splitinstall.SplitInstallManager
import com.google.android.play.core.splitinstall.SplitInstallManagerFactory
class MyApplication : Application() {
lateinit var splitInstallManager: SplitInstallManager
private set
override fun onCreate() {
super.onCreate()
// 初始化管理器(线程安全,应在主线程调用)
splitInstallManager = SplitInstallManagerFactory.create(this)
// 可选:检查当前设备是否支持按需加载
if (!splitInstallManager.installedModules.contains("base")) {
// 设备不受支持或 Play 商店版本过旧
// 降级为全量打包
}
}
}
初始化要点:
SplitInstallManagerFactory.create()是轻量级调用,内部不会立即发起网络请求;真正的服务绑定发生在第一次调用startInstall()或registerListener()时;- 建议将
splitInstallManager通过Dagger/Hilt或ServiceLocator注入到 ViewModel 中,避免 Activity 间传递; - 在
Application中保存引用后,后续可通过(context.applicationContext as MyApplication).splitInstallManager获取。
调试验证:在
onCreate 中打印 splitInstallManager.installedModules,预期输出至少包含 "base"。如果列表为空或抛出异常,说明 Play 商店环境异常。
4.4 处理安装状态回调
实现 SplitInstallStateUpdatedListener 接口,并在需要监听状态的组件(如 Activity / Fragment)中注册与注销。
4.4.1 定义监听器
// SplitInstallListener.kt
import com.google.android.play.core.splitinstall.SplitInstallSessionState
import com.google.android.play.core.splitinstall.SplitInstallStateUpdatedListener
import com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus
class SplitInstallListener : SplitInstallStateUpdatedListener {
override fun onStateUpdate(sessionState: SplitInstallSessionState) {
when (sessionState.status()) {
SplitInstallSessionStatus.PENDING -> {
// 请求已提交,等待开始
Log.d("SplitInstall", "PENDING: 等待队列")
}
SplitInstallSessionStatus.DOWNLOADING -> {
// 正在下载,可通过 sessionState.bytesDownloaded() 获取进度
val progress = sessionState.bytesDownloaded() / sessionState.totalBytesToDownload().toFloat()
Log.d("SplitInstall", "DOWNLOADING: ${(progress * 100).toInt()}%")
// 发布进度到 UI
}
SplitInstallSessionStatus.INSTALLING -> {
Log.d("SplitInstall", "INSTALLING: 安装中")
}
SplitInstallSessionStatus.INSTALLED -> {
Log.d("SplitInstall", "INSTALLED: 模块可用")
// 加载模块中的类或资源
loadSplitModule()
}
SplitInstallSessionStatus.FAILED -> {
val errorCode = sessionState.errorCode()
Log.e("SplitInstall", "FAILED: errorCode=$errorCode")
// 根据 errorCode 提示用户重试或降级
handleInstallError(errorCode)
}
SplitInstallSessionStatus.CANCELED -> {
Log.d("SplitInstall", "CANCELED: 用户取消")
}
else -> {
// REQUIRES_USER_CONFIRMATION 等其他状态
Log.d("SplitInstall", "其他状态: ${sessionState.status()}")
}
}
}
}
4.4.2 注册与注销(生命周期绑定)
// MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var splitInstallManager: SplitInstallManager
private val listener = SplitInstallListener()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
splitInstallManager = (application as MyApplication).splitInstallManager
// 注册监听器(必须在主线程)
splitInstallManager.registerListener(listener)
// 可选:检查是否有未完成的会话
splitInstallManager.sessionStates.addOnSuccessListener { sessions ->
sessions.forEach { session ->
if (session.status() == SplitInstallSessionStatus.DOWNLOADING) {
// 恢复进度 UI
}
}
}
}
override fun onDestroy() {
super.onDestroy()
// 必须注销,否则内存泄漏
splitInstallManager.unregisterListener(listener)
}
}
最佳实践:在
Application 中注册全局监听器用于日志记录,在具体 Activity 中注册业务监听器用于 UI 更新。注销时务必调用 unregisterListener,避免已销毁的 Activity 收到回调导致崩溃。
4.5 调试与验证清单
| 检查项 | 验证方法 | 预期结果 |
|---|---|---|
| 依赖解析 | ./gradlew :app:dependencies | play-core 出现在 releaseRuntimeClasspath |
| 管理器初始化 | Application.onCreate 中断点 | splitInstallManager 非 null,installedModules 包含 "base" |
| 回调注册 | Activity.onCreate 后触发手动状态模拟 | onStateUpdate 收到 PENDING → DOWNLOADING → INSTALLED |
| 注销防泄漏 | Activity.onDestroy 后调用 manager 状态查询 | 不会抛出 DeadObjectException |
| 多进程兼容 | 在 manifest 中标记 android:process=":splitinstall" | 回调仅在主进程触发(非多进程场景可忽略) |
4.6 常见错误与解决方案
| 错误现象 | 根因 | 解决方案 |
|---|---|---|
SplitInstallManagerFactory.create() 抛出 RuntimeException: Failed to bind to service |
设备 Google Play 商店版本过旧,或设备为 AOSP 模拟器未登录 Google 账号 | 确保模拟器已安装 Play 商店并登录;使用 SplitInstallManagerFactory.create(context) 的 try-catch 降级 |
| 回调始终不触发 | 监听器注册在非 UI 线程,或未保持 SplitInstallManager 引用 |
在主线程调用 registerListener();确保 manager 对象非局部变量 |
onStateUpdate 收到 FAILED 且 errorCode= -2 |
网络不可用或 Play 商店服务不可达 | 检查设备网络;确认 Play 商店可以访问 |
Android 12+ 上运行时崩溃:MissingBackupException |
未处理 SplitInstallErrorCode.APP_NOT_OWNED |
在 FAILED 分支中增加对 errorCode == SplitInstallErrorCode.APP_NOT_OWNED 的捕获,提示用户从 Play 商店安装 |
| 多模块同时下载时回调混乱 | 同一个 listener 实例处理多个 session,未区分 sessionState.sessionId() |
在回调中通过 sessionId 分发到不同的协程或状态机 |
4.7 性能与稳定性优化建议
- 回调去重:在
onStateUpdate中引入 sessionId + status 的HashSet过滤器,避免多次调用loadSplitModule()导致类重复加载; - 主线程安全:SDK 的回调默认在主线程触发,不要在回调中执行文件 I/O 或反射加载等耗时操作,应投递到后台协程;
- 降级策略:在
Application.onCreate()中增加try-catch包裹SplitInstallManagerFactory.create(),一旦失败则使用FeatureFlag全局标记为降级模式,后续所有按需加载直接走全量包中的 fallback 资源; - 内存泄漏防护:使用
WeakReference包装 Activity 再传入自定义 listener,或者严格遵循register/unregister配对; - 监控上报:在回调中采集
status、errorCode、bytesDownloaded并上报到Firebase Analytics或自建打点系统,用于监控模块分发成功率。
不要在 Application 中注册长时间存活且持有 Activity 引用的 listener。如果需要在全局监听安装状态,请使用不带 UI 引用的 listener,并通过 EventBus / SharedFlow 向 Activity 转发状态。
4.8 集成验证快速脚本
在 MainActivity 的 onCreate 末尾添加以下临时代码,用于快速验证集成是否完整:
// 快速验证(仅用于开发阶段)
splitInstallManager.installedModules.let { modules ->
Log.d("SplitInstall", "已安装模块: $modules")
}
splitInstallManager.registerListener(object : SplitInstallStateUpdatedListener {
override fun onStateUpdate(state: SplitInstallSessionState) {
Log.d("SplitInstall", "回调收到: status=${state.status()} sessionId=${state.sessionId()}")
}
})
执行后观察 Logcat 的 SplitInstall tag。如果看到 "已安装模块: [base]" 且无异常,则 SDK 集成成功。若没有任何日志输出,优先检查 registerListener 是否在 onCreate 中执行以及 Play 商店服务是否可用。