Android-WiFi开发之 WiFi广播监听(格式化版)

引子

  • 安卓中关于系统开发的文章比较少, 而且较为不全面.
  • 对于刚刚接触做系统应用的开发的开发者而言, 在自己动手开发前, 需要借助 Android 源码查询, 参考的方式来实现.
  • 本文为本人在开发中 WIFI 模块功能过程中总结出来的广播部分, 希望为大伙指点迷津.

过程

  • 最近做的研发项目, 需要做一个设置功能, 替换原生设置的apk, 内部牵涉到的功能元有: 网络, 蓝牙, 存储等. 前期被分配到涉及 WIFI 相关的功能开发. 其中重要功能列表包含: 开启/关闭 WIFI, 扫描/刷新WIFI列表, 添加隐藏网络, 连接网络, 编辑/保存/修改网络, 忘记网络等.
  • 刚开始是无从下手的, 对于监听系统的广播及需要处理的数据也是摸不着头脑, 更对Android本身的WIFI体系, 驱动识别, 数据存储, 数据结构更新等一无所知, 经过一系列的学习和摸索, 参考原生 Settings 应用的各种处理手段, 才一步步破解了脑中的一团, 现抽空做了一点总结, 本文为 WIFI 开发广播篇, 具体内容如下.

关于网络开发, 你可能会用到的广播

  • 原生 Settings 中的代码原型:
    IntentFilter filter = new IntentFilter();
    filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
    filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
    filter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
    filter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
    filter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
    filter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
    filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
    filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
  • 如上代码块是原生 Settings 中, 关于 WLAN 页面中的整体功能的一个 IntentFilter 注册. 其主要使用了 WifiManager 类中的 action 作为匹配. 对于不大了解上述 action 的开发者, 可以根据每个 ACTION 的常量的名字来猜一猜大概含义. Android 在广播方面做的比较好的, 就有 ACTION 的定义这一条, 让初学者, 可以通过ACTION的常量名, 就能大概猜出来, 这条广播是干嘛的了. 下面, 我会围绕上述的几条广播来做一个简单的阐述.

ConnectivityManager 的一些说明:

  • 大多数做互联网应用的开发者, 会在请求网络 / 刷新内容时, 对网路联通做一个初步处理, 即: 网路存在, 进行网络请求, 网络不存在, 提示用户当前没网络联通, 设置网络 / 显示加载失败. 那么首先, 如果判断当前是否正常联通网络了呢? 这就用到了 Android 提供给广大开发者的一个关于网络的管理类: ConnectivityManager 了;

  • ConnectivityManager 中封装了关于连接方式、连接类型、是否当前连接可用...丰富的供外部调用的函数; 可以通过当前的 Context 以获取系统服务的方式来获取, 具体代码如下:

    // 获取 ConnectivityManager 对象. 
    public static ConnectivityManager getConnectivityManager(Context context) {
        return context == null ? null : (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
    }
  • 利用 ConnectivityManager 获取一些关于连接的东西了, 需要在 AndroidManifest 中配置android.permission.ACCESS_NETWORK_STATE的权限:
    // 获取默认的连接方式: 
    public static NetworkInfo getDefaultNetwork(ConnectivityManager manager) {
        return manager == null ? null : manager.getActiveNetworkInfo();
    }
    
    // 默认网络是否连接: 
    public static boolean isDefaultNetworkConnected(NetworkInfo info) {
        return info != null && info.isConnected() && info.isAvailable();
    }
    
    // 默认网络是有线网: 
    public static boolean isDefaultNetworkIsEthernet(NetworkInfo info) {
        return info != null && info.getType() == ConnectivityManager.TYPE_ETHERNET;
    }
    
    // 默认网络是无线网: 
    public static boolean isDefaultNetworkIsWifi(NetworkInfo info) {
        return info != null && info.getType() == ConnectivityManager.TYPE_WIFI;
    }
    
    // 有线网是否连接: 
    public static boolean isEthernetConnected(ConnectivityManager manager) {
        return manager != null && manager.getNetworkInfo(ConnectivityManager.TYPE_ETHERNET) != null && manager.getNetworkInfo(ConnectivityManager.TYPE_ETHERNET).isConnected;
    }
    
    // 无线网是否连接: 
    public static boolean isWifiConnected(ConnectivityManager manager) {
        return manager != null && manager.getNetworkInfo(ConnectivityManager.TYPE_WIFI) != null && manager.getNetworkInfo(ConnectivityManager.TYPE_WIFI).isConnected;
    }
  • 动态监听网络连接状态的变化: ConnectivityManager.CONNECTIVITY_ACTION, 互联网应用大多会根据网络连接成功, 而主动刷出来内容给用户, 其监听的网络连接变化的ACTION, 便是这一条.

关于 action 的一些说明:

  1. 通过第一步, 已经可以静态获取当前的网络连接了, 而在实际开发过程中, 在很多情况下, 是需要动态刷新状态的, 那么就依赖到 Android 的广播机制了.

  2. 广播机制是 Android 中重要的机制之一, 在跨进程方面的表现也是相当乐观的, 不仅是安卓刚入门还是精通安卓开发的高级工程师, 对此机制应该都是津津乐道的. 一方面是简单易用, 另一方面, 对于进程通知, 调度, 数据传递都存在非常大的意义.

  3. 那么, 说回来了, 既然要动态监听, 那么我们应该怎么做呢, 当然是动态监听系统发出的广播了;下面就把开发中你可能需要用到的广播做一小说明:

  • 首先例举出网络开发中主体会使用到的action, 如下:
    ConnectivityManager.CONNECTIVITY_ACTION
    WifiManager.WIFI_STATE_CHANGED_ACTION
    WifiManager.SCAN_RESULTS_AVAILABLE_ACTION
    WifiManager.NETWORK_IDS_CHANGED_ACTION
    WifiManager.SUPPLICANT_STATE_CHANGED_ACTION
    WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION
    WifiManager.LINK_CONFIGURATION_CHANGED_ACTION
    WifiManager.NETWORK_STATE_CHANGED_ACTION
    WifiManager.RSSI_CHANGED_ACTION
  • 上述 action, 做如下说明, 使用起来也会更加清晰:

  • ConnectivityManager.CONNECTIVITY_ACTION: 网络连接发生了变化的广播, 通常是默认的连接类型已经建立连接或者已经失去连接会触发的广播; 监听到这个广播之后, 可以从 intent 中获取字段 ConnectivityManager.EXTRA_NO_CONNECTIVITY, 如果返回 true, 代表当前连接断开. 否则, 连接成功. 同时可以从 intent 中取出字段 ConnectivityManager.EXTRA_NETWORK_INFO, 返回 NetWorkInfo, 通过此对象, 你会获取当前连接的一些更为具体的信息. 部分代码如下:

    // 是否无连接.
    public static boolean isNoConnectivity(Intent intent) {
        return intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
    }
    
    // 获取当前网络信息. 
    public static NetworkInfo getExtraNetworkInfo(Intent intent) {
        return intent.getPacelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
    }
    
    // 获取当前网络状态. 
    public static NetworkInfo.State getNetState(NetworkInfo info) {
        return info == null ? null : info.getState();
    }
    
    // 获取当前网络类型. 
    public static int getNetState(NetworkInfo info) {
        return info == null ? -1 : info.getType();
    }
  • WifiManager.WIFI_STATE_CHANGED_ACTION: WiFi模块硬件状态改变的广播, 对于肉眼而言, 看到的直观表征有, WiFi开启, WiFi关闭; 而在实际的过程中, WIFI 从开启到关闭, 或是从关闭到开启, 需要经历三个状态, 以开启WIFI为例, 其要经过的状态分别为: 已关闭, 开启中, 已开启. 关闭WIFI则相反, 分为为: 已开启, 关闭中, 关闭. 接收到这个广播后, 你可以从intent中取出当前WiFi硬件的变化状态, 可以使用 int 值来区别; 这个key是: EXTRA_WIFI_STATE, 可能得到的值为:0, 1, 2, 3, 4; 当然除了这种获取方式, 也可以通过WiFiManager对象getWifiState() 获取这个值. 也可以从 intent 中取出另外一个值, 表示之前WiFi模块的状态, 是不是很爽? 那么, 对应的key, 就是: EXTRA_PREVIOUS_WIFI_STATE;
    // 通过 intent 获取当前WIFI状态. 
    public static int getWifiStateByIntent(Intent intent) {
        return intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN);
    }
    
    // 通过 WifiManager 获取当前WIFI状态. 
    public static int getWifiStateByWifiManager(WifiManager manager) {
        return manager == null ? WifiManager.WIFI_STATE_UNKNOWN : manager.getState();
    }
    
    // 获取WIFI前一时刻状态. 
    public static int getWifiPreviousState(Intent intent) {
        return intent.getIntExtra(WifiManager.EXTRA_PREVIOUS_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN);
    }
其中:
 0 --> WiFiManager.WIFI_STATE_DISABLING, 表示 WiFi 正关闭的瞬间状态;
 1 --> WifiManager.WIFI_STATE_DISABLED, 表示 WiFi 模块已经完全关闭的状态; 
 2 --> WifiManager.WIFI_STATE_ENABLING, 表示 WiFi 模块正在打开中瞬间的状态; 
 3 --> WiFiManager.WIFI_STATE_ENABLED, 表示 WiFi 模块已经完全开启的状态;
 4 --> WiFiManager.WIFI_STATE_UNKNOWN, 表示 WiFi 处于一种未知状态; 通常是在开启或关闭WiFi的过程中出现不可预知的错误, 通常是底层状态机可能跑的出现故障了, 会到这种情况, 与底层控制相关; 
  • WifiManager.SCAN_RESULTS_AVAILABLE_ACTION: 扫描到一个热点, 并且此热点达可用状态 会触发此广播; 此时, 你可以通过 wifiManager.getScanResult() 来取出当前所扫描到的 ScanResult; 同时, 你可以从intent中取出一个boolean值; 如果此值为true, 代表着扫描热点已完全成功; 为false, 代表此次扫描不成功, ScanResult 距离上次扫描并未得到更新;
    // 获取 ScanResult 列表: 
    public static List<ScanResult> getScanResultForWifi(WifiManager manager) {
        return manager == null ? null : manager.getScanResult();
    }

    // result 是否更新: 
    public static boolean isResultUpdated(Intent intent) {
        return intent != null && intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false);
    }
  • WifiManager.NETWORK_IDS_CHANGED_ACTION: 在网络配置, 保存, 添加, 连接, 断开, 忘记的操作过后, 均会对 WIFI 热点配置形成影响, 在shell下, 如果有root权限, 可以在执行上述动作前后, 分别浏览 /data/misc/wifi/wpa_supplicant.conf 应该是有本质的变化, 此时会收到此广播. 具体的执行指令为:
adb shell 
$ cat /data/misc/wifi/wpa_supplicant.conf
  • WifiManager.SUPPLICANT_STATE_CHANGED_ACTION: 建立连接的热点正在发生变化. 象征变化的相关类为: SupplicantState, 你可以在接收到此广播时, 观察到已经建立连接的热点的整个连接过程, 包含可能会出现连接错误的错误码. 相关代码为:
    // 获取当前网络新状态. 
    public static SupplicantState getCurrentNetworkState(Intent intent) {
        return intent.getParcelableExtra(WifiManager.EXTRA_NEW_STATE);
    }
    
    // 获取当前网络连接状态码. 
    public static int getCurrentNetworkCode(Intent intent) {
        return int netConnectErrorCode = intent.getIntExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, 0);
    }
    
    // 当前网络是否连接失败
    public static boolean isCurrentNetworkConnectFailed(intent intent) {
        return WifiManager.ERROR_AUTHENTICATING == getCurrentNetworkCode(intent);
    }
  • WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION: 官方的注释是这么说的, 广播已配置的网络发生变化, 可由添加, 修改, 删除网络的触发. 当从 intent 中取出key值为 EXTRA_MULTIPLE_NETWORKS_CHANGED, 其值为 true 时, 那么字段 EXTRA_WIFI_CONFIGURATION 中取出来的配置已经过时, 不是最新配置了, 具体的代码为:
    // 是否多重网络发生变化. 
    public static boolean isMultipleNetworkChanged(Intent intent) {
        return intent.getBooleanExtra(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED, false);
    }
    
    // 获取当前最新网络配置: 
    public static WifiConfiguration getCurWifiConfig(Intent intent) {
        return intent.getParcelableExtra(WifiManager.EXTRA_WIFI_CONFIGURATION);
    }
  • WifiManager.LINK_CONFIGURATION_CHANGED_ACTION: WIFI 连接配置发生改变的广播. 此时, 网路连接功能封装 LinkProperties 和 NetworkCapabilities 可能发生变化.
    // 获取 LinkProperties 
    public static LinkProperties getLinkProperties(Intent intent) {
        return intent.getParcelableExtra(WifiManager.EXTRA_LINK_PROPERTIES);
    }
    
    // 获取 NetworkCapabilities
    public static NetworkCapabilities getNetworkCapabilities(Intent intent) {
        return intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_CAPABILITIES);
    }
  • WifiManager.NETWORK_STATE_CHANGED_ACTION: WIFI 连接状态发生改变的广播. 可以从 intent 中取得 NetworkInfo, 此时 NetworkInfo 中提供了连接的新状态, 如果连接成功, 可以获取当前连接网络的 BSSID, 和 WifiInfo. 相关代码:
    // 获取当前网络
    public static NetworkInfo getCurrentNetworkInfo(Intent intent) {
        return intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
    }
    
    // 获取当前网路状态. 
    public static NetworkInfo.State getCurrentNetworkState(NetworkInfo info) {
        return info != null ? info.getState() : null;
    }
    
    // 获取当前网路BSSID.
    public static String getCurrentNetworkBssid(Intent intent) {
        return intent.getStringExtra(WifiManager.EXTRA_BSSID);
    }
    
    // 获取当前网路的WifiInfo. wifi 连接成功有效.
    public static WifiInfo getCurrentWifiInfo(Intent intent) {
        return intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO);
    }
  • WifiManager.RSSI_CHANGED_ACTION: WIFI 热点信号强度发生变化的广播. 可以获取当前变化热点的最新的信号强度.
// 获取当前热点最新的信号强度
public static int getCurrentNetworkRssi(Intent intent) {
    return intent.getIntExtra(WifiManager.EXTRA_NEW_RSSI, -1000);
}

小结:

  1. 如上, 针对原生 Android Settings源代码, 结合 SDK 中给出的官方 javadoc 整合出, 无线网络开发中常用的广播. WLAN 开放热点, 是另外的功能, 有所区别, 在之后会单独整合出来, 如上, 感谢~~~

  2. 如有疑问, 请简信, 或邮箱告知. 亦可下方评论区留言.

  3. qq 邮箱: 1281641968@qq.com

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容