4. SplitInstall SDK 集成:依赖、初始化与回调

SplitInstall SDK 是 Google Play Core 库中负责按需加载功能的核心组件。本章完整演示如何从零集成 SDK:添加 Maven 依赖、初始化 SplitInstallManager 单例、以及注册安装状态回调监听器。只有正确完成这三步,后续的模块请求、下载、安装和异常处理才能正常工作。

SplitInstall SDK 集成流程:依赖 → 管理器 → 回调 SplitInstall SDK 集成架构:依赖 → 初始化 → 状态回调 1. Maven 依赖声明 build.gradle (app 层) implementation com.google.android.play:core:1.10.3 (或 latest stable) 2. 初始化 SplitInstallManager SplitInstallManagerFactory.create(context) val manager = SplitInstallManagerFactory .create(applicationContext) // 建议在 Application.onCreate 中完成 3. 安装状态回调 SplitInstallStateUpdatedListener onStateUpdate ✅ INSTALLED ⏳ DOWNLOADING ❌ FAILED 业务侧响应动作 INSTALLED → 加载模块类 / DOWNLOADING → 更新进度条 / FAILED → 提示重试

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:dependenciesplay-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 商店服务是否可用。