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

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)
}

额外注意事项

  1. 内存泄漏预防:在NetworkMonitor中添加unregisterNetworkCallback方法,在ViewModel的onCleared()中调用,避免未注销回调导致内存泄漏。
  2. 单元测试友好:可将NetworkMonitor定义为接口,实现类处理具体逻辑,单元测试时可轻松Mock该接口,无需依赖Android系统服务。
  3. 权限声明:确保在AndroidManifest.xml中添加必要权限:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 04:37:03