监听Android设备网络变化 - 亲测在华为Emui8.0 以及 Android原生5.1、7.1.2有效

1、本文章基于:监听Android设备网络变化 - 简书
2、其他的优秀开源库:GHdeng/NetMonitor: 使用广播监听网络变化更新UI , 这个库也很好,也推荐伙伴们去使用哈。
3、其他的优秀文章:[23]—— 10分钟让你实现在APP中对网络状态变化进行全局提示 - 简书

原理分析与源码介绍

0x01 目标

在实际开发中,我们不可避免地需要对请求错误进行处理,通常情况下,我们会这样去处理错误请求:

  1. 没有网络的情况下,提示用户网络连接不可用,引导用户打开网络或重新刷新等
  2. 有网络的情况下,则是客户端或服务端的错误,给用户相应的提示

如果针对第一种情况,我们需要在网络恢复的时候重新刷新数据或进行其他操作,又应该如何实现呢?以下就是我们的目标:
(1)、 监听Android设备网络状态
(2)、 在网络状态发生改变时,做出相应操作

在示例中,我们在网络状态发生变化时,显示当前网络变化的类型。

0x02 思路

在Android系统在网络变化的情况下,会发出 action 为 ConnectivityManager.CONNECTIVITY_ACTION 的系统广播,我们只需要注册 BroadcastReceiver 去监听该广播即可监听设备的网络变化情况。

那么,注册 BroadcastReceiver 是静态注册呢,还是动态注册呢?

  • 静态注册:通常来讲,退出应用后,该应用仍然能够接收到相应的广播
  • 动态注册:随着所在Context或应用被销毁后,不会收到相应的广播

注意:针对静态注册,这里是用“通常来讲”来修饰的,也就是说,存在特殊情况,即:存在即使使用静态注册,也不会收到相应的广播的情况:

Android3.1之后,系统为了加强了安全性控制,应用程序安装后或是(设置)应用管理中被强制关闭后处于stopped状态,在这种状态下接收不到任何广播,除非广播带有 FLAG_INCLUDE_STOPPED_PACKAGES 标志,而默认所有系统广播都是 FLAG_EXCLUDE_STOPPED_PACKAGES 的,所以就没法通过系统广播自启动了。 这其中就包括 ConnectivityManager.CONNECTIVITY_ACTION
关于这一块的内容,不是本篇重点,欲了解详情,请移步Android应用为何开机自启动、自启动失败原因

另外,Android 7.0 移除了三个隐式广播(Android 7.0 行为变更),其中就包括 ConnectivityManager.CONNECTIVITY_ACTION , 这意味着通过静态注册 BroadcastReceiver 来监听该广播的方式在 targetSdkVersion >= 24 版本上不再生效,如何解决这一问题请移步 Android 7.0 网络变化监听

所以,这里采取动态注册 BroadcastReceiver 的方式。那么,应该在哪里动态注册呢?这里有两种思路:

    • 定义 BroadcastReceiver 监听网络状态,并提供回调接口 NetStateChangeObserver 用以回调网络状态的变化
    • 抽象出 BaseActivity ,提供注册/取消注册 BroadcastReceiver 的方法,并实现 NetStateChangeObserver
    • 需要监听网络状态的 Activity 调用 BaseActivity 提供的方法即可
    • 定义 BroadcastReceiver 监听网络状态,并提供回调接口 NetStateChangeObserver 用以回调网络状态的变化,并在 BroadcastReceiver 中维护 NetStateChangeObserver 列表,当网络发生变化则通知这些 Observer ,实现回调。
    • Application 中注册/取消注册 BroadcastReceiver
    • 抽象 BaseActivity ,提供注册/取消注册 NetStateChangeObserver 观察者的方法, 并实现 NetStateChangeObserver
    • 需要监听网络状态的 Activity 调用 BaseActivity 提供的方法即可

上面的两种思路,比较重要的区别在于,第1种是在 Activity 中注册 BroadcastReceiver , 第2种是在 Application 中注册 BroadcastReceiver。前者需要多次注册 BroadcastReceiver 而后者只注册一次,所以在这里选择第2种思路。

0x03 实现 - 亲测在华为Emui8.0 以及 Android原生7.1.2有效

添加权限

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

定义网络类型

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;
    }
}

定义观察者

/**
 * 网络状态变化观察者
 */
public interface NetStateChangeObserver {

    void onNetDisconnected();

    void onNetConnected(NetworkType networkType);
}

实现 BroadcastReceiver

/**
 * 监听网络状态变化的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);
        }
    }

    /**
     * 注册网络监听
     */
    public static void registerReceiver(@NonNull Context context) {
        IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
        context.registerReceiver(InstanceHolder.INSTANCE, intentFilter);
    }

    /**
     * 取消网络监听
     */
    public static void unregisterReceiver(@NonNull Context context) {
        context.unregisterReceiver(InstanceHolder.INSTANCE);
    }

    /**
     * 注册网络变化Observer
     */
    public static void registerObserver(NetStateChangeObserver observer) {
        if (observer == null)
            return;
        if (!InstanceHolder.INSTANCE.mObservers.contains(observer)) {
            InstanceHolder.INSTANCE.mObservers.add(observer);
        }
    }

    /**
     * 取消网络变化Observer的注册
     */
    public static void unregisterObserver(NetStateChangeObserver observer) {
        if (observer == null)
            return;
        if (InstanceHolder.INSTANCE.mObservers == null)
            return;
        InstanceHolder.INSTANCE.mObservers.remove(observer);
    }

    /**
     * 通知所有的Observer网络状态变化
     */
    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 注册 BroadcastReceiver

public class AppContext extends Application{

    @Override
    public void onCreate() {
        super.onCreate();
        // 注册BroadcastReceiver
        NetStateChangeReceiver.registerReceiver(this);
    }

    @Override
    public void onTerminate() {
        super.onTerminate();
        // 取消BroadcastReceiver注册
        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);
        }
    }

    /**
     * 是否需要注册网络变化的Observer,如果不需要监听网络变化,则返回false;否则返回true.默认返回false
     */
    protected boolean needRegisterNetworkChangeObserver() {
        return false;
    }

    @Override
    public void onNetDisconnected() {
    }

    @Override
    public void onNetConnected(NetworkType networkType) {
    }
}

需要实现网络监听的 Activity 只需要复写 needRegisterNetworkChangeObserver 并返回 true ,并复写相关回调函数即可。


错误做法

  • 网络最新的一片文章便是“觉得低于Android N(24)的就使用BroadcastReceiver,高于Android N(24)
    就是用JobScheduler”(理由:Android 7.0 为了后台优化,推荐使用 JobScheduler 代替 BroadcastReceiver 来监听网络变化。),

    比如下面这种写法,下面这种写法在华为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;
        }


        //亲测在华为Emui8.0 以及 Android原生7.1.2无效
        //https://www.jianshu.com/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);
                ///网络不可用的情况下的方法
            }

            @Override
            public void onAvailable(Network network) {
                super.onAvailable(network);
                ///网络可用的情况下的方法
                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();
            }
        }

    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,392评论 25 707
  • 版权声明:本文源自简书tianma,转载请务必注明出处: http://www.jianshu.com/p/2f1...
    tianma阅读 6,265评论 5 10
  • 【Android 广播】 BroadcastReceiver简介 BroadcastReceiver(广播接收器)...
    Rtia阅读 3,380评论 1 17
  • 0.Android手机操作系统的四层架构? Applications , Application Framewor...
    lucas777阅读 7,817评论 0 16
  • 师父原文: <曾国藩的读经窍诀>对于经典,熟读强识是非常重要的。古人说:读书百遍,其义自现。这个口诀尤其适用于经典...
    真承阅读 367评论 0 5