经验是昂贵的老师,但愚人只能从经验学习。——《人月神话》
场景设计:依赖的三方 SDK 需要全局初始化
设想如下场景,在进行 Android 功能开发时,需要引入一个第三方 SDK,暂且将其命名为 AILib,即提供 AI 能力的组件。
在使用 SDK 的具体能力之前,通常要对其进行初始化,且在应用的全部生命进程内,有且仅有一次初始化。
AILib 的接口可以使用伪代码描述如下:
// AILib 接口
class AILib {
fun initialize(context: Context) // ===> 初始化,可能需要传入 ApplicationContext
fun askQuestion() {} // ===> 无返回值的接口
fun askQuestionAndGetAnswer(): Answer {} // ===> 有返回值的接口
}
该 SDK 进行初始化时,可能需要传入 Context
对象,此时必然应当是 ApplicationContext
,因为 SDK 的生命周期是全应用。如果一个 SDK 要求跟随进程初始化,但初始化依赖 ActivityContext
,这种设计是有问题的。
在 Application.onCreate()
中进行全局唯一初始化
对于全局唯一初始化,通常在进程启动时进行,由于 AI 能力并非应用强需求,在启动任务列表里属于 无依赖关系,且可以异步进行 那一类,故采用 IO线程异步初始化 的策略。
对此我们采用 Kotlin 提供的 object
单例 AILibManager
,实现 全局唯一 的特性。
// 全局初始化,IO 异步
// ==================== Application 初始化核心 ====================
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// 在后台线程启动初始化(非阻塞式)
CoroutineScope(Dispatchers.IO).launch {
AILibManager.init(applicationContext)
}
}
}
// ==================== 库管理核心类,封装了原始的 AILib ====================
object AILibManager {
private const val TAG = "AILibManager"
private const val INIT_TIMEOUT = 5000L // 5秒超时
// 使用原子变量保证可见性
private val isInitialized = AtomicBoolean(false)
private val mutex = Mutex() // 加锁防止重复进入初始化流程
private var aiLib : AILib? = null
// 异步初始化入口
suspend fun init(context: Context) {
if (!isInitialized.get()) {
withTimeoutOrNull(INIT_TIMEOUT) {
aiLib = AILib().apply { init(context) } // 实际初始化调用
}?.also { isInitialized.set(true) } // 超时时间内完成,设置状态位
}
// 对外提供检测初始化状态功能
fun checkInitialized() = isInitialized.get()
// 带安全校验的功能调用
fun <T> callWithValidation(block: LibA.() -> T): T {
if (!isInitialized.get()) {
throw IllegalStateException("libA must be initialized first")
}
return libA.block()
}
}
变体1:对外开放监听初始化进度监听
如果在初始化任务尚未完成时,用户就进入到了依赖 AILib
能力的界面,并且尝试调用 AI 相关能力。此时应当在页面展示 Loading
状态,并且监听初始化完成后的广播。
是的,对此的第一反应是初始化完成后发送广播,在新页面先判断 checkInitialized()
如果未完成则显示 Loading
且监听广播。
但 Kotlin 提供了更优雅的实现方法,用来监听状态 —— StateFlow
,它自带广播特性,且能保存最近一次的状态。
在上述代码里补充状态监控功能:
object AILibManager {
...
// 不再需要 isInitialized 标志位
private val _initState = MutableStateFlow<InitState>(InitState.Idle)
val initState: StateFlow<InitState> = _initState // 开放给外部观测
// 异步初始化入口
suspend fun init(context: Context) {
if (_initState.value != InitState.Idle) return
_initState.value = InitState.Processing // 设置为“初始化中”
// 执行初始化,也可增加超时逻辑
try {
aiLib = AILib().apply { init(context) }
_initState.value = InitState.Success
} catch (e: Exception) {
_initState.value = InitState.Failed(e)
}
}
// 状态密封类
sealed class LibState {
object Idle : LibAState()
object Processing : LibAState()
object Success : LibAState()
data class Failed(val exception: Throwable) : LibAState()
}
}
在 BaseActivity
中监测 AILib
初始化进程:
abstract class BaseActivity : AppCompatActivity() {
// 初始化状态观察
private fun setupLibAStateObserver() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
AILibManager.initState.collect { state ->
when (state) {
AILibManager.LibState.Success -> onLibAReady()
is AILibManager.LibState.Failed -> showError(state.exception)
AILibManager.LibState.Processing -> showLoading()
AILibManager.LibState.Idle -> Unit
}
}
}
}
}
override fun onCreate() {
super.onCreate()
setupLibAStateObserver()
}
abstract fun onAILibReady()
}
变体2:将初始化未完成时的接口调用,加入缓存待执行队列
上述代码里,如果 AILib
未初始化完成,是通过抛出 IllegalStateException
来通知调用者的。通常是由调用方自行控制重试。
如果设计 AILib
时,已经明确,初始化就是会耗费很长时间,并且需要将初始化阶段的任务缓存起来,待初始化完成后顺序执行。此时可以设计一个 pendingTasks
队列,用于存放初始化完成之前到达的任务。
个人并不推荐进行此类任务管理,这样强行附加给调用方失败和重试的逻辑,导致代码冗余度上升。SDKManager 理应只负责执行最基础的初始化任务,并将初始化进度和状态暴露给外界即可。
object AISdkManager {
@Volatile
private var isInitialized = false // 用 Volatile 或者是 AtomicBoolean
// 并发队列,对象为高阶函数
private val pendingTasks = CocurrentLinkedQueue<() -> Unit>()
// 初始化入口(支持同步或异步调用)
fun initialize(context: Context) {
synchronized(this) {
if (isInitialized) return
appContext = context.applicationContext
// 模拟耗时初始化操作(例如网络请求)
ThirdPartySDK.init(appContext) // 假设 SDK 初始化是同步的
isInitialized = true
// 执行队列中的任务并清空
drainQueue()
}
}
// 执行并清空队列
private fun drainQueue() {
while (true) {
val task = taskQueue.poll() ?: break
task.invoke()
}
}
// 通用执行方法(线程安全)
fun execute(task: () -> Unit) {
if (isInitialized) {
task()
} else {
synchronized(this) { // 互斥锁,防止在初始化后清空队列时,继续向队列里添加
if (isInitialized) {
task()
} else {
taskQueue.add(task)
}
}
}
}
}