使用流观察视图模型中的广播接收器

问题描述 投票:0回答:1

我知道此类问题已经在这里被问过,但没有一个对我有用,所以我会在这里问。 我正在使用 kotlin 和 jetpack compose 开发一个 VPN 应用程序。我们应用程序的核心使用这个自定义 V2ray Java 模块。该功能工作正常,除了我试图观察或收听广播接收器返回的内容(例如连接状态、ping、流量...)。正如您在此文件中看到的,与 VPN 服务相关的所有过程都发生在这里,我已经看到了所有堆栈溢出类似的问题,但没有一个有帮助。 这是我观察 jetpack compose 中不同连接状态的尝试:

这是我的视图模型:

@HiltViewModel
class MainViewModel @Inject constructor(
    private val mainRepo: MainRepository,
) : ViewModel() {

    var showDialog = mutableStateOf(false)
    var connectedStatus = mainRepo.connectedStatus.value
    private val _isConnected = MutableStateFlow(false)
    val isConnected: StateFlow<Boolean> = _isConnected

    private val _isConnecting = MutableStateFlow(false)
    val isConnecting: StateFlow<Boolean> = _isConnecting
    private var v2rayBroadCastReceiver: BroadcastReceiver? = null

    fun init(activity: Activity) {
        // Initialize the broadcast receiver and register it
        v2rayBroadCastReceiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                // Update the states based on the broadcast message
                viewModelScope.launch(Dispatchers.Main) {
                    Log.e("TAG" , "connection mode ${V2rayConfigs.serviceMode}")
                    when (Objects.requireNonNull<Serializable?>(
                        intent.extras!!
                            .getSerializable(V2rayConstants.SERVICE_CONNECTION_STATE_BROADCAST_EXTRA)
                    ) as V2rayConstants.CONNECTION_STATES) {
                        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 -> {}
                    }
                }
            }
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            activity.registerReceiver(
                v2rayBroadCastReceiver,
                IntentFilter(V2rayConstants.V2RAY_SERVICE_STATICS_BROADCAST_INTENT),
                AppCompatActivity.RECEIVER_EXPORTED
            )
        } else {
            activity.registerReceiver(
                v2rayBroadCastReceiver,
                IntentFilter(V2rayConstants.V2RAY_SERVICE_STATICS_BROADCAST_INTENT)
            )
        }
    }

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


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


}

这是我调用

init()
函数的地方:

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    private val viewModel: MainViewModel by viewModels()

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

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

        enableEdgeToEdge()
        setContent {
            SparrowTheme {

                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->

                    val navController = rememberNavController()

                   // other jetpack code such as Navgraph...
                }
            }


        }
    }
}

至于流程,我只是在可组合项中将它们称为简单的

val isConnected by mainViewModel.isConnected.collectAsState()
但我总是弄错。 奇怪的是,我的视图模型中的
Log()
正在工作,它显示了不同的状态,但流量没有被发射。

我尝试的其他解决方案之一是我将 V2ray 模块中与连接有关的所有部分(例如 thisthis )复制到我的主应用程序模块中(并将其转换为 kotlin)尝试将其连接到 viewmodel 的流程(因为我们无法访问其他模块中的应用程序模块类,而且 V2ray 模块是用 Java 编写的),但我收到了这个

Receiver not registered exception
错误。

如果有人能提供任何解决方案,我将不胜感激。谢谢!

android-jetpack-compose broadcastreceiver android-broadcast kotlin-flow v2ray
1个回答
0
投票

我确实完全理解您发布的代码,但是当执行

Log.e("TAG", "connected")
时,
_isConnected.emit(true)
也会执行。据我所知,没有什么可以阻止流程按预期工作。这留下了两个选择:要么消耗流的可组合项出现问题,要么流的值立即改回。

您的代码中存在几个问题,使得理解正在发生的事情变得更加困难,但据我所知,它们不应该导致流程不按预期运行。不过,您可以尝试的一件事是将流程替换为

callbackFlow
。这是将回调(如
onReceive
)转换为流的推荐方法。这是 BroadcastReceiver
示例。您的两个流将减少为一个流,因此您应该使用这样的数据结构作为新流的值,而不是使用两个布尔值:

sealed interface ConnectionState {
    data object Connecting : ConnectionState
    data object Connected : ConnectionState
    data object Disconnected : ConnectionState
}

在视图模型(甚至可能是存储库)中使用返回回调流的私有函数,然后将其传递到 UI,如下所示:

val connectionState = connectionState().stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(5_000),
    initialValue = ConnectionState.Disconnected,
)

private fun connectionState(): Flow<ConnectionState> {
    // return callbackFlow here
}

您需要调用

registerReceiver
的上下文应该由Hilt注入。这是开箱即用的:

@HiltViewModel
class MainViewModel @Inject constructor(
    private val mainRepo: MainRepository,
    @ApplicationContext private val appContext: Context,
) : ViewModel() {
    // you can now call appContext.registerReceiver(...)
}

您的 Activity 将不再需要调用某些

init
函数,您应该在撰写代码中移动视图模型实例化,以便它不再是您的 Activity 的属性:

val viewModel: MainViewModel = hiltViewModel()

很可能与您当前的问题无关,但这些问题也应该得到解决:

  • 使用

    collectAsStateWithLifecycle
    而不是
    collectAsState
    。另请参阅此处:如何安全地(生命周期感知).collectAsState() StateFlow?

  • 如果您的应用程序仅使用 Compose 而没有 Fragments,则让您的 MainActivity 直接扩展

    ComponentActivity
    而不是
    AppCompatActivity
    。另请参阅此处:Android Jetpack Compose 中的 ComponentActivity 与 AppCompatActivity

  • V2rayController.init
    移至您使用
    V2rayController
    的存储库/数据源。您需要的上下文应该由 Hilt 注入。

  • 也将

    AppCompatActivity.RECEIVER_EXPORTED
    替换为
    Context.RECEIVER_EXPORTED

  • viewModelScope.launch(Dispatchers.Main)
    没有意义。您的函数是从 UI 调用的,因此它已经在主线程上运行。相反,您想要的是将其从该线程中“移开”。您应该在这里使用 Dispatchers.IO
    
    

  • _isConnected

    (也适用于

    _isConnecting
    )是一个 MutableStateFlow,因此您应该使用
    _isConnected.emit(true)
    而不是
    _isConnected.value = true
    。另请参阅此处:
    MutableStateFlow value 和emit 之间的区别

  • 不要在视图模型(甚至存储库)中使用 MutableState(如
  • showDialog

    )。请改用带有专用 setter 函数的(私有)MutableStateFlow。请参阅此处:

    https://stackoverflow.com/a/74601657

  • 确保
  • isConnected

    (也适用于

    isConnecting
    )不能被投射回
    MutableStateFlow
    val isConnected: StateFlow<Boolean> = _isConnected.asStateFlow()
    

  • 用 Kotlin 中更简洁的
  • Objects.requireNonNull<Serializable?>

    替换冗长的 Java 结构

    requireNotNull
    
    

© www.soinside.com 2019 - 2024. All rights reserved.