Android即時(shí)網(wǎng)絡(luò)監(jiān)聽(tīng)(一)-BroadcastReceiver

通常我們?cè)谶M(jìn)行網(wǎng)絡(luò)請(qǐng)求之前魄眉,會(huì)先進(jìn)行網(wǎng)絡(luò)狀態(tài)判斷争占,再?zèng)Q定是否進(jìn)行網(wǎng)絡(luò)請(qǐng)求燃逻,常用方法如下:

/**
* 判斷網(wǎng)絡(luò)是否可用
*
* @return true/false
*/
@SuppressLint("MissingPermission")
public static boolean isNetworkAvailable() {
    ConnectivityManager connMgr = (ConnectivityManager) NetworkListener.getInstance().getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
    if (connMgr == null) {
        return false;
    }
    NetworkInfo[] infos = connMgr.getAllNetworkInfo();
    if (infos != null) {
        for (NetworkInfo info : infos) {
            if (info.getState() == NetworkInfo.State.CONNECTED) {
                return true;
            }
        }
    }
    return false;
}
/**
* 獲取當(dāng)前網(wǎng)絡(luò)類(lèi)型
*
* @return NetType
*/
@SuppressLint("MissingPermission")
public static NetType getNetType() {
    ConnectivityManager connMgr = (ConnectivityManager) NetworkListener.getInstance().getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
    if (connMgr == null) {
        return NetType.NONE;
    }
    NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
    if (networkInfo == null) {
        return NetType.NONE;
    }
    int nType = networkInfo.getType();
    if (nType == ConnectivityManager.TYPE_MOBILE) {
        if ("cmnet".equals(networkInfo.getExtraInfo().toLowerCase())) {
            return NetType.CMNET;
        } else {
            return NetType.CMWAP;
        }
    } else if (nType == ConnectivityManager.TYPE_WIFI) {
        return NetType.WIFI;
    }
    return NetType.NONE;
}

但這種做法無(wú)法做到即時(shí)網(wǎng)絡(luò)監(jiān)聽(tīng),例如在播放視頻或下載文件過(guò)程中臂痕,網(wǎng)絡(luò)狀態(tài)發(fā)生變化伯襟,我們無(wú)法做到即時(shí)處理,解決方式有2種:
1握童、使用廣播方式
2姆怪、使用ConnectivityManager.NetworkCallback方式
本次我們先使用廣播監(jiān)聽(tīng)的方式進(jìn)行網(wǎng)絡(luò)變化監(jiān)聽(tīng)。
我們知道澡绩,網(wǎng)絡(luò)狀態(tài)發(fā)生變化的時(shí)候稽揭,系統(tǒng)會(huì)發(fā)出android.net.CONNECTIVITY_CHANGE廣播,所以我們可以用監(jiān)聽(tīng)此廣播的方式來(lái)判斷網(wǎng)絡(luò)連接:
1肥卡、權(quán)限申請(qǐng)

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

2溪掀、創(chuàng)建常量

//網(wǎng)絡(luò)連接改變廣播
public static final String ANDROID_NET_CHANGE_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";

3、定義枚舉類(lèi)步鉴,表示網(wǎng)絡(luò)狀態(tài):

public enum NetType {
    //任意網(wǎng)絡(luò)
    AUTO,
    //WIFI
    WIFI,
    CMNET,
    //手機(jī)上網(wǎng)
    CMWAP,
    //無(wú)網(wǎng)絡(luò)
    NONE
}

4揪胃、定義網(wǎng)絡(luò)監(jiān)聽(tīng)接口

public interface NetChangeListener {
    /**
     * 已連接
     * @param netType NetType
     */
    void onConnect(NetType netType);

    /**
     * 連接斷開(kāi)
     */
    void onDisConnect();
}

5、創(chuàng)建廣播接收者

public class NetworkStateReceiver extends BroadcastReceiver {
    private static final String TAG = "NetworkStateReceiver";
    private NetType netType;//網(wǎng)絡(luò)類(lèi)型
    private NetChangeListener listener;

    public NetworkStateReceiver() {
        //初始化網(wǎng)絡(luò)連接狀態(tài)
        this.netType = NetType.NONE;
    }

    public void setListener(NetChangeListener listener) {
        this.listener = listener;
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent == null || intent.getAction() == null) {
            Log.e(TAG, "onReceive: 異常");
            return;
        }
        if (intent.getAction().equals(Constants.ANDROID_NET_CHANGE_ACTION)) {
            Log.d(TAG, "onReceive: 網(wǎng)絡(luò)發(fā)生變化");
            //獲取當(dāng)前聯(lián)網(wǎng)的網(wǎng)絡(luò)類(lèi)型
            netType = NetworkUtils.getNetType();
            if (NetworkUtils.isNetworkAvailable()) {
                Log.d(TAG, "onReceive: 網(wǎng)絡(luò)連接成功");
                if (listener != null) {
                    listener.onConnect(netType);
                }
            } else {
                Log.e(TAG, "onReceive: 網(wǎng)絡(luò)連接失敗");
                if (listener != null) {
                    listener.onDisConnect();
                }
            }
        }
    }
}

6氛琢、定義NetworkListener類(lèi)(單例)用于初始化監(jiān)聽(tīng)

public class NetworkListener {
    private Context context;
    private NetworkStateReceiver receiver;

    /**
     * 私有化構(gòu)造方法
     */
    private NetworkListener() {
        receiver = new NetworkStateReceiver();
    }

    private static final SingletonTemplate<NetworkListener> INSTANCE = new SingletonTemplate<NetworkListener>() {
        @Override
        protected NetworkListener create() {
            return new NetworkListener();
        }
    };

    public static NetworkListener getInstance() {
        return INSTANCE.get();
    }

    public Context getContext() {
        return context;
    }

    public void setListener(NetChangeListener listener) {
        receiver.setListener(listener);
    }

    /**
     * 初始化
     *
     * @param context context
     */
    @SuppressLint("MissingPermission")
    public void init(Context context) {
        this.context = context;
        IntentFilter filter = new IntentFilter();
        filter.addAction(Constants.ANDROID_NET_CHANGE_ACTION);
        context.registerReceiver(receiver, filter);
    }
}

7喊递、使用:
Application中初始化:

NetworkListener.getInstance().init(this);

在需要監(jiān)聽(tīng)的Activity中實(shí)現(xiàn)NetChangeListener接口,并在onCreate()中初始化NetworkListener艺沼,如下:

public class MainActivity extends AppCompatActivity implements NetChangeListener {
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        NetworkListener.getInstance().setListener(this);
    }

    @Override
    public void onConnect(NetType netType) {
        Log.d(TAG, "onConnect: 網(wǎng)絡(luò)連接成功:狀態(tài)_" + netType.name());
    }

    @Override
    public void onDisConnect() {
        Log.e(TAG, "onDisConnect: 網(wǎng)絡(luò)連接斷開(kāi)");
    }
}

通過(guò)以上代碼册舞,我們可以實(shí)現(xiàn)在APP運(yùn)行過(guò)程中實(shí)時(shí)監(jiān)聽(tīng)網(wǎng)絡(luò)連接狀態(tài),此方式使用廣播監(jiān)聽(tīng)結(jié)合接口回調(diào)實(shí)現(xiàn)障般。
但此種方式也有個(gè)缺點(diǎn):我們每次需要進(jìn)行網(wǎng)絡(luò)監(jiān)聽(tīng)都需要實(shí)現(xiàn)NetChangeListener這個(gè)接口调鲸,并且如果我們只需要對(duì)某一個(gè)網(wǎng)絡(luò)連接如WIFI或者GPRS進(jìn)行處理的話盛杰,也需要寫(xiě)很多判斷代碼,會(huì)顯得比較繁瑣藐石,那么有沒(méi)有一種更好的實(shí)現(xiàn)方式呢即供,當(dāng)然有!我們可以借助注解的方式實(shí)現(xiàn)監(jiān)聽(tīng)于微,下面我們來(lái)看一下具體操作:
1逗嫡、自定義注解

@Target(ElementType.METHOD)//作用在方法之上
@Retention(RetentionPolicy.RUNTIME)//jvm運(yùn)行時(shí),通過(guò)反射獲取該注解的值
public @interface Network {
    NetType netType();
}

2株依、每個(gè)類(lèi)中可能有多個(gè)注解方法驱证,那么我們需要將APP里所有添加注解的類(lèi)放入一個(gè)全局的集合中

private Map<Object,List<MethodManager>> networkMap;

其中Key是類(lèi)(Activity/Fragment),Value是類(lèi)中使用了注解的方法Method恋腕。MethodManager保存了符合要求的網(wǎng)絡(luò)監(jiān)聽(tīng)注解方法抹锄,可以根據(jù)方法的參數(shù)類(lèi)型、網(wǎng)絡(luò)類(lèi)型來(lái)確定:

/**
 * 保存符合要求的網(wǎng)絡(luò)監(jiān)聽(tīng)注解方法
 * Created by ywd on 2019/6/12.
 */
public class MethodManager {
    //方法的類(lèi)型(NetType)
    private Class<?> type;
    //網(wǎng)絡(luò)類(lèi)型(netType = NetType.WIFI)
    private NetType netType;
    //需要執(zhí)行的方法(自動(dòng)執(zhí)行)
    private Method method;

    public MethodManager(Class<?> type, NetType netType, Method method) {
        this.type = type;
        this.netType = netType;
        this.method = method;
    }

    public Class<?> getType() {
        return type;
    }

    public void setType(Class<?> type) {
        this.type = type;
    }

    public NetType getNetType() {
        return netType;
    }

    public void setNetType(NetType netType) {
        this.netType = netType;
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }
}

3荠藤、定義了集合之后伙单,我們需要為Activity/Fragment添加注冊(cè)和移除方法,該方法只在NetworkListener類(lèi)中提供對(duì)外方法哈肖,核心實(shí)現(xiàn)需要在Receiver中:

/**
 * 網(wǎng)絡(luò)監(jiān)聽(tīng)
 * Created by ywd on 2019/6/5.
 */

public class NetworkListenerWithAnno {
    private Context context;
    private NetworkStateReceiverWithAnno receiver;

    /**
     * 私有化構(gòu)造方法
     */
    private NetworkListenerWithAnno() {
        receiver = new NetworkStateReceiverWithAnno();
    }

    private static final SingletonTemplate<NetworkListenerWithAnno> INSTANCE = new SingletonTemplate<NetworkListenerWithAnno>() {
        @Override
        protected NetworkListenerWithAnno create() {
            return new NetworkListenerWithAnno();
        }
    };

    public static NetworkListenerWithAnno getInstance() {
        return INSTANCE.get();
    }

    public Context getContext() {
        return context;
    }

    /**
     * 初始化
     *
     * @param context context
     */
    @SuppressLint("MissingPermission")
    public void init(Context context) {
        this.context = context;
        IntentFilter filter = new IntentFilter();
        filter.addAction(Constants.ANDROID_NET_CHANGE_ACTION);
        context.registerReceiver(receiver, filter);
    }

    /**
     * 注冊(cè)
     *
     * @param observer 觀察者(Activity/Fragment)
     */
    public void registerObserver(Object observer) {
        receiver.registerObserver(observer);
    }

    /**
     * 解除注冊(cè)
     *
     * @param observer 觀察者(Activity/Fragment)
     */
    public void unRegisterObserver(Object observer) {
        receiver.unRegisterObserver(observer);
    }
}

Receiver中的實(shí)現(xiàn):

/**
* 注冊(cè)
* 獲取Activity/Fragment中的注解方法并添加到networkList中
*
* @param observer 觀察者(Activity/Fragment)
*/
public void registerObserver(Object observer) {
    List<MethodManager> methodList = networkMap.get(observer);
    if (methodList == null) {
        methodList = findAnnotationMethod(observer);
        networkMap.put(observer, methodList);
    }
}

/**
* 解除注冊(cè)
*
* @param observer 觀察者(Activity/Fragment)
*/
public void unRegisterObserver(Object observer) {
    if (!networkMap.isEmpty()) {
        networkMap.remove(observer);
    }
    Log.d(TAG, "unRegisterObserver:" + observer.getClass().getName() + "注銷(xiāo)成功");
}

findAnnotationMethod(observer)方法為獲取Activity/Fragment中的注解方法吻育,可以在此類(lèi)中進(jìn)行過(guò)濾,具體實(shí)現(xiàn)如下:

/**
* 獲取Activity/Fragment中的注解方法
* @param observer 觀察者(Activity/Fragment)
* @return
*/
private List<MethodManager> findAnnotationMethod(Object observer) {
    List<MethodManager> methodList = new ArrayList<>();
    Class<?> clazz = observer.getClass();
    //獲取Activity中的所有公有方法淤井,包括其父類(lèi)及其實(shí)現(xiàn)的接口的方法布疼,因方法數(shù)過(guò)多,此處使用getDeclaredMethods()
    //Method[] methods = clazz.getMethods();
    //獲取Activity中的所有方法庄吼,包括公共缎除、保護(hù)、默認(rèn)(包)訪問(wèn)和私有方法,當(dāng)然也包括它所實(shí)現(xiàn)接口的方法总寻,但不包括繼承的方法
    Method[] methods = clazz.getDeclaredMethods();
    for (Method method : methods) {
        //獲取方法的注解
        Network network = method.getAnnotation(Network.class);
        if (network == null) {
            continue;
        }
        //方法參數(shù)校驗(yàn)
        Class<?>[] parameterTypes = method.getParameterTypes();
        if (parameterTypes.length != 1) {
            throw new RuntimeException(method.getName() + "方法有且只有一個(gè)參數(shù)");
        }
        //參數(shù)類(lèi)型校驗(yàn)
        String name = parameterTypes[0].getName();
        if (name.equals(NetType.class.getName())) {
            MethodManager methodManager = new MethodManager(parameterTypes[0], network.netType(), method);
            methodList.add(methodManager);
        }
    }
    return methodList;
}

獲取到Activity/Fragment中的所有注解方法之后器罐,就可以在網(wǎng)絡(luò)發(fā)生變化時(shí)進(jìn)行事件分發(fā)了,接下來(lái)我們?cè)趏nReceive中進(jìn)行處理:

if (intent == null || intent.getAction() == null) {
    Log.e(TAG, "onReceive: 異常");
    return;
}
if (intent.getAction().equals(Constants.ANDROID_NET_CHANGE_ACTION)) {
    Log.d(TAG, "onReceive: 網(wǎng)絡(luò)發(fā)生變化");
    netType = NetworkUtils.getNetType();
    if (NetworkUtils.isNetworkAvailable()) {
        Log.d(TAG, "onReceive: 網(wǎng)絡(luò)連接成功");
    } else {
        Log.e(TAG, "onReceive: 網(wǎng)絡(luò)連接失敗");
    }
    post(netType);
}

post方法實(shí)現(xiàn):

/**
* 分發(fā)
*
* @param netType NetType
*/
private void post(NetType netType) {
    //Activity集合
    Set<Object> set = networkMap.keySet();
    for (Object observer : set) {
        //所有注解的方法
        List<MethodManager> methodList = networkMap.get(observer);
        if (methodList != null) {
            for (MethodManager methodManager : methodList) {
                //兩者參數(shù)比較
                if (methodManager.getType().isAssignableFrom(netType.getClass())) {
                    switch (methodManager.getNetType()) {
                        case AUTO:
                            invoke(methodManager, observer, netType);
                            break;
                        case WIFI:
                            if (netType == NetType.WIFI || netType == NetType.NONE) {
                                invoke(methodManager, observer, netType);
                            }
                            break;
                        case CMWAP:
                            if (netType == NetType.CMWAP || netType == NetType.NONE) {
                                invoke(methodManager, observer, netType);
                            }
                            break;
                        case CMNET:
                            if (netType == NetType.CMNET || netType == NetType.NONE) {
                                invoke(methodManager, observer, netType);
                            }
                            break;
                    }
                }
            }
        }
    }
}

invoke方法:

/**
 * 在Activity中執(zhí)行方法渐行,參數(shù)為NetType
 *
 * @param methodManager
 * @param observer
 * @param netType
 */
private void invoke(MethodManager methodManager, Object observer, NetType netType) {
    try {
        methodManager.getMethod().invoke(observer, netType);
    } catch (IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
    }
}

使用:

@Network(netType = NetType.WIFI)
public void onNetChanged(NetType netType) {
    Log.d(TAG, "onNetChanged: 網(wǎng)絡(luò)發(fā)生改變" + netType.name());
}

可以看到轰坊,通過(guò)反射+注解形式,在使用上更加簡(jiǎn)潔祟印,同時(shí)相比接口回調(diào)形式更加解耦肴沫。
注意:Android7.0為了提高用戶(hù)體驗(yàn)和提升設(shè)備性能移除了這個(gè)隱式廣播,所以利用靜態(tài)注冊(cè)的方式是不能接收到該廣播的蕴忆,如果需要使用廣播方式監(jiān)聽(tīng)網(wǎng)絡(luò)變化可以使用動(dòng)態(tài)注冊(cè)的方式進(jìn)行接收颤芬。
代碼詳見(jiàn)Demo
PS:本文為某平臺(tái)公開(kāi)課筆記,在此記錄下,文中實(shí)現(xiàn)還可以再次進(jìn)行優(yōu)化站蝠。
另附使用ConnectivityManager.NetworkCallback方式實(shí)現(xiàn)網(wǎng)絡(luò)監(jiān)聽(tīng):
詳見(jiàn)
Android即時(shí)網(wǎng)絡(luò)監(jiān)聽(tīng)(二)-ConnectivityManager.NetworkCallback

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末汰具,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子菱魔,更是在濱河造成了極大的恐慌留荔,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件澜倦,死亡現(xiàn)場(chǎng)離奇詭異聚蝶,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)藻治,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén)碘勉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人栋艳,你說(shuō)我怎么就攤上這事恰聘【涓鳎” “怎么了吸占?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,711評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)凿宾。 經(jīng)常有香客問(wèn)我矾屯,道長(zhǎng),這世上最難降的妖魔是什么初厚? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,380評(píng)論 1 293
  • 正文 為了忘掉前任件蚕,我火速辦了婚禮,結(jié)果婚禮上产禾,老公的妹妹穿的比我還像新娘排作。我一直安慰自己,他們只是感情好亚情,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布妄痪。 她就那樣靜靜地躺著,像睡著了一般楞件。 火紅的嫁衣襯著肌膚如雪衫生。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,301評(píng)論 1 301
  • 那天土浸,我揣著相機(jī)與錄音罪针,去河邊找鬼。 笑死黄伊,一個(gè)胖子當(dāng)著我的面吹牛泪酱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼墓阀,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼愈腾!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起岂津,我...
    開(kāi)封第一講書(shū)人閱讀 39,008評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤虱黄,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后吮成,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體橱乱,經(jīng)...
    沈念sama閱讀 45,443評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評(píng)論 3 334
  • 正文 我和宋清朗相戀三年粱甫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了泳叠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,795評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡茶宵,死狀恐怖危纫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情乌庶,我是刑警寧澤种蝶,帶...
    沈念sama閱讀 35,501評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站瞒大,受9級(jí)特大地震影響螃征,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜透敌,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評(píng)論 3 328
  • 文/蒙蒙 一盯滚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧酗电,春花似錦魄藕、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,731評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至荷荤,卻和暖如春退渗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蕴纳。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,865評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工会油, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人古毛。 一個(gè)月前我還...
    沈念sama閱讀 47,899評(píng)論 2 370
  • 正文 我出身青樓翻翩,卻偏偏與公主長(zhǎng)得像都许,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嫂冻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評(píng)論 2 354