专栏名称: 郭霖
Android技术分享平台,每天都有优质技术文章推送。你还可以向公众号投稿,将自己总结的技术心得分享给大家。
目录
相关文章推荐
鸿洋  ·  ActivityTaskManagerSer ... ·  11 小时前  
鸿洋  ·  关于 2025 副业探索,DeepSeek ... ·  昨天  
51好读  ›  专栏  ›  郭霖

Android - 监听网络状态

郭霖  · 公众号  · android  · 2024-12-06 08:00

正文



/   今日科技快讯   /

近日,韩国研究机构SNE Research发布全球动力电池统计数据,今年1-10月,全球动力电池总装车辆达686.7 GWh,同比增长25%。这一增速较去年同期放缓19个百分点。

共有六家中企进入该榜单。其中,宁德时代前10月实现了252.8 GWh的动力电池装机量,稳居全球榜首,是全球唯一一家装机量突破200 GWh的企业,占据了全球36.8%的市场份额。宁德时代的动力电池客户涵盖极氪、问界、理想在内的中国主要本土汽车主机厂,也包括了特斯拉、宝马、梅赛德斯奔驰和大众等海外汽车品牌。

/   作者简介   /

大家周五好,明天即将迎来短暂的周末休息时光,简单调整下,我们下周见!

本篇文章来自Sunday1990的投稿,文章主要分享了Android 开发中监听网络状态的相关知识,相信会对大家有所帮助!同时也感谢作者贡献的精彩文章。

Sunday1990的博客地址:
https://juejin.cn/user/3843548382274455/posts

/   前言   /

早期监听网络状态用的是广播,后面安卓为我们提供了android.net.ConnectivityManager.NetworkCallback,ConnectivityManager有多个方法可以注册NetworkCallback,通过不同方法注册,在回调时逻辑会有些差异,本文探讨的是以下这个方法:

public void registerNetworkCallback(
    @NonNull NetworkRequest request, 
    @NonNull NetworkCallback networkCallback
)


首先需要创建NetworkRequest:

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

该方法在注册成功以及能力变化时回调,参数是:

  • Network,网络
  • NetworkCapabilities,网络能力

该方法触发的前提是,这个网络要满足addCapability方法传入的条件。具体有哪些网络能力,可以看一下源码,这里就不一一列出来。

public void onLost(@NonNull Network network) {}

onLost比较简单,在网络由满足条件变为不满足条件时回调。

/   正文   /

封装

有了前面的基础,就可以开始封装,基本思路如下:

  • 定义一个网络状态类
  • 维护一个满足条件的网络状态流Flow,并在状态变化时,更新Flow
  • 注册NetworkCallback回调,开始监听

网络状态类

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网络的状态或者能力,更多方法可以查看源码。

网络状态流Flow

接下来在回调中,把网络状态更新到Flow:

// 满足条件的网络
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秒后继续尝试注册,如果注册成功,停止循环,否则一直重复循环。

建议把这个逻辑放在非主线程执行。如果一直注册失败的话,这种降级策略有如下缺点:


  • 每隔1秒获取一次网络状态,所以有一定的延迟,当然你可以把间隔设置的更小,这个取决于你的业务。

  • 最多只能获取到一个满足条件的网络,因为是通过ConnectivityManager.getActiveNetwork()来获取当前网络状态的。


有的读者可能知道有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。

最后我们对外暴露Flow就可以了:

/** 监听所有网络 */
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()。





请到「今天看啥」查看全文