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

在ViewModel中使用Flow监听BroadcastReceiver状态异常排查

问题描述

我使用Kotlin和Jetpack Compose开发一款VPN应用,核心基于自定义V2ray Java模块。功能运行正常,但监听BroadcastReceiver返回的连接状态、延迟、流量等数据时遇到以下问题:

  • 在ViewModel中实现BroadcastReceiver并通过StateFlow更新状态,Composable使用collectAsState()获取时始终得到false值
  • ViewModel中的Log能正常打印不同连接状态,但Flow未正确发射状态变更
  • 尝试将V2ray模块中连接相关代码拷贝到主应用模块并转为Kotlin后,出现「Receiver not registered exception」错误
解决方案

1. 修复StateFlow状态发射异常

ViewModel中直接读取mainRepo.connectedStatus.value是一次性取值,无法监听后续状态变化;同时需避免不必要的协程嵌套,确保StateFlow的状态更新逻辑简洁可靠。

2. 正确管理BroadcastReceiver生命周期

  • 禁止在ViewModel中持有Activity引用或注册Receiver:ViewModel生命周期与Activity不一致,易引发内存泄漏或Receiver未注销问题
  • 应在Activity/Fragment层完成Receiver的注册与注销,通过ViewModel暴露的方法传递状态变更

3. 解决Receiver未注册异常

  • 确保注册与注销的是同一个Receiver实例
  • 必须在Activity的onDestroy方法中注销Receiver,避免内存泄漏和异常抛出
修改后的代码

ViewModel代码

@HiltViewModel
class MainViewModel @Inject constructor(
    private val mainRepo: MainRepository,
) : ViewModel() {
    var showDialog = mutableStateOf(false)
    // 改为直接暴露StateFlow,而非一次性读取value
    val connectedStatus = mainRepo.connectedStatus
    private val _isConnected = MutableStateFlow(false)
    val isConnected: StateFlow<Boolean> = _isConnected

    private val _isConnecting = MutableStateFlow(false)
    val isConnecting: StateFlow<Boolean> = _isConnecting

    // 暴露状态更新方法,由外部Receiver调用
    fun updateConnectionState(state: V2rayConstants.CONNECTION_STATES) {
        viewModelScope.launch {
            when (state) {
                V2rayConstants.CONNECTION_STATES.CONNECTED -> {
                    Log.e("TAG", "connected")
                    _isConnected.emit(true)
                    _isConnecting.emit(false)
                }
                V2rayConstants.CONNECTION_STATES.DISCONNECTED -> {
                    Log.e("TAG", "DISCONNECTED")
                    _isConnected.emit(false)
                    _isConnecting.emit(false)
                }
                V2rayConstants.CONNECTION_STATES.CONNECTING -> {
                    Log.e("TAG", "CONNECTING")
                    _isConnected.emit(false)
                    _isConnecting.emit(true)
                }
                else -> {}
            }
        }
    }

    fun startTestV2ray(
        remark: String,
        config: String,
        activity: Activity,
    ) {
        mainRepo.startTestV2ray(activity = activity, remark = remark, config = config)
    }

    fun stopTestV2ray(activity: Activity) {
        V2rayController.stopV2ray(activity)
    }
}

MainActivity代码

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    private val viewModel: MainViewModel by viewModels()
    private var v2rayBroadCastReceiver: BroadcastReceiver? = null

    @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        V2rayController.init(
            this,
            R.drawable.ic_launcher_foreground,
            this.getString(R.string.app_name)
        )

        initBroadcastReceiver()

        enableEdgeToEdge()
        setContent {
            SparrowTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    val navController = rememberNavController()
                    // 正确收集StateFlow状态
                    val isConnected = viewModel.isConnected.collectAsState()
                    val isConnecting = viewModel.isConnecting.collectAsState()

                    // 此处使用isConnected.value和isConnecting.value更新UI
                    // ...其他Jetpack Compose导航及UI代码
                }
            }
        }
    }

    private fun initBroadcastReceiver() {
        v2rayBroadCastReceiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                val state = intent.extras?.getSerializable(V2rayConstants.SERVICE_CONNECTION_STATE_BROADCAST_EXTRA) as? V2rayConstants.CONNECTION_STATES
                state?.let { viewModel.updateConnectionState(it) }
                Log.e("TAG", "connection mode ${V2rayConfigs.serviceMode}")
            }
        }

        val intentFilter = IntentFilter(V2rayConstants.V2RAY_SERVICE_STATICS_BROADCAST_INTENT)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            registerReceiver(v2rayBroadCastReceiver, intentFilter, RECEIVER_EXPORTED)
        } else {
            registerReceiver(v2rayBroadCastReceiver, intentFilter)
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        // 销毁时注销Receiver,避免内存泄漏和异常
        v2rayBroadCastReceiver?.let { unregisterReceiver(it) }
    }
}

内容的提问来源于stack exchange,提问作者farzin

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 05:00:18