目录
前言
公司设备产品不同于互联网移动端项目开发,设备端的WiFi、蓝牙相关功能需要在咱们的应用开发中进行实现和定制,故我们必须熟悉WiFi、蓝牙等相关标准开发流程,以减少在开发过程和测试过程因为流程不规范而引入的问题。
本文主要讨论的是WiFi的开关、扫描、连接等相关流程,以Android4.3 至Android6.0系统设置WiFi相关流程作为参考。Android各个版本的WiFi相关设置有些出入,但是大致流程不变,流程供参考,若有借鉴请自行充分验证并查阅对应系统WiFi设置源码进行对比。系统源码在线查阅地址,Android 7.1以后:CPC | 设备控制台,Android 9.0之前:AndroidXRef。
WiFi权限
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
WiFi的开启与关闭
-
系统设置中WiFi开启与关闭控制类:
/packages/apps/Settings/src/com/android/settings/wifi/WifiEnabler.java
-
这个类主要控制的是WiFi UI按钮的状态和WiFi的状态。以下是核心代码整理如下:
import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.wifi.WifiManager; import android.widget.CompoundButton; import android.widget.Switch; public class WifiEnabler implements CompoundButton.OnCheckedChangeListener { private final Context mContext; private Switch mSwitch; private final WifiManager mWifiManager; private boolean mStateMachineEvent; private final IntentFilter mIntentFilter; private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) { handleWifiStateChanged(intent.getIntExtra( WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN)); } } }; public WifiEnabler(Context context, Switch switch_) { mContext = context; mSwitch = switch_; mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); mIntentFilter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION); // The order matters! We really should not depend on this. :( mIntentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); } // public void resume() { // Wi-Fi state is sticky, so just let the receiver update UI mContext.registerReceiver(mReceiver, mIntentFilter); mSwitch.setOnCheckedChangeListener(this); } public void pause() { mContext.unregisterReceiver(mReceiver); mSwitch.setOnCheckedChangeListener(null); } /** * 设置UI WiFi按钮,以便在这里控制按钮状态 */ public void setSwitch(Switch switch_) { if (mSwitch == switch_) return; mSwitch.setOnCheckedChangeListener(null); mSwitch = switch_; mSwitch.setOnCheckedChangeListener(this); final int wifiState = mWifiManager.getWifiState(); boolean isEnabled = wifiState == WifiManager.WIFI_STATE_ENABLED; boolean isDisabled = wifiState == WifiManager.WIFI_STATE_DISABLED; mSwitch.setChecked(isEnabled); mSwitch.setEnabled(isEnabled || isDisabled); } public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { //Do nothing if called as a result of a state machine event if (mStateMachineEvent) { return; } // ① 如果在飞行模式下等其他不允许使用 Wi-Fi的情况,则显示 toast 消息 if (isChecked && isWifiForbidden()) { //将开关重置为关闭。没有无限的 checklistenr 循环。 buttonView.setChecked(false); } //② 如果启用 Wifi,则禁用热点网络共享 if (isChecked && isWiFiAPOpen()) { //关闭热点 TODO } if (mWifiManager.setWifiEnabled(isChecked)) { // 考虑进行中的状态,禁用直到新状态处于活动状态 mSwitch.setEnabled(false); } else { // Error } } private boolean isWifiForbidden() { //... return false; } private boolean isWiFiAPOpen() { //... return false; } private void handleWifiStateChanged(int state) { switch (state) { case WifiManager.WIFI_STATE_ENABLING: mSwitch.setEnabled(false); break; case WifiManager.WIFI_STATE_ENABLED: setSwitchChecked(true); mSwitch.setEnabled(true); break; case WifiManager.WIFI_STATE_DISABLING: mSwitch.setEnabled(false); break; case WifiManager.WIFI_STATE_DISABLED: setSwitchChecked(false); mSwitch.setEnabled(true); break; default: setSwitchChecked(false); mSwitch.setEnabled(true); break; } } private void setSwitchChecked(boolean checked) { if (checked != mSwitch.isChecked()) { mStateMachineEvent = true; mSwitch.setChecked(checked); mStateMachineEvent = false; } } }
使用时创建一个对象,在合适的地方调用resume()和pause(),系统设置是在对应activity生命周期中调用的。
WiFi扫描及ResultList获取
WiFi扫描
-
系统设置中WiFi扫描的控制类为内部类Scanner。
-
Android 6.0以前在 Scanner在
"/packages/apps/Settings/src/com/android/settings/wifi/WifiSettings.java"中;
-
Android 6.0及以后 Scanner在
"/frameworks/base/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java"中
-
-
该类主要控制WiFi的扫描逻辑和流程,核心代码整理如下:
class Scanner extends Handler { static final int MSG_SCAN = 0; private int mRetry = 0; void resume() { if (!hasMessages(MSG_SCAN)) { sendEmptyMessage(MSG_SCAN); } } void forceScan() { removeMessages(MSG_SCAN); sendEmptyMessage(MSG_SCAN); } void pause() { mRetry = 0; removeMessages(MSG_SCAN); } boolean isScanning() { return hasMessages(MSG_SCAN); } @Override public void handleMessage(Message message) { if (message.what != MSG_SCAN) return; if (mWifiManager.startScan()) { mRetry = 0; } else if (++mRetry >= 3) { mRetry = 0; //TODO 通知用户扫描失败 return; } sendEmptyMessageDelayed(MSG_SCAN, WIFI_RESCAN_INTERVAL_MS); } }
// Combo scans can take 5-6s to complete - set to 10s //(组合扫描可能需要 5-6 秒才能完成 - 设置为 10 秒) private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000;
使用时创建一个对象,在合适的地方调用resume()和pause(),系统设置是在对应activity生命周期中调用的,及对应的广播状态中调用。
这里有个注意点,即在WiFi正在获取IP地址是的状态不要进行WiFi扫描;故对scan方法封装如下:
private void scan() { WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); NetworkInfo.DetailedState detailedState = wifiInfo.getDetailedStateOf(wifiInfo.getSupplicantState()); //获取IP中并且没有连接上则跳过本次扫描 if (!(detailedState == NetworkInfo.DetailedState.OBTAINING_IPADDR )|| WifiUtils.isConnect(this)) { mWifiUtils.startScan(); } else { Log.w(TAG, "scan canceled , wifi detailedState = " + detailedState); } }
ResultList获取
WiFi扫描指令(mWifiManager.startScan)发起后,系统会进行扫描动作,扫描完成会发送一个扫描完成的广播,在收到扫描完成广播后再去获取系统扫描的WiFi缓存列表。相关流程代码梳理如下:
//广播注册 mFilter = new IntentFilter(); //add actions mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); //SCAN_RESULTS_AVAILABLE_ACTION 就是WiFi扫描已完成,并且结果可用的Action mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION); mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { handleEvent(context, intent); } }; //广播处理及列表获取 private void handlerEvent(Context context,Intent intent){ String action = intent.getAction(); if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action)) { int wifiState = mWifiManager().getWifiState(); switch (wifiState) { case WifiManager.WIFI_STATE_ENABLED: { mResultList = mWifiManager.getScanResults(); break; case WifiManager.WIFI_STATE_DISABLED: //... break; case WifiManager.WIFI_STATE_DISABLING: //... break; case WifiManager.WIFI_STATE_ENABLING: //... break; case WifiManager.WIFI_STATE_UNKNOWN: //... break; } }else{ //... } }
WiFi连接
WiFi流程和代码封装
WiFi连接是根据WiFi SSID和password 以及加密类型生成WiFi配置信息,然后通过配置信息来保存连接网络的,这里参照系统WiFi设置里的实习方式以及项目中相关封装代码,整理如下:
/** * @Description 连接网络 * @param SSID ssid * @param password 密码 * @param type * WIFI_CONNECT_NO_PWD = 1 * WIFI_CONNECT_WEP = 2 * WIFI_CONNECT_WPA = 3 * @return */ public int connect(String SSID, String password, int type) { //删除旧选项; WifiConfiguration tempConfig = isExsits(SSID); if (tempConfig != null) { mWifiManager.removeNetwork(tempConfig.networkId); } WifiConfiguration wifiConfig = createWifiInfo(SSID, password, type); addNetwork(wifiConfig); WifiConfiguration targetConfigure = isExsits(SSID); if (targetConfigure != null) { int networkId = targetConfigure.networkId; return networkId; } return -1; } /** * @Description 检查该网络配置文件是否存在 * @param SSID * @return */ public WifiConfiguration isExsits(String SSID) { List<WifiConfiguration> existingConfigs = mWifiManager.getConfiguredNetworks(); if(existingConfigs == null){ return null; } for (WifiConfiguration existingConfig : existingConfigs) { if (existingConfig.SSID.equals("\"" + SSID + "\"")) { return existingConfig; } } return null; } /** * @Description 创建一个网络配置文件 * @param SSID * @param password * @param type * @return 返回网络配置 */ public WifiConfiguration createWifiInfo(String SSID, String password, int type) { WifiConfiguration config = new WifiConfiguration(); config.allowedAuthAlgorithms.clear(); config.allowedGroupCiphers.clear(); config.allowedKeyManagement.clear(); config.allowedPairwiseCiphers.clear(); config.allowedProtocols.clear(); config.SSID = "\"" + SSID + "\""; if (type == 1) {// NO_PASWORD config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); } if (type == 2) {// WEP加密 config.hiddenSSID = true; config.wepKeys[0] = "\"" + password + "\""; config.allowedAuthAlgorithms .set(WifiConfiguration.AuthAlgorithm.SHARED); config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP); config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP); config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40); config.allowedGroupCiphers .set(WifiConfiguration.GroupCipher.WEP104); config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); config.wepTxKeyIndex = 0; } if (type == 3) {// WPA/WPA2加密 config.preSharedKey = "\"" + password + "\""; config.hiddenSSID = true; config.allowedAuthAlgorithms .set(WifiConfiguration.AuthAlgorithm.OPEN); config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP); config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); config.allowedPairwiseCiphers .set(WifiConfiguration.PairwiseCipher.TKIP); config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP); config.allowedPairwiseCiphers .set(WifiConfiguration.PairwiseCipher.CCMP); config.status = WifiConfiguration.Status.ENABLED; } return config; } /** * @Description 添加一个网络并连接 * @param wcg */ public boolean addNetwork(WifiConfiguration wcg) { int wcgID = mWifiManager.addNetwork(wcg); if (wcgID != -1) { mWifiManager.enableNetwork(wcgID, true); mWifiManager.saveConfiguration(); mWifiManager.reconnect(); Log.d(TAG, "addNetwork: reconnect"); return true; } return false; }
连接过程结果判断,可以通过以下【WiFi的连接状态判断】来获取。
注:若是成功,以下两种都是可以,但是连接失败在Android 6.0以前是不会获取到连接失败的广播的,故Android 6.0以前最好做一个超时定时器进行循环主动查询判断。
WiFi的连接状态判断
-
-
连接状态被动接收。
通过注册WiFi状态广播进行接收。
if (WifiManager.SUPPLICANT_STATE_CHANGED_ACTION.equals(action)) { WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); int linkWifiResult = intent.getIntExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, -1); if (linkWifiResult == WifiManager.ERROR_AUTHENTICATING) { if (currentScanResult != null && currentScanResult.BSSID.equals(wifiInfo.getBSSID())) { hideLoadingDialog(); if (timeOutTimer != null) { timeOutTimer.cancel(); } alertPsdErrorDialog(); // ... } } } else /** * 描 述 此段代码是基于Android6.0新增密码错误广播,满足现有需求 */ if ( "android.net.wifi.supplicant.PASSWARD_ERROR".equals(action)) { WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); if (currentScanResult != null && currentScanResult.BSSID.equals(wifiInfo.getBSSID())) { hideLoadingDialog(); if (timeOutTimer != null) { timeOutTimer.cancel(); } alertPsdErrorDialog(); // ... } /** *NETWORK_STATE_CHANGED_ACTION:WIFI连接状态发生改变的广播.可以从intent中取得NetworkInfo *此时 NetworkInfo 中提供了连接的新状态,如果连接成功, 可以获取当前连接网络的 BSSID,和WifiInfo. */ }else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) { //网络状态改变... } else{ //... }
-
-
-
主动查询
通过WiFiManager相关Api进行WiFi连接状态查询。代码封装如下:
/** * @Description 判断当前设备是否连接上了对应wifi热点 * @param context * @param wifiHotspotName WiFi热点名称 * @return */ public boolean isWifiConnected(Context context, String wifiHotspotName) { ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo info = manager.getActiveNetworkInfo(); if (info != null) { String extraInfo = info.getExtraInfo(); if (extraInfo.equals("\"" + wifiHotspotName + "\"")) { return true; } } return false; } /** * @Description 判断网络是否连接成功 * @param context * @return */ public static boolean isConnect(Context context) { ConnectivityManager connMgr = (ConnectivityManager) context .getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeInfo = connMgr.getActiveNetworkInfo(); if (activeInfo != null && activeInfo.isConnected()) { return activeInfo.getType() == ConnectivityManager.TYPE_WIFI; } return false; }
-