Android随笔3:如何判断网络断开

判断手机没网了,手机又有网了,看起来应该是个很基础的功能,但是我总感觉实现起来有点别扭。

以前判断网络是否连接的时候是ping一下服务器,这个方法目前看还是万无一失,ping不通App就没有数据,除非服务器挂了。

try {
      //-c 3  ping3次,-w 60  超时时间为60秒
      Process p = Runtime.getRuntime().exec("ping -c 3 -w 60 " + "服务器ip地址");
      return p.waitFor() == 0;
  } catch (Exception e) {
      e(e.toString(), TAG);
      return false;
  }

同时还使用了ConnectivityManager#getActiveNetworkInfo方法获取联网状态,比如用NetworkInfo#isConnected()判断网络是否连接,用NetworkInfo#getType()判断网络连接的类型。但是版本29之后NetworkInfo这个类被标记过期了。

NetworkInfo deprecated

推荐使用ConnectivityManager中的NetworkCallback获取网络连接的相关信息。或者通过getNetworkCapabilityes或者getNetworkLinkProperties同步获取。

之前获取网络变化状态的方法是通过注册广播CONNECTIVITY_ACTION,同样被标记过期


广播 deprecated

同时推荐了另外三个方法,这个三个方法就不是通过广播获取网络状态了,而是通过接口回调的方式异步返回信息。

研究一下新方法应该怎么用。还是在ConnectivityManager 这个类中。


ConnectivityManager

ConnectivityManager就是用来告诉我们网络的连接状态,以及通知网络连接发生了哪些变化。那么现在应该如何判断当前的网络状态?ConnectivityManager#getType的注释中给了一些提示。

/**
     * Reports the type of network to which the
     * info in this {@code NetworkInfo} pertains.
     * @return one of {@link ConnectivityManager#TYPE_MOBILE}, {@link
     * ConnectivityManager#TYPE_WIFI}, {@link ConnectivityManager#TYPE_WIMAX}, {@link
     * ConnectivityManager#TYPE_ETHERNET},  {@link ConnectivityManager#TYPE_BLUETOOTH}, or other
     * types defined by {@link ConnectivityManager}.
     * @deprecated Callers should switch to checking {@link NetworkCapabilities#hasTransport}
     *             instead with one of the NetworkCapabilities#TRANSPORT_* constants :
     *             {@link #getType} and {@link #getTypeName} cannot account for networks using
     *             multiple transports. Note that generally apps should not care about transport;
     *             {@link NetworkCapabilities#NET_CAPABILITY_NOT_METERED} and
     *             {@link NetworkCapabilities#getLinkDownstreamBandwidthKbps} are calls that
     *             apps concerned with meteredness or bandwidth should be looking at, as they
     *             offer this information with much better accuracy.
     */
    @Deprecated
    public int getType() {
        synchronized (this) {
            return mNetworkType;
        }
    }

这个提到了如果想判断当前网络连接的类型,可以用NetworkCapabilities#hasTransport以及NetworkCapabilities中的常量进行判断,随后又说app中可以不关心传输方式(transport)是怎样的,通过NetworkCapabilities#NET_CAPABILITY_NOT_METERED和NetworkCapabilities#getLinkDownstreamBandwidthKbps可以更准确的获取到我们想要的信息。

其中getLinkDownstreamBandwidthKbps就是字面意思,下载的带宽是多少,个人认为这也是判断网络是否连接的标准,有数据回来了网络就连接好了。有时连接到了路由器,但是网络欠费了,状态是连接成功,实际上确没网。

另外提到的NET_CAPABILITY_NOT_METERED,这个常量代表了是否是付费网络,比如手机连接的是wifi,那么就是not metered,未计量、不收费的,如果连接的是运营商的移动网络,那么就是metered,计量的、收费的。

最终判断网络是否连接的方式是,是否有数据传输回来:

NetworkCapabilities networkCapabilities = connectivityManager.getNetworkCapabilities(connectivityManager.getActiveNetwork());
int downstreamBandwidthKbps = networkCapabilities.getLinkDownstreamBandwidthKbps();
return downstreamBandwidthKbps > 0;

如果此时网络不是连通状态,那么获取到的networkCapabilities直接就是null。

接下来看看如何监听网络状态变化,文档里推荐了三个方法,其中requestNetwork方法需要Manifest.permission.CHANGE_NETWORK_STATE权限,或者是修改系统设置(android.provider.Settings.System)。修改系统设置很麻烦,debug模式无法设置,需要打包并给apk系统签名,然后跳转到一个修改系统页面手动允许。

我用手里的华为手机测试时,设置了CHANGE_NETWORK_STATE权限,就不需要修改系统设置了,官方文档对于这两种操作的说明也是用“or”进行连接的,所以暂时就认为两种操作取其一就可以了,如果其他手机既需要权限又需要修改系统设置,可能也可能。

registerNetworkCallback(NetworkRequest request, NetworkCallback networkCallback)方法就稍微省事一些,不涉及修改系统设置,只需要考虑如何设置NetworkRequest就行了,比如要监听移动网络和wifi。

NetworkRequest.Builder builder = new NetworkRequest.Builder();
builder.addTransportType(TRANSPORT_WIFI);
builder.addTransportType(TRANSPORT_CELLULAR);
NetworkRequest request = builder.build();

如果还是觉得麻烦,request也不想设置,因为还得弄明白request的参数都代表什么,可以直接用registerDefaultNetworkCallback(NetworkCallback networkCallback),监听系统默认的network,参数只有callback回调,不需要对请求进行设置,也可以监听移动网络和wifi的变化。

网络连接有变化时会在NetworkCallback回调中告知我们,NetworkCallback中的方法有:

mConnectivityManager.registerDefaultNetworkCallback(new ConnectivityManager.NetworkCallback(){
                                    
        @Override
        public void onAvailable(Network network) {
              super.onAvailable(network);
              //连接准备就绪时调用
        }

        @Override
        public void onLosing(Network network, int maxMsToLive) {
                super.onLosing(network, maxMsToLive);
                //断开连接时调用。随后会出现新的连接,紧接着触发onAvailable
        }

        @Override
        public void onLost(Network network) {
                super.onLost(network);
                //断开连接时调用,彻底断开,比如手动关闭手机网络。
        }

        @Override
        public void onUnavailable() {
              super.onUnavailable();
              //未发现适用的网络
        }

        @Override
        public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
              super.onCapabilitiesChanged(network, networkCapabilities);
              //Capabilities属性改变时调用
        }

        @Override
        public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {
              super.onLinkPropertiesChanged(network, linkProperties);
              //LinkProperties属性改变时调用
        }

});

当连接到新的网络时,onAvailable,onCapabilitiesChanged,onLinkPropertiesChanged,三个方法会依次出现。

在测试时发现,如果手动断开网络,仅仅会调用onLoss方法,再此手动打开手机网络,才会触发onAvailable。同时,在源码中发现了如下用法。


callback

在onAvailable中记录下当前连接的NetWork,然后在onLost中判断断开的是否为当前的连接的NetWork,如果是,则认为网络断开。

这里还有个细节需要弄清楚,手机是否一次只能连接一个Network,如果是,那么onAvailable和onLost组合才能正确判断网络是否连接。我在测试时,如果手机先连接了移动网络,在切换到Wi-Fi网络,会调用onAvailable方法,此时如果关闭Wi-Fi,会调用onLost方法,但此时手机为无网络状态(ping不通),并不会马上切换回移动网络,需要一小段时间才能切换回移动网络时,然后重新触发onAvailable方法。

所以判断网络断开的方式就可以是在onAvailable中确认网络连接,在onLost中确认网络断开:

public void onAvailable(Network network) {
        super.onAvailable(network);

        mCurrentNetwork = network;
        //网络连接 do sth
    }

    @Override
    public void onLost(Network network) {
        super.onLost(network);

        ConnectivityManager conn = (ConnectivityManager) mContext.getSystemService(Activity.CONNECTIVITY_SERVICE);
        if (conn == null) {
            return;
        }

        if (network.equals(mCurrentNetwork)) {
            mCurrentNetwork = null;

            //延迟3s执行,如果mCurrentNetwork仍为null,那么可能没有新的Network连接,则认为网络断开
            Handler handler = new Handler();
            handler.postDelayed(() -> {
                if (mCurrentNetwork == null ) {
                    //网络断开 do sth
                }
            }, 3000);
        }

    }

Google之所依废弃以前的方法,就是为了提供给我们更多,更准确网络信息。比如在onCapabilitiesChanged和onLinkPropertiesChanged两个回调方法中,把参数打印一下。
networkCapabilities.toString():

[ Transports: CELLULAR Capabilities: SUPL&INTERNET&NOT_RESTRICTED&TRUSTED&NOT_VPN&VALIDATED&FOREGROUND LinkUpBandwidth>=51200Kbps LinkDnBandwidth>=102400Kbps Specifier: <2>]

linkProperties.toString():


linkProperties.toString()

NetworkCapabilities,描述了网络的种类和带宽,原来描述手机网络只需要一个常量TYPE_MOBILE,但是随着手机网络的速度越来快,4G、5G的发展,简单的分类不足以描述越来越多的手机网络,于是Google采用了直接用带宽描述网络连接的方式,所以不直接给一个返回布尔值的方法判断网络连接状态也没啥好别扭的了。

LinkProperties 则包含地址、网关、dns、代理等信息。

如果想知道具体的网络种类,可以通过NetworkCapabilities#hasTransport方法,通过不同的参数,可以判断当前的连接的网络是什么,参数有7种。

TRANSPORT_CELLULAR:蜂窝网络
TRANSPORT_WIFI:wifi
TRANSPORT_BLUETOOTH:蓝牙拨号,手机通过蓝牙连接pc上的网络,网上有教程
TRANSPORT_ETHERNET:因特网,手机直接连接网线,网上有这样的转接头
TRANSPORT_VPN:vpn
TRANSPORT_WIFI_AWARE:这个没见过,据说场景大概是,比如你和一家商店通过这种协议进行了网络连接,下次你路过这家商店的时候,手机会直接收到这家店的商品信息
TRANSPORT_LOWPAN:ipv6网络

Google也提示我们不需要关心这些,关注是否计费和网络带宽就行。

你可能感兴趣的:(Android随笔3:如何判断网络断开)