监听 Android 设备网络状态的理想实践

版权声明:本文为博主原创文章,转载请注明出处,如有问题,欢迎指正,谢谢。 https://blog.csdn.net/qq_33404903/article/details/87864922

目录

1.Android 5.0 以下版本

2. Android 5.0 及以上版本

3.Android 7.0 及以上版本

4.代码实现


App都需要监听 Android 设备的网络连接状态,典型的例子就是在下载大文件前,需要判断当前是否正在使用 WIFI 网络,若是则下载;如果使用移动网络,则需要弹框提醒用户可能会消耗流量并扣费。

要监听网络状态,也需要考虑 Android 版本差异。

1.Android 5.0 以下版本

对于5.0以下版本,可以使用基类 activity 中注册网络状态接受者的方式来监听网络状态变化,这样所有继承基类的 activity 都可以监听网络状态变化。

2. Android 5.0 及以上版本

让我们看下Android 5.0行为变更—多个网络连接中的描述:

多个网络连接

Android 5.0 提供了新的多网络 API,允许您的应用动态扫描具有特定能力的可用网络,并与它们建立连接。当您的应用需要 SUPL、彩信或运营商计费网络等专业化网络时,或者您想使用特定类型的传输协议发送数据时,就可以使用此功能。

要从您的应用以动态方式选择并连接网络,请执行以下步骤:

  1. 创建一个 ConnectivityManager
  2. 使用 NetworkRequest.Builder 类创建一个 NetworkRequest 对象,并指定您的应用感兴趣的网络功能和传输类型。
  3. 要扫描合适的网络,请调用 requestNetwork() 或 registerNetworkCallback(),并传入 NetworkRequest 对象和 ConnectivityManager.NetworkCallback 的实现。如果您想在检测到合适的网络时主动切换到该网络,请使用 requestNetwork() 方法;如果只是接收已扫描网络的通知而不需要主动切换,请改用 registerNetworkCallback() 方法。

当系统检测到合适的网络时,它会连接到该网络并调用 onAvailable() 回调。您可以使用回调中的 Network 对象来获取有关网络的更多信息,或者引导通信使用所选网络。

针对5.0及以上版本,google推荐我们使用上述方法来监听网络状态,至于使用 requestNetwork() 还是 registerNetworkCallback(),就看自己的需求了。同时在5.0以下版本中使用的方法仍然可用。

3.Android 7.0 及以上版本

通过注册网络状态接收器,5.0 以上或以下的Android版本的网络状态我们都可以监听,但是面向 Android 7.0 开发的应用不会收到在清单文件中注册的 CONNECTIVITY_ACTION 广播,以下是 Android 7.0 行为变更—后台优化中的描述:

Project Svelte:后台优化

Android 7.0 移除了三项隐式广播,以帮助优化内存使用和电量消耗。此项变更很有必要,因为隐式广播会在后台频繁启动已注册侦听这些广播的应用。删除这些广播可以显著提升设备性能和用户体验。

移动设备会经历频繁的连接变更,例如在 WLAN 和移动数据之间切换时。目前,可以通过在应用清单中注册一个接收器来侦听隐式 CONNECTIVITY_ACTION 广播,让应用能够监控这些变更。由于很多应用会注册接收此广播,因此单次网络切换即会导致所有应用被唤醒并同时处理此广播。

同理,在之前版本的 Android 中,应用可以注册接收来自其他应用(例如相机)的隐式 ACTION_NEW_PICTURE 和 ACTION_NEW_VIDEO 广播。当用户使用相机应用拍摄照片时,这些应用即会被唤醒以处理广播。

为缓解这些问题,Android 7.0 应用了以下优化措施:

  • 面向 Android 7.0 开发的应用不会收到 CONNECTIVITY_ACTION 广播,即使它们已有清单条目来请求接受这些事件的通知。在前台运行的应用如果使用 BroadcastReceiver 请求接收通知,则仍可以在主线程中侦听 CONNECTIVITY_CHANGE
  • 应用无法发送或接收 ACTION_NEW_PICTURE 或 ACTION_NEW_VIDEO 广播。此项优化会影响所有应用,而不仅仅是面向 Android 7.0 的应用。

如果您的应用使用任何 intent,您仍需要尽快移除它们的依赖关系,以正确适配 Android 7.0 设备。Android 框架提供多个解决方案来缓解对这些隐式广播的需求。例如,JobScheduler API 提供了一个稳健可靠的机制来安排满足指定条件(例如连入无限流量网络)时所执行的网络操作。您甚至可以使用 JobScheduler 来适应内容提供程序变化。

如需了解有关 Android N 中后台优化以及如何改写应用的详细信息,请参阅后台优化

就是说,若你的目标版本若为7.0 及以上,但是你仍采用注册 CONNECTIVITY_ACTION 广播接收器的方式来监听网络, 那么你应该应该在代码中动态注册该网络状态广播接收器,而不应该静态注册在清单文件中

4.代码实现

在父类 Activity 中,使用代码来注册一个监听 CONNECTIVITY_ACTION 的广播接收器即可;如果你是一位喜欢积极响应Google号召的新青年,也可以捎带脚来判断下当前当前设备 Android 版本,在大于等于5.0时使用  ConnectivityManager requestNetwork() 或 registerNetworkCallback()方法来监听网络状态变化。

好了,

Talk is cheap,show me the money  code.

创建 MainActivity ,布局只有一个 TextView 来显示当前网络状态。

Maintivity 布局文件

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:id="@+id/root_view"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <TextView
            android:layout_gravity="center"
            android:textSize="18sp"
            android:id="@+id/tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Hello World!"/>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

创建一个网络状态广播接收器 NetworkStatusReceiver

    /**
     * 网络状态接收器
     */
    inner class NetworkStatusReceiver : BroadcastReceiver() {

        override fun onReceive(context: Context, intent: Intent) {
            if (intent.action == ConnectivityManager.CONNECTIVITY_ACTION) {
                val activeInfo = mConnectivityManager.activeNetworkInfo
                if (activeInfo != null && activeInfo.isConnected && activeInfo.isAvailable) {
                    val type = activeInfo.type
                    wifiConnected = type == ConnectivityManager.TYPE_WIFI
                    mobileConnected = type == ConnectivityManager.TYPE_MOBILE
                    if (wifiConnected) {
                        // wifi已连接
                        tv.text = "wifi已连接—NetworkStatusReceiver"
                    } else if (mobileConnected) {
                        // 移动网络已连接
                        tv.text = "移动网络已连接—NetworkStatusReceiver"
                    }
                    onNetAvailable()
                } else {
                    // 网络不可用
                    tv.text = "网络不可用—NetworkStatusReceiver"
                    onNetUnavailable()
                }
            }
        }
    }

wifiConnected 和 mobileConnected 是分别标识当前 wifi 和 移动网络 是否已连接的变量。

在 onCreate( ) 中注册这个接收器

    /**
     * 注册网络状态广播接收器
     */
    private fun registerNetworkStatusReceiver() {
        // 向filter中添加 ConnectivityManager.CONNECTIVITY_ACTION 以监听网络
        val intentFilter = IntentFilter()
        intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION)

        mNetWorkReceiver = NetworkStatusReceiver()
        registerReceiver(mNetWorkReceiver, intentFilter)

        needUnregisterReceiver = true
    }

在 onDestroy( ) 中来注销这个接收器(同时包含了注销 NetworkCallback 的代码)

    override fun onDestroy() {
        super.onDestroy()
        if (needUnregisterReceiver) {
            if (mNetWorkReceiver != null) {
                // 注销网络状态广播接收器
                unregisterReceiver(mNetWorkReceiver)
            }
        } else {
            // 注销 NetworkCallback
            unregisterNetworkCallback()
        }
    }

在老机子(Android 4.4 )上测试一下

也可以在注册接收器前,判断一下 Android 版本,5.0 及以上可以在 onCreate( ) 中调用 requestNetwork() 或 registerNetworkCallback()。我使用的是registerNetworkCallback(),因为在监听到网络可用时,我不希望系统切换到符合条件的网络。

    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
    private fun listeningNetwork() {
        // Android 5.0 及以上
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            // 经过测试,使用 networkCallback 来监测网络状态,
            // 如果app启动前网络可用,那么在app刚启动时 onAvailable( ) 方法会被调用;
            // 如果app启动前网络不可用,在app刚启动时,onLost( ) 方法却不会被调用,
            // 所以需要我们自己来监测一下初始网络状态,如果是不可用,则执行无网需要执行的操作
            val activeInfo = mConnectivityManager.activeNetworkInfo
            if (activeInfo == null || !activeInfo.isConnected || !activeInfo.isAvailable) {
                tv.text = "网络不可用—NetworkCallback"
                onNetUnavailable()
            }
            val request = NetworkRequest.Builder()
                // NetworkCapabilities.NET_CAPABILITY_INTERNET 表示此网络应该能够连接到Internet
                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                // NetworkCapabilities.TRANSPORT_WIFI 表示该网络使用Wi-Fi传输
                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
                // NetworkCapabilities.TRANSPORT_CELLULAR 表示此网络使用蜂窝传输
                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
                .build()

            mNetworkCallback = object : ConnectivityManager.NetworkCallback() {

                /**
                 * 当 framework 连接并已声明新网络可供使用时调用
                 */
                override fun onAvailable(network: Network) {
                    val activeInfo = mConnectivityManager.activeNetworkInfo
                    val type = activeInfo.type
                    wifiConnected = type == ConnectivityManager.TYPE_WIFI
                    mobileConnected = type == ConnectivityManager.TYPE_MOBILE
                    runOnUiThread {
                        if (wifiConnected) {
                            // wifi已连接
                            tv.text = "wifi已连接—NetworkCallback"
                        } else if (mobileConnected) {
                            // 移动网络已连接
                            tv.text = "移动网络已连接—NetworkCallback"
                        }
                    }

                    onNetAvailable()
                }

                /**
                 * 当此请求的 framework 连接到的网络更改功能,但仍满足所述需求时调用。
                 */
                override fun onCapabilitiesChanged(
                    network: Network,
                    networkCapabilities: NetworkCapabilities
                ) {
                    Log.e(TAG, "onCapabilitiesChanged,NetworkCapabilities -> $networkCapabilities")
                }

                /**
                 * 丢失网络时调用
                 */
                override fun onLost(network: Network) {
                    runOnUiThread {
                        tv.text = "网络不可用—NetworkCallback"
                        onNetUnavailable()
                    }
                }
            }
            mConnectivityManager.registerNetworkCallback(request, mNetworkCallback)
        } else {
            // 注册网络状态接收者
            registerNetworkStatusReceiver()
        }
    }

同时不要忘记在 onDestroy 中注销 ConnectivityManager.NetworkCallback

    private fun unregisterNetworkCallback() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            if (mNetworkCallback != null) {
                Log.d(TAG, "Unregistering network callback")
                mConnectivityManager.unregisterNetworkCallback(mNetworkCallback)
            }
        }
    }

在 Android 版本 7.0 的机子上测试一下

移动网络

当然了,你也可以加个 Snackbar,在网络不可用时来提示用户,引导用户进行网络设置

    /**
     * 网络不可用
     */
    private fun onNetUnavailable() {
        Log.e(TAG, "onNetUnavailable")
        if (mSnackbar == null) {
            mSnackbar = Snackbar.make(root_view, "当前网络不可用", Snackbar.LENGTH_INDEFINITE)
                .setAction("前往设置", View.OnClickListener {
                    startActivity(Intent(Settings.ACTION_WIRELESS_SETTINGS))
                })
        }
        if (!mSnackbar?.isShown!!) {
            mSnackbar?.show()
        }
    }

    /**
     * 网络可用
     */
    fun onNetAvailable() {
        Log.e(TAG, "onNetAvailable")
        if (mSnackbar != null && mSnackbar?.isShown!!) {
            mSnackbar?.dismiss()
        }
    }

Snackbar 配合 CoordinatorLayout 使用,可以通过右滑来 dismiss。

以下是 MainActivity的完整代码

package cn.net.leading.mynetworkstatesapplication

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import android.view.View
import androidx.annotation.RequiresPermission
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_main.*


class MainActivity : AppCompatActivity() {
    val TAG = MainActivity::class.java.simpleName

    private var mSnackbar: Snackbar? = null

    /**
     * ConnectivityManager
     */
    private lateinit var mConnectivityManager: ConnectivityManager

    /**
     * 在 5.0 以下版本使用的 网络状态接收器
     */
    private var mNetWorkReceiver: NetworkStatusReceiver? = null

    /**
     * 在 5.0 及以上版本使用的 NetworkCallback
     */
    private var mNetworkCallback: ConnectivityManager.NetworkCallback? = null

    /**
     * 是否需要注销网络状态接收器
     */
    private var needUnregisterReceiver = false

    /**
     * wifi 是否已连接
     */
    private var wifiConnected = false

    /**
     * 移动网络是否已连接
     */
    private var mobileConnected = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        mConnectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        // 监听网络
        listeningNetwork()
    }

    /**
     * 网络不可用
     */
    private fun onNetUnavailable() {
        Log.e(TAG, "onNetUnavailable")
        if (mSnackbar == null) {
            mSnackbar = Snackbar.make(root_view, "当前网络不可用", Snackbar.LENGTH_INDEFINITE)
                .setAction("前往设置", View.OnClickListener {
                    startActivity(Intent(Settings.ACTION_WIRELESS_SETTINGS))
                })
        }
        if (!mSnackbar?.isShown!!) {
            mSnackbar?.show()
        }
    }

    /**
     * 网络可用
     */
    fun onNetAvailable() {
        Log.e(TAG, "onNetAvailable")
        if (mSnackbar != null && mSnackbar?.isShown!!) {
            mSnackbar?.dismiss()
        }
    }

    @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
    private fun listeningNetwork() {
        // Android 5.0 及以上
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            // 经过测试,使用 networkCallback 来监测网络状态,
            // 如果app启动前网络可用,那么 onAvailable( ) 方法会被调用;
            // 如果app启动前网络不可用,在app刚启动时,onLost( ) 方法却不会被调用,
            // 所以需要我们自己来监测一下初始网络状态,如果是不可用,则执行无网需要执行的操作
            val activeInfo = mConnectivityManager.activeNetworkInfo
            if (activeInfo == null || !activeInfo.isConnected || !activeInfo.isAvailable) {
                tv.text = "网络不可用—NetworkCallback"
                onNetUnavailable()
            }
            val request = NetworkRequest.Builder()
                // NetworkCapabilities.NET_CAPABILITY_INTERNET 表示此网络应该能够连接到Internet
                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
                // NetworkCapabilities.TRANSPORT_WIFI 表示该网络使用Wi-Fi传输
                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
                // NetworkCapabilities.TRANSPORT_CELLULAR 表示此网络使用蜂窝传输
                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
                .build()

            mNetworkCallback = object : ConnectivityManager.NetworkCallback() {

                /**
                 * 当 framework 连接并已声明新网络可供使用时调用
                 */
                override fun onAvailable(network: Network) {
                    val activeInfo = mConnectivityManager.activeNetworkInfo
                    val type = activeInfo.type
                    wifiConnected = type == ConnectivityManager.TYPE_WIFI
                    mobileConnected = type == ConnectivityManager.TYPE_MOBILE
                    runOnUiThread {
                        if (wifiConnected) {
                            // wifi已连接
                            tv.text = "wifi已连接—NetworkCallback"
                        } else if (mobileConnected) {
                            // 移动网络已连接
                            tv.text = "移动网络已连接—NetworkCallback"
                        }
                    }

                    onNetAvailable()
                }

                /**
                 * 当此请求的 framework 连接到的网络更改功能,但仍满足所述需求时调用。
                 */
                override fun onCapabilitiesChanged(
                    network: Network,
                    networkCapabilities: NetworkCapabilities
                ) {
                    Log.e(TAG, "onCapabilitiesChanged,NetworkCapabilities -> $networkCapabilities")
                }

                /**
                 * 丢失网络时调用
                 */
                override fun onLost(network: Network) {
                    runOnUiThread {
                        tv.text = "网络不可用—NetworkCallback"
                        onNetUnavailable()
                    }
                }
            }
            mConnectivityManager.registerNetworkCallback(request, mNetworkCallback)
        } else {
            // 注册网络状态接收者
            registerNetworkStatusReceiver()
        }
    }

    private fun unregisterNetworkCallback() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            if (mNetworkCallback != null) {
                Log.d(TAG, "Unregistering network callback")
                mConnectivityManager.unregisterNetworkCallback(mNetworkCallback)
            }
        }
    }

    /**
     * 注册网络状态广播接收器
     */
    private fun registerNetworkStatusReceiver() {
        // 向filter中添加 ConnectivityManager.CONNECTIVITY_ACTION 以监听网络
        val intentFilter = IntentFilter()
        intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION)

        mNetWorkReceiver = NetworkStatusReceiver()
        registerReceiver(mNetWorkReceiver, intentFilter)

        needUnregisterReceiver = true
    }

    /**
     * 网络状态接收器
     */
    inner class NetworkStatusReceiver : BroadcastReceiver() {

        override fun onReceive(context: Context, intent: Intent) {
            if (intent.action == ConnectivityManager.CONNECTIVITY_ACTION) {
                val activeInfo = mConnectivityManager.activeNetworkInfo
                if (activeInfo != null && activeInfo.isConnected && activeInfo.isAvailable) {
                    val type = activeInfo.type
                    wifiConnected = type == ConnectivityManager.TYPE_WIFI
                    mobileConnected = type == ConnectivityManager.TYPE_MOBILE
                    if (wifiConnected) {
                        // wifi已连接
                        tv.text = "wifi已连接—NetworkStatusReceiver"
                    } else if (mobileConnected) {
                        // 移动网络已连接
                        tv.text = "移动网络已连接—NetworkStatusReceiver"
                    }
                    onNetAvailable()
                } else {
                    // 网络不可用
                    tv.text = "网络不可用—NetworkStatusReceiver"
                    onNetUnavailable()
                }
            }
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        if (needUnregisterReceiver) {
            if (mNetWorkReceiver != null) {
                // 注销网络状态广播接收器
                unregisterReceiver(mNetWorkReceiver)
            }
        } else {
            // 注销 NetworkCallback
            unregisterNetworkCallback()
        }
    }

}

demo链接:MyNetworkStatesApplication

本文也参考了 Google 的文档和官方示例:

Android 7.0 行为变更

android-WearHighBandwidthNetworking

android-BasicNetworking

猜你喜欢

转载自blog.csdn.net/qq_33404903/article/details/87864922