You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

进程死亡后,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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 12:22:51