进程死亡后,SDK如何实现Activity间不关闭的接口回调通信?
嘿,我之前做SDK开发时刚好碰到过这个问题——进程死亡后回调丢失真的挺闹心的。咱们来拆解几个可行的解决方案:
方案一:用「回调标识+持久化」解决序列化问题
核心思路是不直接存储回调接口实例(因为接口无法序列化,进程重启后会丢失),而是用一个可序列化的唯一标识来关联回调,同时把未处理的回调事件持久化起来,进程重启后再匹配标识执行回调。
SDK端修改代码:
class SDK private constructor() { companion object { val instance by lazy { SDK() } } // 用SharedPreferences持久化未处理的回调事件 private fun getPrefs(context: Context) = context.getSharedPreferences("sdk_prefs", Context.MODE_PRIVATE) // 初始化时,客户端传入「标识-回调」的映射,恢复未处理事件 fun initialize(context: Context, callbackMap: Map<String, ResultsCallback>) { val prefs = getPrefs(context) // 读取进程死亡前保存的未处理回调 val pendingEvents = prefs.getStringSet("pending_callbacks", mutableSetOf()) ?: mutableSetOf() pendingEvents.forEach { eventStr -> val (callbackId, eventType) = eventStr.split(":", limit = 2) // 根据标识找到对应的回调并执行 callbackMap[callbackId]?.let { callback -> when(eventType) { "RESPONSE1" -> callback.response1() "RESPONSE2" -> callback.response2() } } } // 清空已处理的事件 prefs.edit().remove("pending_callbacks").apply() } // SDK内部触发回调的方法,先尝试执行,失败则持久化 fun triggerResponse1(context: Context, callbackId: String) { // 这里可以维护一个当前活跃的回调映射(可选,用于进程未死亡时的实时回调) val activeCallback = ... // 如果SDK暂存了活跃回调,直接调用 if (activeCallback != null) { activeCallback.response1() } else { // 持久化事件,等待进程重启后处理 val prefs = getPrefs(context) val pendingEvents = prefs.getStringSet("pending_callbacks", mutableSetOf()) ?: mutableSetOf() pendingEvents.add("$callbackId:RESPONSE1") prefs.edit().putStringSet("pending_callbacks", pendingEvents).apply() } } // 同理实现triggerResponse2 fun triggerResponse2(context: Context, callbackId: String) { // 逻辑和triggerResponse1一致 } }
客户端侧代码:
class ClientActivity : AppCompatActivity() { // 定义唯一的回调标识(可以用UUID,或者客户端自定义的常量) private val callbackId = "CLIENT_MAIN_ACTIVITY_CALLBACK" // 维护「标识-回调」的映射 private val callbackMap = mutableMapOf<String, ResultsCallback>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_client) // 初始化回调实例并关联标识 callbackMap[callbackId] = object : ResultsCallback { override fun response1() { // 处理SDK返回的response1 Toast.makeText(this@ClientActivity, "收到response1回调", Toast.LENGTH_SHORT).show() } override fun response2() { // 处理SDK返回的response2 Toast.makeText(this@ClientActivity, "收到response2回调", Toast.LENGTH_SHORT).show() } } // 初始化SDK,传入回调映射 SDK.instance.initialize(this, callbackMap) } // 按钮点击事件调用SDK fun onSdkInitClick(view: View) { // 调用SDK方法时传入回调标识 SDK.instance.someSdkAction(this, callbackId) } }
方案二:用Jetpack LiveData/Flow做响应式回调(推荐给用Jetpack的客户端)
如果客户端项目已经在用Jetpack组件,这个方案更简洁——SDK内部暴露可观察的数据流,客户端Activity通过观察数据流来接收回调,进程死亡后LiveData会自动恢复状态(结合SavedStateHandle),Flow则可以用SharedFlow来保证新订阅者能收到最新事件。
SDK端代码:
class SDK private constructor() { companion object { val instance by lazy { SDK() } } // 用MutableSharedFlow发送事件,replay=1确保重启后能收到最新事件 private val response1Flow = MutableSharedFlow<Unit>(replay = 1) private val response2Flow = MutableSharedFlow<Unit>(replay = 1) // 暴露不可变的Flow给客户端 fun observeResponse1(): Flow<Unit> = response1Flow fun observeResponse2(): Flow<Unit> = response2Flow // SDK内部触发回调的方法 fun triggerResponse1() { response1Flow.tryEmit(Unit) } fun triggerResponse2() { response2Flow.tryEmit(Unit) } }
客户端侧代码:
class ClientActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_client) // 用lifecycleScope观察Flow,自动和Activity生命周期绑定 lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { SDK.instance.observeResponse1().collect { // 处理response1回调 Toast.makeText(this@ClientActivity, "收到response1", Toast.LENGTH_SHORT).show() } } } lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { SDK.instance.observeResponse2().collect { // 处理response2回调 Toast.makeText(this@ClientActivity, "收到response2", Toast.LENGTH_SHORT).show() } } } } fun onSdkInitClick(view: View) { SDK.instance.initialize() // 这里不需要传回调了 } }
方案三:本地广播(兼容性方案,不推荐但可用)
如果客户端不用Jetpack,也可以用本地广播(注意:LocalBroadcastManager已被官方废弃,推荐用普通广播+自定义权限来限制范围)。SDK在需要回调时发送广播,客户端Activity注册广播接收器,进程重启后重新注册接收器即可。
可行性说明
这三个方案都是完全可行的:
- 方案一兼容性最好,不管客户端用不用Jetpack都能适配,适合通用SDK;
- 方案二最简洁,代码量少,且能和Jetpack生命周期自动绑定,避免内存泄漏;
- 方案三属于传统实现方式,现在已经不是最优解,但如果有特殊兼容性需求也可以用。
原来的方案失效的核心原因是匿名回调接口持有Activity引用,无法被序列化,进程重启后内存中的实例完全丢失,所以我们的核心改进就是避免直接存储回调实例,转而用可持久化的标识或者响应式数据流来传递回调事件。
内容的提问来源于stack exchange,提问作者Viktor Vostrikov
相关产品推荐
相关产品推荐

