Android網(wǎng)絡(luò)狀態(tài)監(jiān)聽實(shí)現(xiàn)
功能分析
背景介紹
為了給用戶一個(gè)好的使用體驗(yàn),尤其是一些視頻嘁扼、圖片類型的app趁啸,我們經(jīng)常需要在用戶網(wǎng)絡(luò)狀態(tài)發(fā)生變化的時(shí)候給用戶一些及時(shí)的提示督惰,比如當(dāng)前從Wi-Fi切換到GPRS,那么就需要給用戶提示是否需要繼續(xù)播放并會產(chǎn)生多少流量访娶。對于網(wǎng)絡(luò)狀態(tài)變化的監(jiān)聽方法很簡單崖疤,不管是用廣播還是NetworkCallback都可以很好實(shí)現(xiàn)。本文從一個(gè)小架構(gòu)的角度劫哼,嘗試把網(wǎng)絡(luò)狀態(tài)的監(jiān)聽功能抽出封裝成一個(gè)lib庫权烧,便于任何一個(gè)項(xiàng)目、版本使用妻率。
功能分析
要監(jiān)聽網(wǎng)絡(luò)狀態(tài)發(fā)生變化板祝,以及對于Android不同版本的支持,站在一個(gè)lib角度來實(shí)現(xiàn)該功能的話囊嘉,我們就需要做到以下幾點(diǎn):
- 定義好網(wǎng)絡(luò)的狀態(tài)
- 根據(jù)不同版本使用不同的網(wǎng)絡(luò)監(jiān)聽方法
- 讓使用的地方盡可能的少集成即可使用該功能
網(wǎng)絡(luò)狀態(tài)定義
對于手機(jī)而言扭粱,我們關(guān)注的網(wǎng)絡(luò)狀態(tài)大體有三種:沒網(wǎng)情況震檩、Wi-Fi情況、移動(dòng)蜂窩GPRS博其,對于狀態(tài)的定義迂猴,我們要用到j(luò)ava的枚舉類型來定義這些類型。
網(wǎng)絡(luò)狀態(tài)變化監(jiān)聽方法
- BroadcastReceiver
一開始的時(shí)候峰髓,我們可能都習(xí)慣于用BroadcastReceiver來監(jiān)聽網(wǎng)絡(luò)狀態(tài)的變化息尺,BroadcastReceiver的注冊分為靜態(tài)manifest注冊和動(dòng)態(tài)注冊搂誉,雖然通過manifest注冊比較簡單,但是在Android 7.0(targetSdkVersion >= 24)以后并级,新版本移除了一些隱式的廣播,意味著7.0及以上版本無法通過manifest注冊廣播來監(jiān)聽網(wǎng)絡(luò)狀態(tài)變化广恢,所以通過BroadcastReceiver的方式我們只能在代碼中動(dòng)態(tài)注冊廣播了钉迷。
- NetworkCallback
我們要做的功能時(shí)監(jiān)聽網(wǎng)絡(luò)狀態(tài)的變化糠聪,Android 21版本時(shí)增加了NetworkCallback類來監(jiān)聽網(wǎng)絡(luò)狀態(tài)的變化谐鼎,源碼如下:
public static class NetworkCallback {
public NetworkCallback() {
throw new RuntimeException("Stub!");
}
public void onAvailable(Network network) {//網(wǎng)絡(luò)可用的時(shí)候調(diào)用
throw new RuntimeException("Stub!");
}
public void onLosing(Network network, int maxMsToLive) {//網(wǎng)絡(luò)正在減弱狸棍,鏈接會丟失數(shù)據(jù)草戈,即將斷開網(wǎng)絡(luò)時(shí)調(diào)用
throw new RuntimeException("Stub!");
}
public void onLost(Network network) {//網(wǎng)絡(luò)斷開時(shí)調(diào)用
throw new RuntimeException("Stub!");
}
public void onUnavailable() {//網(wǎng)絡(luò)缺失network時(shí)調(diào)用
throw new RuntimeException("Stub!");
}
public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {//網(wǎng)絡(luò)功能發(fā)生改變時(shí)調(diào)用
throw new RuntimeException("Stub!");
}
//網(wǎng)絡(luò)連接屬性發(fā)生改變時(shí)調(diào)用
public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {
throw new RuntimeException("Stub!");
}
}
NetworkCallback的監(jiān)聽有兩種方法:
registerDefaultNetworkCallback(NetworkCallback callback) //Android API 26時(shí)加入
registerNetworkCallback(NetworkRequest request,NetworkCallback callback) //Android API 21時(shí)加入
Android 官方建議API 28及以上通過NetworkCallback的方式來監(jiān)聽網(wǎng)絡(luò)狀態(tài)的變化
其他功能考慮
接收到消息變化了唐片,接下來我們需要實(shí)現(xiàn)將變化的狀態(tài)消息傳遞到使用的地方,這里就用到消息分發(fā)通信的機(jī)制了茧球,我們有兩個(gè)方案供選擇:
- 通過接口
定義一個(gè)接口抢埋,每一個(gè)是用到的地方實(shí)現(xiàn)接口對象钉汗,并將該對象注冊保存起來,在網(wǎng)絡(luò)狀態(tài)變化時(shí)調(diào)用即可。
- 通過EventBus那樣的消息分發(fā)機(jī)制
像EventBus那樣酒来,通過消息分發(fā)機(jī)制,通過注解的標(biāo)注伟墙,收集起需要接受狀態(tài)變化的方法滴铅,在網(wǎng)絡(luò)狀態(tài)變化時(shí)通過反射調(diào)用執(zhí)行汉匙。
網(wǎng)絡(luò)狀態(tài)有多個(gè),可能在某些場景下戏自,我們只需要關(guān)注切換到某一個(gè)狀態(tài)的情況伤锚,以及對于代碼書寫和耦合性考慮屯援,這里采用第二種方案。
代碼實(shí)現(xiàn)
根據(jù)上面的分析弯淘,我們創(chuàng)建一個(gè)Android lib的module之后耳胎,有以下幾個(gè)類元素需要寫:
網(wǎng)絡(luò)狀態(tài)枚舉
網(wǎng)絡(luò)狀態(tài)簡單來說惕它,可分為沒網(wǎng)情況淹魄、Wi-Fi情況、移動(dòng)蜂窩GPRS兆蕉,詳細(xì)分類的話虎韵,當(dāng)然缸废,詳細(xì)分的話還可以分2G、3G测萎、Wi-Fi沒網(wǎng)情況,我們先知考慮簡單的情況:
package com.anonyper.networkmonitor.bean;
/**
* 網(wǎng)絡(luò)狀態(tài)枚舉
* AnnotationApplication
* Created by anonyper on 2019/6/10.
*/
public enum NetWorkState {
WIFI,//Wi-Fi網(wǎng)絡(luò)
GPRS,//移動(dòng)蜂窩網(wǎng)絡(luò)
NONE//沒有網(wǎng)絡(luò)
}
標(biāo)注注解
該注解是運(yùn)行時(shí)份乒,標(biāo)注在方法上的:
@Retention(RetentionPolicy.RUNTIME)//運(yùn)行時(shí)注解
@Target(ElementType.METHOD)//標(biāo)記在方法上
public @interface NetWorkMonitor {
//監(jiān)聽的網(wǎng)絡(luò)狀態(tài)變化 默認(rèn)全部監(jiān)聽并提示
NetWorkState[] monitorFilter() default {NetWorkState.GPRS, NetWorkState.WIFI, NetWorkState.NONE};
}
存儲需要運(yùn)行方法的對象
我們要收集起需要接受網(wǎng)絡(luò)狀態(tài)變化的方法對象或辖,通過反射的方式來調(diào)用執(zhí)行孝凌,和我們前面講過的運(yùn)行時(shí)注解一樣月腋,我們定義一個(gè)NetWorkStateReceiverMethod對象榆骚,里面包含:
- 該方法所屬的對象
- 該方法的Method對象
- 該方法需要監(jiān)聽的網(wǎng)絡(luò)狀態(tài)變化類型妓肢,即上面注解的屬性值:monitorFilter
這個(gè)里面我們沒有寫出方法需要的執(zhí)行的參數(shù),是因?yàn)楸O(jiān)聽網(wǎng)絡(luò)狀態(tài)變化接受的方法里面的參數(shù)有且僅有一個(gè)纲缓,就是當(dāng)前的網(wǎng)絡(luò)狀態(tài)祝高,所以我們不需要保存污筷,同時(shí)也可以根據(jù)參數(shù)類型來過濾不必要的方法。
package com.anonyper.networkmonitor.bean;
import java.lang.reflect.Method;
/**
* 保存接受狀態(tài)變化的方法對象
* AnnotationApplication
* Created by anonyper on 2019/6/10.
*/
public class NetWorkStateReceiverMethod {
/**
* 網(wǎng)絡(luò)改變執(zhí)行的方法
*/
Method method;
/**
* 網(wǎng)絡(luò)改變執(zhí)行的方法所屬的類
*/
Object object;
/**
* 監(jiān)聽的網(wǎng)絡(luò)改變類型
*/
NetWorkState[] netWorkState = {NetWorkState.GPRS, NetWorkState.WIFI, NetWorkState.NONE};
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
public NetWorkState[] getNetWorkState() {
return netWorkState;
}
public void setNetWorkState(NetWorkState[] netWorkState) {
this.netWorkState = netWorkState;
}
}
管理類
對外提供的統(tǒng)一的入口陆蟆,所以我們這個(gè)管理類需要有以下幾點(diǎn)功能:
- 全局單一
- 接受傳入Application(注冊廣播叠殷,監(jiān)聽網(wǎng)絡(luò)狀態(tài)用)
- 提供注冊和反注冊入口
- 處理好不同版本網(wǎng)絡(luò)監(jiān)聽的方法兼容
- 存儲所有需要接受網(wǎng)絡(luò)狀態(tài)變化消息的方法
- 網(wǎng)絡(luò)狀態(tài)變化時(shí)溪猿,根據(jù)狀態(tài)類型纫塌,通知對應(yīng)的方法
源碼如下:
package com.anonyper.networkmonitor;
import android.app.Application;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.os.Build;
import com.anonyper.networkmonitor.annotation.NetWorkMonitor;
import com.anonyper.networkmonitor.bean.NetWorkState;
import com.anonyper.networkmonitor.bean.NetWorkStateReceiverMethod;
import com.anonyper.utillibrary.NetStateUtils;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* AnnotationApplication
* Created by anonyper on 2019/6/10.
*/
public class NetWorkMonitorManager {
public static final String TAG = "NetWorkMonitor >>> : ";
private static NetWorkMonitorManager ourInstance;
private Application application;
public static NetWorkMonitorManager getInstance() {
synchronized (NetWorkMonitorManager.class) {
if (ourInstance == null) {
ourInstance = new NetWorkMonitorManager();
}
}
return ourInstance;
}
/**
* 存儲接受網(wǎng)絡(luò)狀態(tài)變化消息的方法的map
*/
Map<Object, NetWorkStateReceiverMethod> netWorkStateChangedMethodMap = new HashMap<>();
private NetWorkMonitorManager() {
}
/**
* 初始化 傳入application
*
* @param application
*/
public void init(Application application) {
if (application == null) {
throw new NullPointerException("application can not be null");
}
this.application = application;
initMonitor();
}
/**
* 初始化網(wǎng)絡(luò)監(jiān)聽 根據(jù)不同版本做不同的處理
*/
private void initMonitor() {
ConnectivityManager connectivityManager = (ConnectivityManager) this.application.getSystemService(Context.CONNECTIVITY_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {//API 大于26時(shí)
connectivityManager.registerDefaultNetworkCallback(networkCallback);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {//API 大于21時(shí)
NetworkRequest.Builder builder = new NetworkRequest.Builder();
NetworkRequest request = builder.build();
connectivityManager.registerNetworkCallback(request, networkCallback);
} else {//低版本
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ANDROID_NET_CHANGE_ACTION);
this.application.registerReceiver(receiver, intentFilter);
}
}
/**
* 反注冊廣播
*/
private void onDestroy() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
this.application.unregisterReceiver(receiver);
}
}
/**
* 注入
* @param object
*/
public void register(Object object) {
if (this.application == null) {
throw new NullPointerException("application can not be null,please call the method init(Application application) to add the Application");
}
if (object != null) {
NetWorkStateReceiverMethod netWorkStateReceiverMethod = findMethod(object);
if (netWorkStateReceiverMethod != null) {
netWorkStateChangedMethodMap.put(object, netWorkStateReceiverMethod);
}
}
}
/**
* 刪除
*
* @param object
*/
public void unregister(Object object) {
if (object != null && netWorkStateChangedMethodMap != null) {
netWorkStateChangedMethodMap.remove(object);
}
}
/**
* 網(wǎng)絡(luò)狀態(tài)發(fā)生變化,需要去通知更改
* @param netWorkState
*/
private void postNetState(NetWorkState netWorkState) {
Set<Object> set = netWorkStateChangedMethodMap.keySet();
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
Object object = iterator.next();
NetWorkStateReceiverMethod netWorkStateReceiverMethod = netWorkStateChangedMethodMap.get(object);
invokeMethod(netWorkStateReceiverMethod, netWorkState);
}
}
/**
* 具體執(zhí)行方法
*
* @param netWorkStateReceiverMethod
* @param netWorkState
*/
private void invokeMethod(NetWorkStateReceiverMethod netWorkStateReceiverMethod, NetWorkState netWorkState) {
if (netWorkStateReceiverMethod != null) {
try {
NetWorkState[] netWorkStates = netWorkStateReceiverMethod.getNetWorkState();
for (NetWorkState myState : netWorkStates) {
if (myState == netWorkState) {
netWorkStateReceiverMethod.getMethod().invoke(netWorkStateReceiverMethod.getObject(), netWorkState);
return;
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
/**
* 找到對應(yīng)的方法
*
* @param object
* @return
*/
private NetWorkStateReceiverMethod findMethod(Object object) {
NetWorkStateReceiverMethod targetMethod = null;
if (object != null) {
Class myClass = object.getClass();
//獲取所有的方法
Method[] methods = myClass.getDeclaredMethods();
for (Method method : methods) {
//如果參數(shù)個(gè)數(shù)不是1個(gè) 直接忽略
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (method.getParameterCount() != 1) {
continue;
}
}
//獲取方法參數(shù)
Class[] parameters = method.getParameterTypes();
if (parameters == null || parameters.length != 1) {
continue;
}
//參數(shù)的類型需要時(shí)NetWorkState類型
if (parameters[0].getName().equals(NetWorkState.class.getName())) {
//是NetWorkState類型的參數(shù)
NetWorkMonitor netWorkMonitor = method.getAnnotation(NetWorkMonitor.class);
targetMethod = new NetWorkStateReceiverMethod();
//如果沒有添加注解,默認(rèn)就是所有網(wǎng)絡(luò)狀態(tài)變化都通知
if (netWorkMonitor != null) {
NetWorkState[] netWorkStates = netWorkMonitor.monitorFilter();
targetMethod.setNetWorkState(netWorkStates);
}
targetMethod.setMethod(method);
targetMethod.setObject(object);
//只添加第一個(gè)符合的方法
return targetMethod;
}
}
}
return targetMethod;
}
private static final String ANDROID_NET_CHANGE_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equalsIgnoreCase(ANDROID_NET_CHANGE_ACTION)) {
//網(wǎng)絡(luò)發(fā)生變化 沒有網(wǎng)絡(luò)-0:WIFI網(wǎng)絡(luò)1:4G網(wǎng)絡(luò)-4:3G網(wǎng)絡(luò)-3:2G網(wǎng)絡(luò)-2
int netType = NetStateUtils.getAPNType(context);
NetWorkState netWorkState = NetWorkState.NONE;
switch (netType) {
case 0://None
netWorkState = NetWorkState.NONE;
break;
case 1://Wifi
netWorkState = NetWorkState.WIFI;
break;
default://GPRS
netWorkState = NetWorkState.GPRS;
break;
}
postNetState(netWorkState);
}
}
};
ConnectivityManager.NetworkCallback networkCallback = new ConnectivityManager.NetworkCallback() {
/**
* 網(wǎng)絡(luò)可用的回調(diào)連接成功
*/
@Override
public void onAvailable(Network network) {
super.onAvailable(network);
int netType = NetStateUtils.getAPNType(NetWorkMonitorManager.this.application);
NetWorkState netWorkState = NetWorkState.NONE;
switch (netType) {
case 0://None
netWorkState = NetWorkState.NONE;
break;
case 1://Wifi
netWorkState = NetWorkState.WIFI;
break;
default://GPRS
netWorkState = NetWorkState.GPRS;
break;
}
postNetState(netWorkState);
}
/**
* 網(wǎng)絡(luò)不可用時(shí)調(diào)用和onAvailable成對出現(xiàn)
*/
@Override
public void onLost(Network network) {
super.onLost(network);
postNetState(NetWorkState.NONE);
}
/**
* 在網(wǎng)絡(luò)連接正常的情況下,丟失數(shù)據(jù)會有回調(diào) 即將斷開時(shí)
*/
@Override
public void onLosing(Network network, int maxMsToLive) {
super.onLosing(network, maxMsToLive);
}
/**
* 網(wǎng)絡(luò)功能更改 滿足需求時(shí)調(diào)用
* @param network
* @param networkCapabilities
*/
@Override
public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
super.onCapabilitiesChanged(network, networkCapabilities);
}
/**
* 網(wǎng)絡(luò)連接屬性修改時(shí)調(diào)用
* @param network
* @param linkProperties
*/
@Override
public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) {
super.onLinkPropertiesChanged(network, linkProperties);
}
/**
* 網(wǎng)絡(luò)缺失network時(shí)調(diào)用
*/
@Override
public void onUnavailable() {
super.onUnavailable();
}
};
}
里面引用的一個(gè)NetStateUtils類是網(wǎng)上找到關(guān)于網(wǎng)絡(luò)的工具類:
/**
* 獲取當(dāng)前的網(wǎng)絡(luò)狀態(tài) :沒有網(wǎng)絡(luò)-0:WIFI網(wǎng)絡(luò)1:4G網(wǎng)絡(luò)-4:3G網(wǎng)絡(luò)-3:2G網(wǎng)絡(luò)-2
* 自定義
*
* @param context
* @return
*/
public static int getAPNType(Context context) {
//結(jié)果返回值
int netType = 0;
//獲取手機(jī)所有連接管理對象
ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context
.CONNECTIVITY_SERVICE);
//獲取NetworkInfo對象
NetworkInfo networkInfo = manager.getActiveNetworkInfo();
//NetworkInfo對象為空 則代表沒有網(wǎng)絡(luò)
if (networkInfo == null) {
return netType;
}
//否則 NetworkInfo對象不為空 則獲取該networkInfo的類型
int nType = networkInfo.getType();
if (nType == ConnectivityManager.TYPE_WIFI) {
//WIFI
netType = 1;
} else if (nType == ConnectivityManager.TYPE_MOBILE) {
int nSubType = networkInfo.getSubtype();
TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService
(Context.TELEPHONY_SERVICE);
//3G 聯(lián)通的3G為UMTS或HSDPA 電信的3G為EVDO
if (nSubType == TelephonyManager.NETWORK_TYPE_LTE
&& !telephonyManager.isNetworkRoaming()) {
netType = 4;
} else if (nSubType == TelephonyManager.NETWORK_TYPE_UMTS
|| nSubType == TelephonyManager.NETWORK_TYPE_HSDPA
|| nSubType == TelephonyManager.NETWORK_TYPE_EVDO_0
&& !telephonyManager.isNetworkRoaming()) {
netType = 3;
//2G 移動(dòng)和聯(lián)通的2G為GPRS或EGDE,電信的2G為CDMA
} else if (nSubType == TelephonyManager.NETWORK_TYPE_GPRS
|| nSubType == TelephonyManager.NETWORK_TYPE_EDGE
|| nSubType == TelephonyManager.NETWORK_TYPE_CDMA
&& !telephonyManager.isNetworkRoaming()) {
netType = 2;
} else {
netType = 2;
}
}
return netType;
}
具體使用
在app的build.gradle中引入上述lib庫酵使,在app的Application中添加:
public class AnonyperApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
NetWorkMonitorManager.getInstance().init(this);
}
}
然后在使用的地方:
@Override
protected void onStart() {
super.onStart();
NetWorkMonitorManager.getInstance().register(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
NetWorkMonitorManager.getInstance().unregister(this);
}
//不加注解默認(rèn)監(jiān)聽所有的狀態(tài)口渔,方法名隨意穿撮,只需要參數(shù)是一個(gè)NetWorkState即可
//@NetWorkMonitor(monitorFilter = {NetWorkState.GPRS})//只接受網(wǎng)絡(luò)狀態(tài)變?yōu)镚PRS類型的消息
public void onNetWorkStateChange(NetWorkState netWorkState) {
Log.i("TAG", "onNetWorkStateChange >>> :" + netWorkState.name());
}
當(dāng)然悦穿,需要在manifest中添加網(wǎng)絡(luò)狀態(tài)變化監(jiān)聽的權(quán)限:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
關(guān)掉WIfi后log:
2019-06-11 19:45:34.825 13778-13809/com.anonyper.annotationapplication I/Anonyper >>>: onNetWorkStateChange >>> :NONE
2019-06-11 19:45:35.525 13778-13809/com.anonyper.annotationapplication I/Anonyper >>>: onNetWorkStateChange >>> :GPRS
以上咧党,我們就完成了一個(gè)網(wǎng)絡(luò)狀態(tài)變化監(jiān)聽的lib庫。