引子
- 安卓中关于系统开发的文章比较少, 而且较为不全面.
- 对于刚刚接触做系统应用的开发的开发者而言, 在自己动手开发前, 需要借助 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 的一些说明:
通过第一步, 已经可以静态获取当前的网络连接了, 而在实际开发过程中, 在很多情况下, 是需要动态刷新状态的, 那么就依赖到 Android 的广播机制了.
广播机制是 Android 中重要的机制之一, 在跨进程方面的表现也是相当乐观的, 不仅是安卓刚入门还是精通安卓开发的高级工程师, 对此机制应该都是津津乐道的. 一方面是简单易用, 另一方面, 对于进程通知, 调度, 数据传递都存在非常大的意义.
那么, 说回来了, 既然要动态监听, 那么我们应该怎么做呢, 当然是动态监听系统发出的广播了;下面就把开发中你可能需要用到的广播做一小说明:
- 首先例举出网络开发中主体会使用到的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);
}
小结:
如上, 针对原生 Android Settings源代码, 结合 SDK 中给出的官方 javadoc 整合出, 无线网络开发中常用的广播. WLAN 开放热点, 是另外的功能, 有所区别, 在之后会单独整合出来, 如上, 感谢~~~
如有疑问, 请简信, 或邮箱告知. 亦可下方评论区留言.
qq 邮箱: 1281641968@qq.com