此篇存在的主要意义在于解决用户使用app中网络状态发生了变化,需要我们去动态监听网络连接状态(有网、无网)、网络类型 (包括wifi、移动网络 -> 3G、4G等等)
门前授课
关于网络状态的监听,主要是基于 Android 广播 - BroadcaseReceiver组件
~
同时关于广播的注册方面,从Android7.0开始已经初步进行限制,所以尽可能采用动态注册,献文如下:
Apps targeting Android 7.0 (API level 24) and higher do not receive this
broadcast if they declare the broadcast receiver in their manifest. Apps
will still receive broadcasts if they register their BroadcastReceiver with
Context.registerReceiver() and that context is still valid.
直译如下
针对Android 7.0
(API级别24)或更高的应用程序,如果它们在其manifest中声明广播接收器,则不会接收此广播。如果应用程序将其BroadcastReceiver注册为context
. registerreceiver()并且该context仍然有效,那么应用程序仍然会接收广播
Google在Android7.0时虽已对广播添加了限制,但是Android8.0后基于安全原因又一次加强了限制,且已声明:
应用无法使用其清单的大部分隐式广播(即,并非专门针对此应用的广播)
针对于此我们在本篇均使用动态注册广播,稍微科普一下,广播的两种注册方式 ~
Here:不论使用哪种注册方式均需在AndroidMainest清单文件里面进行注册
-
静态注册
也就是说在AndroidManifest文件中对BroadcastReceiver进行注册,通常还会加上action用来过滤;此注册方式即使退出应用后,仍然能够收到相应的广播 -
动态注册
调用Context中的registerReceiver对广播进行动态注册,使用unRegisterReceiver方法对广播进行取消注册的操作;故此注册方式一般都是随着所在的Activity或者应用销毁以后,不会再收到该广播
动态广播提示:广播注册是一个创建的过程,那么必然也要有一个销毁的过程,从而防止内存泄露,那么销毁就在是onDestroy中unregisterReceiver广播 ~
Activity/Fragment - 注销广播
java
@Override
protected void onDestroy() {
super.onDestroy();
//netWorkReceiver 之前已注册的广播
if (netWorkReceiver != null) {
unregisterReceiver(netWorkReceiver);
}
具体实现
此处就是本文的核心内容了,网络的实时动态监听指的是全局实时监听网络状态,针对的对象非一个事件而是整个APP应用项目 ~
AndroidMainfests - 添加权限
java
<!-- 网络状态 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
创建 - 广播接受者
- 基础版
这里主要实时判断网络连接状态,同时判断网络类型 ~
为何说是基础版,因为这里虽然实现了功能,但是会重复收到网络变动的广播,太影响业务逻辑了!很有可能导致其他并发情况!
监听效果
监听实现 - NetworkReceiver(广播接收者)
java
package nk.com.networklinstener;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.util.Log;
/**
* @author MrLiu
* @date 2020/5/11
* desc 广播接收者
*/
public class NetworkReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 监听网络连接,包括wifi和移动数据的打开和关闭,以及连接上可用的连接都会接到监听
// 特殊注意:如果if条件生效,那么证明当前是有连接wifi或移动网络的,如果有业务逻辑最好把esle场景酌情考虑进去!
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
//获取联网状态的NetworkInfo对象
NetworkInfo info = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
if (info != null) {
//如果当前的网络连接成功并且网络连接可用
if (NetworkInfo.State.CONNECTED == info.getState() && info.isAvailable()) {
if (info.getType() == ConnectivityManager.TYPE_WIFI || info.getType() == ConnectivityManager.TYPE_MOBILE) {
Log.e("TAG", getConnectionType(info.getType()) + "已连接");
}
} else {
Log.e("TAG", getConnectionType(info.getType()) + "已断开");
}
}
}
}
/**
* 获取连接类型
* @param type
* @return
*/
private String getConnectionType(int type) {
String connType = "";
if (type == ConnectivityManager.TYPE_MOBILE) {
connType = "3-4G网络数据";
} else if (type == ConnectivityManager.TYPE_WIFI) {
connType = "WIFI网络";
}
return connType;
}
}
- 优化版(推荐)
这里在判断网络连接状态的同时主要防止收到多条网络连接回调,从而导致逻辑错乱,所以采用时间校验实现类似同步锁的效果
监听效果
监听实现 - NetworkReceiver(广播接收者)
java
package com.nk.machine.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.util.Log;
import com.nk.machine.base.MainActivity;
import java.text.SimpleDateFormat;
/**
* @author MrLiu
* @date 2020/5/15
* desc 广播接收者
*/
public class NetWorkReceiver extends BroadcastReceiver {
private static long WIFI_TIME = 0;
private static long ETHERNET_TIME = 0;
private static long NONE_TIME = 0;
private static int LAST_TYPE = -3;
private static String TAG = "TAG";
@Override
public void onReceive(Context context, Intent intent) {
// 监听网络连接,包括wifi和移动数据的打开和关闭,以及连接上可用的连接都会接到监听
// 特殊注意:如果if条件生效,那么证明当前是有连接wifi或移动网络的,如果有业务逻辑最好把esle场景酌情考虑进去!
if (intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
long time = getTime();
if (time != WIFI_TIME && time != ETHERNET_TIME && time != NONE_TIME) {
final int netWorkState = getNetWorkState(context);
if (netWorkState == 0 && LAST_TYPE != 0) {
WIFI_TIME = time;
LAST_TYPE = netWorkState;
Log.e(TAG, "wifi:" + time);
} else if (netWorkState == 1 && LAST_TYPE != 1) {
ETHERNET_TIME = time;
LAST_TYPE = netWorkState;
Log.e(TAG, "数据网络:" + time);
} else if (netWorkState == -1 && LAST_TYPE != -1) {
NONE_TIME = time;
LAST_TYPE = netWorkState;
Log.e(TAG, "无网络:" + time);
}
}
}
}
public long getTime() {
SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyyMMddhhmmss");
String date = sDateFormat.format(new java.util.Date());
return Long.valueOf(date);
}
private static final int NETWORK_NONE = -1; //无网络连接
private static final int NETWORK_WIFI = 0; //wifi
private static final int NETWORK_MOBILE = 1; //数据网络
//判断网络状态与类型
public static int getNetWorkState(Context context) {
ConnectivityManager connectivityManager = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
if (activeNetworkInfo != null && activeNetworkInfo.isConnected()) {
if (activeNetworkInfo.getType() == (ConnectivityManager.TYPE_WIFI)) {
return NETWORK_WIFI;
} else if (activeNetworkInfo.getType() == (ConnectivityManager.TYPE_MOBILE)) {
return NETWORK_MOBILE;
}
} else {
return NETWORK_NONE;
}
return NETWORK_NONE;
}
}
MainActivity - 动态注册广播
java
package nk.com.networklinstener;
import android.annotation.SuppressLint;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.wifi.WifiManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
private NetworkReceiver netWorkReceiver;
@SuppressLint("SetJavaScriptEnabled")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//注册网络状态监听广播
netWorkReceiver = new NetworkReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
registerReceiver(netWorkReceiver, filter);
}
@Override
protected void onDestroy() {
super.onDestroy();
if (netWorkReceiver != null) {
unregisterReceiver(netWorkReceiver);
}
}
}
异常场景
首先AndroidManifest加入以下权限
java
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
此异常场景指的是app在运行时有崩溃异常 - SecurityException,错误如下 ~
java
java.lang.SecurityException: com.xxx.xxx was not granted either of these permissions: android.permission.CHANGE_NETWORK_STATE, android.permission.WRITE_SETTINGS.
at android.os.Parcel.readException(Parcel.java:1602)
at android.os.Parcel.readException(Parcel.java:1555)
at android.net.IConnectivityManager$Stub$Proxy.requestNetwork(IConnectivityManager.java:2064)
at android.net.ConnectivityManager.sendRequestForNetwork(ConnectivityManager.java:2470)
at android.net.ConnectivityManager.requestNetwork(ConnectivityManager.java:2509)
at com.superrtc.call.NetworkMonitorAutoDetect$ConnectivityManagerDelegate.requestMobileNetwork(NetworkMonitorAutoDetect.java)
at com.superrtc.call.NetworkMonitorAutoDetect.<init>(NetworkMonitorAutoDetect.java)
at com.superrtc.mediamanager.XReachability.setAutoDetectConnectivityStateInternal(XReachability.java)
at com.superrtc.mediamanager.XReachability.startMonitoring(XReachability.java)
at com.superrtc.mediamanager.EMediaManager$7.run(EMediaManager.java)
at android.os.Handler.handleCallback(Handler.java:743)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:150)
at com.superrtc.util.LooperExecutor.run(LooperExecutor.java)
按理说android.permission.CHANGE_NETWORK_STATE这一个权限,是普通权限,只在Manifest中声明就可以获取。
出现这个问题很不科学啊,再仔细去看看错误日志,发现报错只发生在6.0这个版本和部分6.0.1版本中。
那问题应该是出在6.0
版本中,之后去查资料后发现,在6.0版本中这个权限默认是被拒绝,无法获取这个权限。所以,在需要个权限的时候会出现权限问题导致应用因为权限问题崩溃。这个在stackoverflow中有人讨论过了,去看看
知道问题的原因之后的解决方案就简单了,既然CHANGE_NETWORK_STATE权限获取不到,那只好想办法打开WRITE_SETTINGS这个权限了。
跳转到应用程序设置页打开WRITE_SETTINGS这个权限:
java
Intent goToSettings = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
goToSettings.setData(Uri.parse("package:" + getPackageName()));
startActivity(goToSettings);
兴趣扩展
网络广播Actiion的三种类型
WifiManager.WIFI_STATE_CHANGED_ACTION
这个监听wifi的打开与关闭,与wifi的连接无关;示例如下,主要查看一下 wifi连接 - 关闭过程
监听效果
示例代码
java
package nk.com.networklinstener;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.wifi.WifiManager;
import android.util.Log;
/**
* @author MrLiu
* @date 2020/5/11
* desc wifi连接/断开过程
*/
public class NetworkReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 监听wifi的打开与关闭,与wifi的连接无关
if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(intent.getAction())) {
int wifiState = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 0);
Log.e("TAG", "wifiState:" + wifiState);
switch (wifiState) {
case WifiManager.WIFI_STATE_ENABLING:
Log.e("TAG", "wifi状态:打开中");
break;
case WifiManager.WIFI_STATE_ENABLED://主用:打开状态
Log.e("TAG", "wifi状态:已打开");
break;
case WifiManager.WIFI_STATE_DISABLING:
Log.e("TAG", "wifi状态:关闭中");
break;
case WifiManager.WIFI_STATE_DISABLED://主用:关闭状态
Log.e("TAG", "wifi状态:已关闭");
break;
case WifiManager.WIFI_STATE_UNKNOWN:
Log.e("TAG", "wifi状态:无法识别");
break;
default:
Log.e("TAG", "wifi状态:未知");
break;
}
}
}
}
WifiManager.NETWORK_STATE_CHANGED_ACTION
这个监听wifi的连接状态即是否连上了一个有效无线路由,当上边广播的状态是WifiManager.WIFI_STATE_DISABLING(wifi
关闭中)、WIFI_STATE_DISABLED(wifi 已关闭)的时候,根本不会接到这个广播。
在上边广播接到广播是WifiManager.WIFI_STATE_ENABLED(wifi
已打开)状态的同时也会接到这个广播,当然刚打开wifi肯定还没有连接到有效的无线
示例代码
java
package nk.com.networklinstener;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.wifi.WifiManager;
import android.util.Log;
/**
* @author MrLiu
* @date 2020/5/11
* desc wifi连接/断开过程
*/
public class NetworkReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 这个监听wifi的连接状态即是否连上了一个有效无线路由,具体如上所述
if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(intent.getAction())) {
Parcelable parcelableExtra = intent
.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
if (null != parcelableExtra) {
NetworkInfo networkInfo = (NetworkInfo) parcelableExtra;
State state = networkInfo.getState();
// 当然,这边可以更精确的确定状态
boolean isConnected = state == State.CONNECTED;
Log.e(TAG1, "isConnected" + isConnected);
if (isConnected) {
APP.getInstance().setWifi(true);
} else {
APP.getInstance().setWifi(false);
}
}
}
}
}
ConnectivityManager.CONNECTIVITY_ACTION
这个监听网络连接的设置,包括wifi和移动数据的打开和关闭;
此监听的作用范围最大!不论wifi的打开、关闭,或是连接上可用的链接都会接到监听;当然有利必有弊,那就是因为范围太广,所以效率相比上方的俩个监听要慢一些!
如果上方的俩个监听需求可用满足我们的业务需求,那么可以优先适用上方俩种监听方式搭配使用;但个人建议如果业务需求上面俩个监听满足不了的话,还是可以采用懒人方式直接用此监听!
Of Course :这里所谓的示例代码,就是最早看到的动态监听的实现部分了!
监听常用
此处主要借鉴与此
-
获取ConnectivityManager对象
java
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context
.CONNECTIVITY_SERVICE); -
获取 NetworkInfo对象
java
//但是这个方法已经过时,官网的解释如下:This method was deprecated in API level 23. This method does not support multiple connected networks of the same type. Use getAllNetworks() and
getNetworkInfo(int networkType)
getNetworkInfo(android.net.Network) instead.
getNetworkInfo(Network network)
getActiveNetwork()
Returns a Network object corresponding to the currently active default data network.
getActiveNetworkInfo(),Returns details about the currently active default data network.
//这个方法已经过时,Use getAllNetworks() and getNetworkInfo(android.net.Network) instead.
getAllNetworkInfo() -
综上所述,我们如果要知道当前Mobile网络或者WiFi网络是否已经连接上,总共有两种方法
方法一:API23时已过时
java
//猜测:APP.getInstance()应该是一个关于网络相关的单例模式,主要用于app其他地方使用
void getNetwork(Context context){
State wifiState = null;
State mobileState = null;
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context
.CONNECTIVITY_SERVICE);
wifiState = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI).getState();
mobileState = cm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE).getState();
Log.d(TAG1,"wifi状态:" + wifiState + "\n mobile状态:" + mobileState);
// 手机网络连接成功
if (wifiState != null && mobileState != null
&& State.CONNECTED != wifiState
&& State.CONNECTED == mobileState) {
Log.d("tag", "手机2g/3g/4g网络连接成功");
APP.getInstance().setMobile(true);
APP.getInstance().setWifi(false);
APP.getInstance().setConnected(true);
}
// 无线网络连接成功
else if (wifiState != null && State.CONNECTED == wifiState) {
Log.d("tag", "无线网络连接成功");
APP.getInstance().setMobile(false);
APP.getInstance().setWifi(true);
APP.getInstance().setConnected(true);
}
// 手机没有任何的网络
else if (wifiState != null && mobileState != null
&& State.CONNECTED != wifiState
&& State.CONNECTED != mobileState) {
Log.d("tag", "手机没有任何的网络");
APP.getInstance().setMobile(false);
APP.getInstance().setWifi(false);
APP.getInstance().setConnected(false);
}
}
方法二:此处和我在上方的网络实时监听大致相同
java
//猜测:APP.getInstance()应该是一个关于网络相关的单例模式,主要用于app其他地方使用
void getNetwork(Context context) {
ConnectivityManager manager = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
Log.i("tag", "CONNECTIVITY_ACTION");
NetworkInfo activeNetwork = manager.getActiveNetworkInfo();
// connected to the internet
if (activeNetwork != null) {
if (activeNetwork.isConnected()) {
if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) {
// connected to wifi
APP.getInstance().setWifi(true);
Log.e("tag", "当前WiFi连接可用 ");
} else if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) {
// connected to the mobile provider's data plan
APP.getInstance().setMobile(true);
Log.e("tag", "当前移动网络连接可用 ");
}
} else {
Log.e("tag", "当前没有网络连接,请确保你已经打开网络 ");
}
} // not connected to the internet
else {
Log.e("tag", "当前没有网络连接,请确保你已经打开网络 ");
APP.getInstance().setWifi(false);
APP.getInstance().setMobile(false);
APP.getInstance().setConnected(false);
}
}
闲来无事正好看到了屏幕打开/关闭的的监听广播,特此记录一番
java
package nk.com.networklinstener;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
/**
* @author MrLiu
* @date 2020/5/12
* desc 此时APP可以后台运行,广播依然有效,但是如果被杀死则无法收到广播。
*
*/
public class ScreenReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
case Intent.ACTION_SCREEN_OFF:
Log.e("屏幕广播","屏幕被关闭");
break;
case Intent.ACTION_SCREEN_ON:
Log.e("屏幕广播","屏幕被打开");
break;
}
}
}
ConnectivityManager API 提供一个更强大的方法,用于仅在满足指定的网络条件时请求回调。首先我们获取到系统的
ConnectivityManager
服务, 并且使用 registerNetworkCallback
将 NetworkRequest
对象传递给系统,最终系统会通过 ConnectivityManager.NetworkCallback
回调,将网络变更情况告知给应用。
java
//获取ConnectivityManager
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
//定义ConnectivityManager.NetworkCallback回调方法
callback = new ConnectivityManager.NetworkCallback() {
// 可用网络接入
public void onCapabilitiesChanged(@NotNull Network network, @NotNull NetworkCapabilities networkCapabilities) {
super.onCapabilitiesChanged(network, networkCapabilities);
LogUtils.d("onCapabilitiesChanged");
checkNetworkCapabilities(networkCapabilities);
}
@Override
public void onLost(@NonNull Network network) {
super.onLost(network);
if (cm != null) {
Network activeNetwork = cm.getActiveNetwork();
if (activeNetwork == null) {
//连接不到可用网络
return;
}
NetworkCapabilities networkCapabilities = cm.getNetworkCapabilities(activeNetwork);
checkNetworkCapabilities(networkCapabilities);
}
}
};
NetworkRequest.Builder builder = new NetworkRequest.Builder();
if (cm != null) {
cm.registerNetworkCallback(builder.build(), callback);
}
//判断当前网络连接情况
private void checkNetworkCapabilities(NetworkCapabilities networkCapabilities) {
if (networkCapabilities == null) {
return;
}
// 表明网络连接成功
if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI_AWARE)) {
// 使用WI-FI
LogUtils.d("WIFI network");
} else if (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
// 使用蜂窝网络
LogUtils.d("mobile network");
} else {
// 未知网络,包括蓝牙、VPN、LoWPAN
LogUtils.d("unknown network");
}
} else {
//网络连接失败
}
}