我知道此类问题已经在这里被问过,但没有一个对我有用,所以我会在这里问。 我正在使用 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 模块中与连接有关的所有部分(例如 this 和 this )复制到我的主应用程序模块中(并将其转换为 kotlin)尝试将其连接到 viewmodel 的流程(因为我们无法访问其他模块中的应用程序模块类,而且 V2ray 模块是用 Java 编写的),但我收到了这个
Receiver not registered exception
错误。
如果有人能提供任何解决方案,我将不胜感激。谢谢!
我确实完全理解您发布的代码,但是当执行
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 之间的区别
showDialog
)。请改用带有专用 setter 函数的(私有)MutableStateFlow。请参阅此处:
https://stackoverflow.com/a/74601657
isConnected
(也适用于
isConnecting
)不能被投射回MutableStateFlow
:val isConnected: StateFlow<Boolean> = _isConnected.asStateFlow()
Objects.requireNonNull<Serializable?>
替换冗长的 Java 结构
requireNotNull
。