Android无Context的ViewModel实现网络状态监听困惑
Android MVVM架构下网络状态监听的核心问题解决
一、ViewModel避免持有Context的实现方案
MVVM要求ViewModel不依赖Android框架类(比如Context),目的是降低耦合、避免内存泄漏、方便单元测试。对于网络状态监听这类依赖系统服务的逻辑,正确做法是将系统服务的封装逻辑抽离到独立工具类/Repository中,让这个类持有Application Context(全局上下文,无泄漏风险),再通过构造函数或依赖注入传递给ViewModel。
步骤1:封装网络状态逻辑到独立类
创建NetworkMonitor类,统一处理系统服务获取、网络状态监听与状态转换:
enum class ConnectivityState { WIFI, CELLULAR, DISCONNECTED } class NetworkMonitor(private val context: Context) { private val logger: Logger = LoggerFactory.getLogger(this::class.java) private val wifiManager = context.getSystemService(WIFI_SERVICE) as WifiManager private val connectivityManager = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager fun getCurrentConnectivityState(): ConnectivityState { val currentNetwork = connectivityManager.getActiveNetwork() val capabilities = connectivityManager.getNetworkCapabilities(currentNetwork) if (currentNetwork == null) { return ConnectivityState.DISCONNECTED } if (wifiManager.isWifiEnabled) { val isWifi = capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ?: false if (isWifi) { logger.debug("当前默认网络为WiFi") return ConnectivityState.WIFI } } val isCellular = capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ?: false if (isCellular) { logger.info("当前默认网络为蜂窝数据") return ConnectivityState.CELLULAR } return ConnectivityState.DISCONNECTED } fun registerNetworkCallback(callback: (ConnectivityState) -> Unit) { val networkCallback = object : ConnectivityManager.NetworkCallback() { override fun onAvailable(network: Network) { super.onAvailable(network) callback(getCurrentConnectivityState()) } override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) { super.onCapabilitiesChanged(network, networkCapabilities) callback(getCurrentConnectivityState()) } override fun onLost(network: Network) { super.onLost(network) callback(getCurrentConnectivityState()) } } connectivityManager.registerDefaultNetworkCallback(networkCallback) } }
步骤2:修改ViewModel,依赖封装类而非Context
ViewModel不再直接持有Context,通过构造函数接收NetworkMonitor实例(可通过Hilt等DI框架注入,或在Activity中用Application Context初始化后传递):
class MainActivityViewModel(private val networkMonitor: NetworkMonitor) : ViewModel() { private val _uiState = MutableStateFlow(MainUIState()) val uiState: StateFlow<MainUIState> = _uiState.asStateFlow() init { // 初始化当前网络状态 _uiState.value = _uiState.value.copy(connectivityState = networkMonitor.getCurrentConnectivityState()) // 注册网络状态变化监听 networkMonitor.registerNetworkCallback { newState -> _uiState.value = _uiState.value.copy(connectivityState = newState) } } } data class MainUIState( val connectivityState: ConnectivityState = ConnectivityState.DISCONNECTED )
注意:将
MainUIState的var改为val,用copy方法更新状态,符合不可变数据最佳实践,避免直接修改StateFlow值导致状态不一致。
步骤3:在Activity中初始化依赖
用Application Context创建NetworkMonitor实例,传递给ViewModel:
class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val networkMonitor = NetworkMonitor(applicationContext) val viewModel = ViewModelProvider(this, object : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { return MainActivityViewModel(networkMonitor) as T } })[MainActivityViewModel::class.java] setContent { YourAppTheme { wifiComposable(viewModel) } } } // ... 其他代码 }
二、Compose中StateFlow的正确使用方式
直接调用StateFlow.value会触发警告,原因是:
- Compose无法感知
value的变化,状态更新时不会触发重组,UI无法自动刷新 value线程不安全,在Compose主线程外访问可能引发问题
正确做法是使用collectAsState()扩展函数,将StateFlow转换为Compose可观察的State,自动监听状态变化并触发重组:
修改后的Composable代码
@Composable fun wifiComposable(viewModel: MainActivityViewModel) { // 将StateFlow转换为Compose可观察的State val uiState by viewModel.uiState.collectAsState() Button( onClick = { openWifiSettings(LocalContext.current) } ) { Image( painter = when(uiState.connectivityState) { ConnectivityState.WIFI -> painterResource(id = R.drawable.wifi_indicator) ConnectivityState.CELLULAR -> painterResource(id = R.drawable.cellular_indicator) ConnectivityState.DISCONNECTED -> painterResource(id = R.drawable.down_indicator) }, modifier = Modifier.size(40.dp), contentDescription = "WiFi设置" ) } } // 抽离打开设置的逻辑 fun openWifiSettings(context: Context) { val intent = Intent(Settings.ACTION_WIFI_SETTINGS) context.startActivity(intent) }
额外注意事项
- 内存泄漏预防:在
NetworkMonitor中添加unregisterNetworkCallback方法,在ViewModel的onCleared()中调用,避免未注销回调导致内存泄漏。 - 单元测试友好:可将
NetworkMonitor定义为接口,实现类处理具体逻辑,单元测试时可轻松Mock该接口,无需依赖Android系统服务。 - 权限声明:确保在
AndroidManifest.xml中添加必要权限:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
内容的提问来源于stack exchange,提问作者moonlightcheese
相关产品推荐
相关产品推荐

