27、WiFi广播与动态注册:监听WiFi状态变化

做Android WiFi开发,绕不开一个核心机制——广播。说白了,系统把WiFi的状态变化通过广播告诉你,你收到后做出响应。比如WiFi开关打开了、连接上了一个热点、信号变弱了……这些都是通过广播传递的。

我个人习惯用动态注册的方式监听这些广播。为什么?因为灵活,而且能避免很多坑。今天我们就来聊聊WiFi相关的两个核心广播,以及如何正确地注册和解注册。

两个核心广播:WIFI_STATE_CHANGED_ACTION 和 NETWORK_STATE_CHANGED_ACTION

Android系统定义了很多WiFi相关的广播,但日常开发中,这两个用得最多:

广播Action 含义 携带的Extra信息
WIFI_STATE_CHANGED_ACTION WiFi硬件状态变化(开启/关闭/错误等) EXTRA_WIFI_STATE:新状态
EXTRA_PREVIOUS_WIFI_STATE:旧状态
NETWORK_STATE_CHANGED_ACTION WiFi网络连接状态变化(连接/断开/认证中) EXTRA_NETWORK_INFO:NetworkInfo对象
EXTRA_BSSID:当前连接的BSSID

嗯,这里要注意:WIFI_STATE_CHANGED_ACTION 是告诉你WiFi模块本身的状态,比如用户去设置里打开了WiFi开关。而 NETWORK_STATE_CHANGED_ACTION 是告诉你网络连接的情况,比如连上了某个路由器,或者断开了。

我在项目中遇到过一个问题:有同事只监听 NETWORK_STATE_CHANGED_ACTION,结果WiFi开关关闭时,他收不到任何通知,导致UI一直显示“已连接”。这就是没搞清楚两个广播的区别。

动态注册 vs 静态注册

Android的广播注册有两种方式:

  • 静态注册:在AndroidManifest.xml里声明。从Android 8.0开始,大部分隐式广播都不支持静态注册了。WiFi相关的广播正好属于被限制的那一类。
  • 动态注册:在代码里通过 registerReceiver 注册,在 onPauseonDestroy 里解注册。

说白了,现在做WiFi开发,你只能用动态注册。静态注册不仅可能收不到广播,还会被系统直接忽略。我建议你养成习惯——所有WiFi广播都用动态注册。

动态注册的完整流程

流程其实很简单,就三步:

  1. 创建 BroadcastReceiver 子类,实现 onReceive 方法。
  2. 在合适的时机(比如 onResume)调用 registerReceiver
  3. 在合适的时机(比如 onPause)调用 unregisterReceiver

你想想看,如果不解注册会怎样?Activity销毁了但Receiver还在,系统会一直持有你的Context引用,导致内存泄漏。我曾经排查过一个内存泄漏问题,查了半天,最后发现就是广播没解注册。

代码示例:监听WiFi状态变化

下面是一个完整的示例,演示如何动态注册并监听WiFi开关状态和网络连接状态:

public class WifiBroadcastReceiver extends BroadcastReceiver {

    private static final String TAG = "WifiReceiver";

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (action == null) return;

        // 处理WiFi开关状态变化
        if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
            int wifiState = intent.getIntExtra(
                WifiManager.EXTRA_WIFI_STATE,
                WifiManager.WIFI_STATE_UNKNOWN
            );
            handleWifiStateChanged(wifiState);
        }

        // 处理网络连接状态变化
        if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
            NetworkInfo networkInfo = intent.getParcelableExtra(
                WifiManager.EXTRA_NETWORK_INFO
            );
            if (networkInfo != null) {
                handleNetworkStateChanged(networkInfo);
            }
        }
    }

    private void handleWifiStateChanged(int state) {
        switch (state) {
            case WifiManager.WIFI_STATE_ENABLED:
                Log.d(TAG, "WiFi已开启");
                break;
            case WifiManager.WIFI_STATE_DISABLED:
                Log.d(TAG, "WiFi已关闭");
                break;
            case WifiManager.WIFI_STATE_ENABLING:
                Log.d(TAG, "WiFi正在开启...");
                break;
            case WifiManager.WIFI_STATE_DISABLING:
                Log.d(TAG, "WiFi正在关闭...");
                break;
            default:
                Log.d(TAG, "WiFi状态未知: " + state);
                break;
        }
    }

    private void handleNetworkStateChanged(NetworkInfo networkInfo) {
        NetworkInfo.State state = networkInfo.getState();
        if (state == NetworkInfo.State.CONNECTED) {
            Log.d(TAG, "WiFi已连接");
        } else if (state == NetworkInfo.State.DISCONNECTED) {
            Log.d(TAG, "WiFi已断开");
        } else if (state == NetworkInfo.State.CONNECTING) {
            Log.d(TAG, "WiFi正在连接...");
        }
    }
}

在Activity中注册和解注册

有了Receiver,接下来就是在Activity里管理它的生命周期:

public class MainActivity extends AppCompatActivity {

    private WifiBroadcastReceiver wifiReceiver;
    private IntentFilter wifiIntentFilter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 创建Receiver实例
        wifiReceiver = new WifiBroadcastReceiver();

        // 创建IntentFilter,添加要监听的Action
        wifiIntentFilter = new IntentFilter();
        wifiIntentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
        wifiIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
    }

    @Override
    protected void onResume() {
        super.onResume();
        // 动态注册广播
        registerReceiver(wifiReceiver, wifiIntentFilter);
    }

    @Override
    protected void onPause() {
        super.onPause();
        // 解注册广播,防止内存泄漏
        unregisterReceiver(wifiReceiver);
    }
}

核心要点:

  • 注册和解注册必须成对出现,推荐在 onResumeonPause 中操作。
  • 不要在 onCreate 里注册、onDestroy 里解注册。因为Activity可能被重建,但 onDestroy 不一定被调用。
  • 如果Receiver是内部类,记得声明为 static,避免持有外部Activity的引用。

避坑指南:我曾经踩过的坑

我曾经在项目里犯过一个低级错误:在 onCreate 里注册了广播,但忘记在 onPause 里解注册。结果用户旋转屏幕时,Activity重建了多次,Receiver也被注册了多次。每次收到广播,onReceive 就被调用多次,导致UI闪烁、数据重复处理。

还有一个坑:NETWORK_STATE_CHANGED_ACTION 在Android 10及以上版本中,EXTRA_NETWORK_INFO 已经被标记为废弃。虽然还能用,但Google推荐使用 ConnectivityManager.NetworkCallback 来监听网络变化。不过对于WiFi专项开发,这个广播仍然是最直接的方式。

小技巧:如果你只需要监听WiFi连接状态,可以只注册 NETWORK_STATE_CHANGED_ACTION。但如果你需要知道WiFi开关是否打开(比如在开关动画时做UI反馈),那就必须同时监听 WIFI_STATE_CHANGED_ACTION

知识体系总览

下面这张图帮你理清本章的核心逻辑:

WiFi广播动态注册知识体系 动态注册广播 WIFI_STATE_CHANGED_ACTION 监听WiFi硬件开关状态 NETWORK_STATE_CHANGED_ACTION 监听WiFi网络连接状态 onResume → registerReceiver 注册时机 onPause → unregisterReceiver 解注册时机 ⚠ 必须成对注册/解注册,否则内存泄漏

总结一下

动态注册WiFi广播,说白了就是三步:创建Receiver、注册、解注册。核心是理解两个广播的区别——一个管硬件状态,一个管网连接状态。生命周期管理是重中之重,我建议你每次写广播相关代码时,都先问自己一句:“解注册写了吗?”

嗯,这一章的内容就到这里。代码不多,但背后的原理和坑不少。你动手写一遍,印象会更深刻。


公众号:蓝海资料掘金营,微信deep3321