監(jiān)聽Android設(shè)備網(wǎng)絡(luò)變化 - 親測(cè)在華為Emui8.0 以及 Android原生5.1、7.1.2有效

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)求:

  1. 沒有網(wǎng)絡(luò)的情況下,提示用戶網(wǎng)絡(luò)連接不可用,引導(dǎo)用戶打開網(wǎng)絡(luò)或重新刷新等
  2. 有網(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();
            }
        }

    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,039評(píng)論 25 709
  • 【Android 廣播】 BroadcastReceiver簡(jiǎn)介 BroadcastReceiver(廣播接收器)...
    Rtia閱讀 3,550評(píng)論 1 17
  • 0.Android手機(jī)操作系統(tǒng)的四層架構(gòu)? Applications , Application Framewor...
    lucas777閱讀 8,146評(píng)論 0 16
  • 師父原文: <曾國(guó)藩的讀經(jīng)竅訣>對(duì)于經(jīng)典,熟讀強(qiáng)識(shí)是非常重要的。古人說:讀書百遍,其義自現(xiàn)。這個(gè)口訣尤其適用于經(jīng)典...
    真承閱讀 454評(píng)論 0 5
  • 被包養(yǎng)啦 1、早中晚三餐都被包啦,我選擇接受,哈哈哈哈,好開心!感謝各位仙家! 2、后海散步打卡,呼吸新鮮空氣,鏈...
    張艾雯閱讀 201評(píng)論 0 0

友情鏈接更多精彩內(nèi)容