1、本文章基于:監(jiān)聽Android設(shè)備網(wǎng)絡(luò)變化 - 簡(jiǎn)書
2、其他的優(yōu)秀開源庫(kù):GHdeng/NetMonitor: 使用廣播監(jiān)聽網(wǎng)絡(luò)變化更新UI , 這個(gè)庫(kù)也很好,也推薦伙伴們?nèi)ナ褂霉?br> 3、其他的優(yōu)秀文章:[23]—— 10分鐘讓你實(shí)現(xiàn)在APP中對(duì)網(wǎng)絡(luò)狀態(tài)變化進(jìn)行全局提示 - 簡(jiǎn)書
原理分析與源碼介紹
0x01 目標(biāo)
在實(shí)際開發(fā)中,我們不可避免地需要對(duì)請(qǐng)求錯(cuò)誤進(jìn)行處理,通常情況下,我們會(huì)這樣去處理錯(cuò)誤請(qǐng)求:
- 沒有網(wǎng)絡(luò)的情況下,提示用戶網(wǎng)絡(luò)連接不可用,引導(dǎo)用戶打開網(wǎng)絡(luò)或重新刷新等
- 有網(wǎng)絡(luò)的情況下,則是客戶端或服務(wù)端的錯(cuò)誤,給用戶相應(yīng)的提示
如果針對(duì)第一種情況,我們需要在網(wǎng)絡(luò)恢復(fù)的時(shí)候重新刷新數(shù)據(jù)或進(jìn)行其他操作,又應(yīng)該如何實(shí)現(xiàn)呢?以下就是我們的目標(biāo):
(1)、 監(jiān)聽Android設(shè)備網(wǎng)絡(luò)狀態(tài)
(2)、 在網(wǎng)絡(luò)狀態(tài)發(fā)生改變時(shí),做出相應(yīng)操作
在示例中,我們?cè)诰W(wǎng)絡(luò)狀態(tài)發(fā)生變化時(shí),顯示當(dāng)前網(wǎng)絡(luò)變化的類型。
0x02 思路
在Android系統(tǒng)在網(wǎng)絡(luò)變化的情況下,會(huì)發(fā)出 action 為 ConnectivityManager.CONNECTIVITY_ACTION 的系統(tǒng)廣播,我們只需要注冊(cè) BroadcastReceiver 去監(jiān)聽該廣播即可監(jiān)聽設(shè)備的網(wǎng)絡(luò)變化情況。
那么,注冊(cè) BroadcastReceiver 是靜態(tài)注冊(cè)呢,還是動(dòng)態(tài)注冊(cè)呢?
- 靜態(tài)注冊(cè):通常來講,退出應(yīng)用后,該應(yīng)用仍然能夠接收到相應(yīng)的廣播
- 動(dòng)態(tài)注冊(cè):隨著所在Context或應(yīng)用被銷毀后,不會(huì)收到相應(yīng)的廣播
注意:針對(duì)靜態(tài)注冊(cè),這里是用“通常來講”來修飾的,也就是說,存在特殊情況,即:存在即使使用靜態(tài)注冊(cè),也不會(huì)收到相應(yīng)的廣播的情況:
Android3.1之后,系統(tǒng)為了加強(qiáng)了安全性控制,應(yīng)用程序安裝后或是(設(shè)置)應(yīng)用管理中被強(qiáng)制關(guān)閉后處于stopped狀態(tài),在這種狀態(tài)下接收不到任何廣播,除非廣播帶有
FLAG_INCLUDE_STOPPED_PACKAGES標(biāo)志,而默認(rèn)所有系統(tǒng)廣播都是FLAG_EXCLUDE_STOPPED_PACKAGES的,所以就沒法通過系統(tǒng)廣播自啟動(dòng)了。 這其中就包括ConnectivityManager.CONNECTIVITY_ACTION。
關(guān)于這一塊的內(nèi)容,不是本篇重點(diǎn),欲了解詳情,請(qǐng)移步Android應(yīng)用為何開機(jī)自啟動(dòng)、自啟動(dòng)失敗原因
另外,Android 7.0 移除了三個(gè)隱式廣播(Android 7.0 行為變更),其中就包括 ConnectivityManager.CONNECTIVITY_ACTION , 這意味著通過靜態(tài)注冊(cè) BroadcastReceiver 來監(jiān)聽該廣播的方式在 targetSdkVersion >= 24 版本上不再生效,如何解決這一問題請(qǐng)移步 Android 7.0 網(wǎng)絡(luò)變化監(jiān)聽。
所以,這里采取動(dòng)態(tài)注冊(cè) BroadcastReceiver 的方式。那么,應(yīng)該在哪里動(dòng)態(tài)注冊(cè)呢?這里有兩種思路:
- 定義
BroadcastReceiver監(jiān)聽網(wǎng)絡(luò)狀態(tài),并提供回調(diào)接口NetStateChangeObserver用以回調(diào)網(wǎng)絡(luò)狀態(tài)的變化 - 抽象出
BaseActivity,提供注冊(cè)/取消注冊(cè)BroadcastReceiver的方法,并實(shí)現(xiàn)NetStateChangeObserver - 需要監(jiān)聽網(wǎng)絡(luò)狀態(tài)的
Activity調(diào)用BaseActivity提供的方法即可
- 定義
- 定義
BroadcastReceiver監(jiān)聽網(wǎng)絡(luò)狀態(tài),并提供回調(diào)接口NetStateChangeObserver用以回調(diào)網(wǎng)絡(luò)狀態(tài)的變化,并在BroadcastReceiver中維護(hù)NetStateChangeObserver列表,當(dāng)網(wǎng)絡(luò)發(fā)生變化則通知這些 Observer ,實(shí)現(xiàn)回調(diào)。 - 在
Application中注冊(cè)/取消注冊(cè)BroadcastReceiver - 抽象
BaseActivity,提供注冊(cè)/取消注冊(cè)NetStateChangeObserver觀察者的方法, 并實(shí)現(xiàn)NetStateChangeObserver - 需要監(jiān)聽網(wǎng)絡(luò)狀態(tài)的
Activity調(diào)用BaseActivity提供的方法即可
- 定義
上面的兩種思路,比較重要的區(qū)別在于,第1種是在 Activity 中注冊(cè) BroadcastReceiver , 第2種是在 Application 中注冊(cè) BroadcastReceiver。前者需要多次注冊(cè) BroadcastReceiver 而后者只注冊(cè)一次,所以在這里選擇第2種思路。
0x03 實(shí)現(xiàn) - 親測(cè)在華為Emui8.0 以及 Android原生7.1.2有效
添加權(quán)限
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
定義網(wǎng)絡(luò)類型
public enum NetworkType {
NETWORK_WIFI("WiFi"),
NETWORK_4G("4G"),
NETWORK_3G("3G"),
NETWORK_2G("2G"),
NETWORK_UNKNOWN("Unknown"),
NETWORK_NO("No network");
private String desc;
NetworkType(String desc) {
this.desc = desc;
}
@Override
public String toString() {
return desc;
}
}
定義觀察者
/**
* 網(wǎng)絡(luò)狀態(tài)變化觀察者
*/
public interface NetStateChangeObserver {
void onNetDisconnected();
void onNetConnected(NetworkType networkType);
}
實(shí)現(xiàn) BroadcastReceiver
/**
* 監(jiān)聽網(wǎng)絡(luò)狀態(tài)變化的BroadcastReceiver
*/
public class NetStateChangeReceiver extends BroadcastReceiver {
private static class InstanceHolder {
private static final NetStateChangeReceiver INSTANCE = new NetStateChangeReceiver();
}
private List<NetStateChangeObserver> mObservers = new ArrayList<>();
public NetStateChangeReceiver() {
}
@Override
public void onReceive(Context context, Intent intent) {
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
NetworkType networkType = NetworkUtils.getNetworkType(context);
notifyObservers(networkType);
}
}
/**
* 注冊(cè)網(wǎng)絡(luò)監(jiān)聽
*/
public static void registerReceiver(@NonNull Context context) {
IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
context.registerReceiver(InstanceHolder.INSTANCE, intentFilter);
}
/**
* 取消網(wǎng)絡(luò)監(jiān)聽
*/
public static void unregisterReceiver(@NonNull Context context) {
context.unregisterReceiver(InstanceHolder.INSTANCE);
}
/**
* 注冊(cè)網(wǎng)絡(luò)變化Observer
*/
public static void registerObserver(NetStateChangeObserver observer) {
if (observer == null)
return;
if (!InstanceHolder.INSTANCE.mObservers.contains(observer)) {
InstanceHolder.INSTANCE.mObservers.add(observer);
}
}
/**
* 取消網(wǎng)絡(luò)變化Observer的注冊(cè)
*/
public static void unregisterObserver(NetStateChangeObserver observer) {
if (observer == null)
return;
if (InstanceHolder.INSTANCE.mObservers == null)
return;
InstanceHolder.INSTANCE.mObservers.remove(observer);
}
/**
* 通知所有的Observer網(wǎng)絡(luò)狀態(tài)變化
*/
private void notifyObservers(NetworkType networkType) {
if (networkType == NetworkType.NETWORK_NO) {
for(NetStateChangeObserver observer : mObservers) {
observer.onNetDisconnected();
}
} else {
for(NetStateChangeObserver observer : mObservers) {
observer.onNetConnected(networkType);
}
}
}
}
Application 注冊(cè) BroadcastReceiver
public class AppContext extends Application{
@Override
public void onCreate() {
super.onCreate();
// 注冊(cè)BroadcastReceiver
NetStateChangeReceiver.registerReceiver(this);
}
@Override
public void onTerminate() {
super.onTerminate();
// 取消BroadcastReceiver注冊(cè)
NetStateChangeReceiver.unregisterReceiver(this);
}
}
BaseActivity 抽取
public class BaseActivity extends AppCompatActivity implements NetStateChangeObserver {
@Override
protected void onResume() {
super.onResume();
if (needRegisterNetworkChangeObserver()) {
NetStateChangeReceiver.registerObserver(this);
}
}
@Override
protected void onStop() {
super.onStop();
if (needRegisterNetworkChangeObserver()) {
NetStateChangeReceiver.unregisterObserver(this);
}
}
/**
* 是否需要注冊(cè)網(wǎng)絡(luò)變化的Observer,如果不需要監(jiān)聽網(wǎng)絡(luò)變化,則返回false;否則返回true.默認(rèn)返回false
*/
protected boolean needRegisterNetworkChangeObserver() {
return false;
}
@Override
public void onNetDisconnected() {
}
@Override
public void onNetConnected(NetworkType networkType) {
}
}
需要實(shí)現(xiàn)網(wǎng)絡(luò)監(jiān)聽的 Activity 只需要復(fù)寫 needRegisterNetworkChangeObserver 并返回 true ,并復(fù)寫相關(guān)回調(diào)函數(shù)即可。
錯(cuò)誤做法
-
網(wǎng)絡(luò)最新的一片文章便是“覺得低于Android N(24)的就使用BroadcastReceiver,高于Android N(24)
就是用JobScheduler”(理由:Android 7.0 為了后臺(tái)優(yōu)化,推薦使用 JobScheduler 代替 BroadcastReceiver 來監(jiān)聽網(wǎng)絡(luò)變化。),
比如下面這種寫法,下面這種寫法在華為M5上不起作用(M5 Emui8.0基于Android 8.0):
@SuppressLint("NewApi")
private void networkCallback() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
mNetReceiver = new NetReceiver();
IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
registerReceiver(mNetReceiver, intentFilter);
return;
}
//親測(cè)在華為Emui8.0 以及 Android原生7.1.2無效
//http://www.itdecent.cn/p/83da8f88ce9c
final ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivityManager == null) {
return;
}
connectivityManager.requestNetwork(new NetworkRequest.Builder().build(), new ConnectivityManager.NetworkCallback() {
@Override
public void onLost(Network network) {
super.onLost(network);
///網(wǎng)絡(luò)不可用的情況下的方法
}
@Override
public void onAvailable(Network network) {
super.onAvailable(network);
///網(wǎng)絡(luò)可用的情況下的方法
loadWebUrl();
}
});
}
public class NetReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (manager == null) {
return;
}
NetworkInfo mobileInfo = manager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
NetworkInfo wifiInfo = manager.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
if (wifiInfo != null && (wifiInfo.isConnected() || mobileInfo.isConnected())) {
loadWebUrl();
}
}
}