近日,韩国研究机构SNE Research发布全球动力电池统计数据,今年1-10月,全球动力电池总装车辆达686.7 GWh,同比增长25%。这一增速较去年同期放缓19个百分点。共有六家中企进入该榜单。其中,宁德时代前10月实现了252.8 GWh的动力电池装机量,稳居全球榜首,是全球唯一一家装机量突破200 GWh的企业,占据了全球36.8%的市场份额。宁德时代的动力电池客户涵盖极氪、问界、理想在内的中国主要本土汽车主机厂,也包括了特斯拉、宝马、梅赛德斯奔驰和大众等海外汽车品牌。大家周五好,明天即将迎来短暂的周末休息时光,简单调整下,我们下周见!本篇文章来自Sunday1990的投稿,文章主要分享了Android 开发中监听网络状态的相关知识,相信会对大家有所帮助!同时也感谢作者贡献的精彩文章。https://juejin.cn/user/3843548382274455/posts
早期监听网络状态用的是广播,后面安卓为我们提供了android.net.ConnectivityManager.NetworkCallback,ConnectivityManager有多个方法可以注册NetworkCallback,通过不同方法注册,在回调时逻辑会有些差异,本文探讨的是以下这个方法:public void registerNetworkCallback(
@NonNull NetworkRequest request,
@NonNull NetworkCallback networkCallback
)
val request = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build()
addCapability方法的字面意思是添加能力,可以理解为添加条件,表示回调的网络要满足指定的条件。这里添加了NetworkCapabilities.NET_CAPABILITY_INTERNET,表示回调的网络应该要满足已连接互联网的条件,即拥有访问互联网的能力。如果指定多个条件,则回调的网络必须同时满足指定的所有条件。创建NetworkRequest实例之后就可以调用注册方法了:val manager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
manager.registerNetworkCallback(request, _networkCallback)
_networkCallback用来监听网络变化,下文会介绍。
重点来了,每个App只允许最多注册100个回调,如果超过会抛RuntimeException异常,所以在注册时要捕获异常并做降级处理,下文会提到。NetworkCallback有多个回调方法,重点关注下面2个方法:public void onCapabilitiesChanged(
@NonNull Network network,
@NonNull NetworkCapabilities networkCapabilities
) {}
该方法触发的前提是,这个网络要满足addCapability方法传入的条件。具体有哪些网络能力,可以看一下源码,这里就不一一列出来。public void onLost(@NonNull Network network) {}
onLost比较简单,在网络由满足条件变为不满足条件时回调。- 维护一个满足条件的网络状态流Flow,并在状态变化时,更新Flow
interface NetworkState {
/** 网络Id */
val id: String
/** 是否Wifi网络 */
val isWifi: Boolean
/** 是否手机网络 */
val isCellular: Boolean
/** 网络是否已连接,已连接不代表网络一定可用 */
val isConnected: Boolean
/** 网络是否已验证可用 */
val isValidated: Boolean
}
NetworkState是接口,定义了一些常用的属性,就不赘述。internal data class NetworkStateModel(
/** 网络Id */
val netId: String,
/** [NetworkCapabilities.TRANSPORT_WIFI] */
val transportWifi: Boolean,
/** [NetworkCapabilities.TRANSPORT_CELLULAR] */
val transportCellular: Boolean,
/** [NetworkCapabilities.NET_CAPABILITY_INTERNET] */
val netCapabilityInternet: Boolean,
/** [NetworkCapabilities.NET_CAPABILITY_VALIDATED] */
val netCapabilityValidated: Boolean,
) : NetworkState {
override val id: String get() = netId
override val isWifi: Boolean get() = transportWifi
override val isCellular: Boolean get() = transportCellular
override val isConnected: Boolean get() = netCapabilityInternet
override val isValidated: Boolean get() = netCapabilityValidated
}
NetworkStateModel是实现类,具体的实例在onCapabilitiesChanged方法回调时,根据回调参数创建,创建方法如下:private fun newNetworkState(
network: Network,
networkCapabilities: NetworkCapabilities,
): NetworkState {
return NetworkStateModel(
netId = network.netId(),
transportWifi = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI),
transportCellular = networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR),
netCapabilityInternet = networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET),
netCapabilityValidated = networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED),
)
}
private fun Network.netId(): String = this.toString()
通过NetworkCapabilities.hasXXX方法,可以知道Network网络的状态或者能力,更多方法可以查看源码。// 满足条件的网络
private val _networks = mutableMapOf()
// 满足条件的网络Flow
private val _networksFlow = MutableStateFlow?>(null)
private val _networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onLost(network: Network) {
super.onLost(network)
// 移除网络,并更新Flow
_networks.remove(network)
_networksFlow.value = _networks.values.toList()
}
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
super.onCapabilitiesChanged(network, networkCapabilities)
// 修改网络,并更新Flow
_networks[network] = newNetworkState(network, networkCapabilities)
_networksFlow.value = _networks.values.toList()
}
}
在onLost和onCapabilitiesChanged中更新_networks和_networksFlow。_networksFlow的泛型是一个List,因为满足条件的网络可能有多个,例如:运营商网络,WIFI网络。_networks是一个Map,KEY是Network,我们看看Network源码:public class Network implements Parcelable {
@UnsupportedAppUsage
public final int netId;
@Override
public boolean equals(@Nullable Object obj) {
if (!(obj instanceof Network)) return false;
Network other = (Network)obj;
return this.netId == other.netId;
}
@Override
public int hashCode() {
return netId * 11;
}
@Override
public String toString() {
return Integer.toString(netId);
}
}
把其他非关键代码都移除了,可以看到它重写了equals和hashCode方法,所以把它当作HashMap这种算法容器的KEY是安全的。细心的读者可能会有疑问,NetworkCallback的回调方法是在什么线程执行的,回调中直接操作Map是安全的吗?默认情况下,回调方法是在子线程按顺序执行的,这里的重点是按顺序,所以在子线程也是安全的,因为没有并发。可以在注册时,调用另一个重载方法传入Handler来修改回调线程,这里就不继续探讨,有兴趣的读者可以看看源码。接下来可以注册回调,开始监听了。上文提到,每个App最多只能注册100个回调,我们的降级策略是:如果注册失败,直接获取当前网络状态,并更新到Flow,延迟1秒后继续尝试注册,如果注册成功,停止循环,否则一直重复循环。建议把这个逻辑放在非主线程执行。如果一直注册失败的话,这种降级策略有如下缺点:
有的读者可能知道有getAllNetworks()方法获取所有网络,但是该方法已经被废弃了,不建议使用。
private suspend fun registerNetworkCallback() {
// 1.创建请求对象,指定要满足的条件
val request = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build()
while (true) {
// 2.注册监听,要捕获RuntimeException异常
val register = try {
manager.registerNetworkCallback(request, _networkCallback)
true
} catch (e: RuntimeException) {
e.printStackTrace()
false
}
// 3.获取当前网络状态
val currentList = manager.currentNetworkState().let { networkState ->
if (networkState == null) {
emptyList()
} else {
listOf(networkState)
}
}
if (register) {
// A: 注册成功,更新Flow,并停止循环
_networksFlow.compareAndSet(null, currentList)
break
} else {
// B: 注册失败,间隔1秒后重新执行上面的循环
_networksFlow.value = currentList
delay(1_000)
continue
}
}
}
代码看起来比较长,实际逻辑比较简单,我们来分析一下。第1步上文已经解释了,就不赘述了。后面的逻辑是在while循环中执行的,就是上面提到的降级策略逻辑。最后根据注册的结果,会走2个分支,B分支是注册失败的降级策略分支。A分支是注册成功的分支,把当前状态更新到Flow,并停止循环。注意:这里更新Flow用的是compareAndSet,这是因为注册之后有可能onCapabilitiesChanged已经回调了最新的网络状态,此时不能用currentList直接更新覆盖,而要进行比较,如果是null才更新,因为null是默认值,表示onCapabilitiesChanged还未被回调。这也解释了上文中定义Flow时,默认值为什么是一个null,而不是一个空列表,因为默认值设置为空列表有歧义,它到底是默认值,还是当前没有满足条件的网络,注册时就没办法compareAndSet。/** 监听所有网络 */
val allNetworksFlow: Flow> = _networksFlow.filterNotNull()
用filterNotNull()把默认值null过滤掉。实际开发中,大部分时候,仅仅需要知道当前的网络状态,而不是所有的网络状态。有了上面的封装,我们可以很方便的过滤出当前网络状态:/** 监听当前网络 */
val currentNetworkFlow: Flow = allNetworksFlow
.mapLatest(::filterCurrentNetwork)
.distinctUntilChanged()
.flowOn(Dispatchers.IO)
过滤的逻辑在filterCurrentNetwork方法中:private suspend fun filterCurrentNetwork(list: List<NetworkState>): NetworkState {
// 1.列表为空,返回一个代表无网络的状态
if (list.isEmpty()) return NetworkStateNone
while (true) {
// 2.从列表中查找网络Id和当前网络Id一样的状态,即当前网络状态
val target = list.find { it.id == manager.activeNetwork?.netId() }
if (target != null) {
return target
} else {
// 3.如果本次未查询到,延迟后继续查询
delay(1_000)
continue
}
}
}
第2步中有个获取网络Id的扩展函数,上文已经有列出,但未做解释,实际上就是调用Network.toString()。为什么会有第3步呢?因为我们是在回调中直接更新Flow,可能导致filterCurrentNetwork立即触发,相当于在回调里面直接查询manager.activeNetwork。在NetworkCallback的回调中,同步调用ConnectivityManager的所有方法都可能有先后顺序问题,即本次调用查询到的状态,可能并非最新的状态,这个在源码中有解释,有兴趣的读者可以看看源码。上面的currentNetworkFlow,我们用了mapLatest,如果在delay时,列表又发生了变化,则会取消本次过滤,重新执行filterCurrentNetwork。当然了distinctUntilChanged也是必须的,假如当前网络activeNetwork是WIFI,另一个满足条件的运营商网络发生变化时也会执行过滤,过滤的结果还是WIFI,就会导致重复回调。最后建议把这个过滤切换到非主线程执行,可以使用flowOn。实际上,如果你只想监听当前网络,不需要知道所有网络,那么在注册回调的时候可以使用registerDefaultNetworkCallback来监听,此时回调的逻辑和本文介绍的稍有差异,这个方法要求API 24,具体可以看一下源码注释,这里就不展开。有了上面的封装,在协程中,我们可以轻松实现:在某个操作之前,判断网络已连接才执行,如果未连接则挂起等待。suspend fun fAwaitNetwork(
condition: (NetworkState) -> Boolean = { it.isConnected },
): Boolean {
if (condition(FNetwork.currentNetwork)) return true
FNetwork.currentNetworkFlow.first { condition(it) }
return false
}
FNetwork.currentNetwork是一个获取当前网络状态的属性,最终获取的方法如下:private fun ConnectivityManager.currentNetworkState(): NetworkState? {
val network = this.activeNetwork ?: return null
val capabilities = this.getNetworkCapabilities(network) ?: return null
return newNetworkState(network, capabilities)
}
fAwaitNetwork调用时,先直接获取一次当前网络状态,如果满足条件,则立即返回,如果不满足条件则开始监听currentNetworkFlow,遇到第一个满足条件的网络时,恢复执行。上层可以通过返回值true或者false知道本次调用是立即满足的,还是挂起等待之后满足的。lifecycleScope.launch {
// 判断网络
fAwaitNetwork()
// 发起请求
requestData()
}
库已经封装好了,在这里:network。地址如下:https://github.com/zj565061763/network
该库会在主进程自动初始化,开箱即用,如果你的App需要在其他进程使用,则需要在其他进程手动调用初始化。感谢你的阅读,如果有问题欢迎一起交流学习,