Settings中設置Wi-Fi的頁面如圖5-5所示。
:-: 
圖5-5 Wi-Fi設置頁面
左圖所示為當前搜索到的無線網絡信息。當選擇圖中"Test"無線網絡時,進入右圖。
右圖所示為"Test"無線網絡設置對話框。用戶在“密碼”一欄中輸入密碼后,點擊“連接”按鈕即可加入目標無線網絡"Test"。
在Settings的代碼中,和圖5-5所示UI相關的類如圖5-6所示。
:-: 
圖5-6 Settings中Wi-Fi設置相關類
圖5-6中,WifiSettings對應于圖5-5的左圖。另外,WifiEnabler類中有一個mSwitch對象,該對象就是圖5-5左圖中右上角對應的Wi-Fi開關按鈕。
WifiDialog類對應于圖5-5中的右圖。WifiDialog顯示的無線網絡配置信息由WifiConfigController來控制和管理。
WifiSettings的內部類Scanner用于處理和無線網絡掃描相關的工作。
下面將按調用順序來分析這些類的功能。首先是WifiSettings相關類的創建。
**1、WifiSettings相關類初始化分析**
先來看WifiSetting的構造函數,代碼如下所示。
**WifiSettings.java**
~~~
public WifiSettings() {
mFilter = new IntentFilter();
mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
mFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
handleEvent(context, intent); // 處理廣播事件
}
};
mScanner = new Scanner(); // 創建一個Scanner對象
}
~~~
WifiSettings創建了一個廣播接收對象,該對象關注的廣播事件較多,其中四個重要的廣播事件如下。
* **WIFI_STATE_CHANGED_ACTION**:該廣播事件反映了Wi-Fi功能所對應的狀態,這些狀態是WIFI_STATE_DISABLED(Wi-Fi功能已被關閉)、WIFI_STATE_DISABLING(Wi-Fi功能正在關閉中)、WIFI_STATE_ENABLED(Wi-Fi功能已被打開)、WIFI_STATE_ENABLING(Wi-fi功能正在打開中)和WIFI_STATE_UNKNOWN(Wi-Fi功能狀態未知)。
* **SCAN_RESULTS_AVAILABLE_ACTION**:該廣播事件表示無線網絡掃描完畢,可以從WPAS中獲取掃描結果。
* **SUPPLICANT_STATE_CHANGED_ACTION**:該廣播事件用于表示WPAS的狀態發生了變化。它和5.2.3節中SupplicantStateTracker介紹的SupplicantState相關。
* **NETWORK_STATE_CHANGED_ACTION**:該廣播事件用于表示Wi-Fi連接狀態發生變化。其攜帶的信息一般是一個NetworkInfo對象。NetworkInfo類在前文代碼注釋中曾介紹過,它用于表達一個網絡接口的狀態(Describes the status of anetwork interface)。
>[info] 提示 以后碰到實際代碼時再來分析上述廣播事件的具體處理方法。
在圖5-5中左圖所示的UI初始化過程中,WifiSettings的onActivityCreated函數將被調用,代碼如下所示。
**WifiSettings.java::onActivityCreated**
~~~
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mP2pSupported =
getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT);
mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
......
Switch actionBarSwitch = new Switch(activity);// 創建Wi-Fi開關按鈕對應的Switch對象
mWifiEnabler = new WifiEnabler(activity, actionBarSwitch);// 創建WifiEnabler對象
......
}
~~~
在onActivityCreated函數中,Switch和WifiEnabler對象被創建。馬上來看WifiEnabler的構造函數,代碼如下所示。
**WifiEnabler.java::WifiEnabler**
~~~
public WifiEnabler(Context context, Switch switch_) {
mContext = context; mSwitch = switch_;
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
// WifiEnabler關注如下三個廣播事件
mIntentFilter = new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION);
mIntentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
mIntentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
}
~~~
WifiEnabler和WifiSettings設置的廣播接收對象在WifiSettings中的onResume函數中被注冊,相關代碼如下所示。
**WifiSettings.java::onResume**
~~~
public void onResume() {
super.onResume();
if (mWifiEnabler != null)
mWifiEnabler.resume();// 先調用WifiEnabler的resume函數
getActivity().registerReceiver(mReceiver, mFilter);// 注冊廣播接收對象
......
}
~~~
**WifiEnabler.java::resume**
~~~
public void resume() {
mContext.registerReceiver(mReceiver, mIntentFilter);
mSwitch.setOnCheckedChangeListener(this);
}
~~~
由于WifiEnabler先注冊廣播接收對象,所以對于WIFI_STATE_CHANGED_ACTION、SUPPLICANT_STATE_CHANGED_ACTION和NETWORK_STATE_CHANGED_ACTION廣播來說,WifiEnabler的廣播接收對象將先被觸發以處理這些消息,然后才是WifiSettings的廣播接收對象[^①]。
>[info] 提示 研究WifiEnabler廣播接收對象的代碼后,會發現實際上它真正處理的只有WIFI_STATE_CHANGED_ACTION廣播。WifiEnabler將根據該廣播的信息以更新Switch的界面。
>
> 廣播雖然是Android平臺中特有的信息發布機制,但廣播派發的過程卻并不輕松。從運行效率角度考慮,一個進程中,同樣的廣播最好用一個廣播接收對象來處理。所以,筆者覺得WifiEnabler中的這個廣播接收對象有些浪費。
假設用戶通過圖5-5左圖右上角的按鈕打開了Wi-Fi功能,這個點擊事件會觸發什么動作呢?來看下文。
**2、啟用Wi-Fi功能**
由圖5-6可知,WifiEnabler實現了CompoundButton的onChechedChangeListener接口,故用戶點擊事件將觸發WifiEnabler的onCheckedChanged函數。
**WifiEnabler.java::onCheckedChanged**
~~~
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
......
// 調用WifiManager的setWifiEnabled函數
if (mWifiManager.setWifiEnabled(isChecked)) mSwitch.setEnabled(false);
......
}
~~~
WifiManager的setWifiEnabled函數將觸發WifiService開展一系列的動作。這些動作的細節內容我們留待下文分析。在WifiService開展這一系列的動作的過程中,它會通過發送廣播的方式向外界發布一些信息。所以,只要關注WifiSettings和WifiEnabler如何處理這些廣播事件即可。
根據前文所述,WifiEnabler注冊的廣播事件對象沒有做什么有意義的事情,所以下面直接來看WifiSettings的廣播接收對象。
**WifiSettings.java::handleEvent**
~~~
private void handleEvent(Context context, Intent intent) {
String action = intent.getAction();
if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
WifiManager.WIFI_STATE_UNKNOWN));
} else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) ||
WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) ||
WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
updateAccessPoints();// 更新圖5-5左圖中的無線網絡列表
} else if (WifiManager.SUPPLICANT_STATE_CHANGED_ACTION.equals(action)) {
SupplicantState state = (SupplicantState) intent.getParcelableExtra(
WifiManager.EXTRA_NEW_STATE);
// 只在SupplicantState處于握手階段才調用updateConnectionState
if (!mConnected.get() && SupplicantState.isHandshakeState(state))
updateConnectionState(WifiInfo.getDetailedStateOf(state));
} else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
WifiManager.EXTRA_NETWORK_INFO);
mConnected.set(info.isConnected());
changeNextButtonState(info.isConnected());
updateAccessPoints();
updateConnectionState(info.getDetailedState());
......
} else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
updateConnectionState(null);
}
}
~~~
**①、觸發掃描**
根據前面對幾個廣播信息的描述,當Wi-Fi功能被啟用時,將收到WIFI_STATE_CHANGED_ACTION廣播,而該廣播的處理函數是updateWifiState。代碼如下所示。
**WifiSettings.java::updateWifiState**
~~~
private void updateWifiState(int state) {
......
switch (state) {
case WifiManager.WIFI_STATE_ENABLED:
mScanner.resume();// 啟動掃描
return;
......
}
......
}
~~~
**WifiSettings.java::Scanner**
~~~
private class Scanner extends Handler {
private int mRetry = 0;
void resume() {
if (!hasMessages(0))
sendEmptyMessage(0);
}
......
public void handleMessage(Message message) {
if (mWifiManager.startScanActive())
mRetry = 0;// 發起掃描
else if (++mRetry >= 3) ......// 掃描失敗
sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS);// 每1秒發起一次掃描
}
}
~~~
請讀者暫時記住WifiManager的startScanActive函數,下文再詳細分析。
**②、更新AP列表**
假設WPAS掃描完畢,則WifiSettings將收到SCAN_RESULTS_AVAILABLE_ACTION廣播,該廣播的處理函數為updateAccessPoints。
**WifiSettings.java::updateAccessPoints**
~~~
private void updateAccessPoints() {
if (getActivity() == null)
return;
final int wifiState = mWifiManager.getWifiState();// 獲取Wi-Fi的狀態
switch (wifiState) {
case WifiManager.WIFI_STATE_ENABLED:
// 創建AP列表
final Collection<AccessPoint> accessPoints = constructAccessPoints();
getPreferenceScreen().removeAll();
if(accessPoints.size() == 0)
addMessagePreference(R.string.wifi_empty_list_wifi_on);
for (AccessPoint accessPoint : accessPoints) {// 添加到UI中顯示
getPreferenceScreen().addPreference(accessPoint);
}
break;
.......
}
}
~~~
來看上述代碼中的constructAccessPoints函數,代碼如下所示。
**WifiSettings.java::constructAccessPoints**
~~~
private List<AccessPoint> constructAccessPoints() {
ArrayList<AccessPoint> accessPoints = new ArrayList<AccessPoint>();
Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>();
// getConfiguredNetworks將從WPAS中讀取wpa_supplicant.conf中保存的那些無線網絡信息
final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
if (configs != null) {
for (WifiConfiguration config : configs) {
// 為每一個已經保存的無線網絡創建一個新AccessPoint對象
AccessPoint accessPoint = new AccessPoint(getActivity(), config);
accessPoint.update(mLastInfo, mLastState);
accessPoints.add(accessPoint);
apMap.put(accessPoint.ssid, accessPoint);
}
}
// 獲取掃描結果。本章不討論getScanResult函數
final List<ScanResult> results = mWifiManager.getScanResults();
if (results != null) {
for (ScanResult result : results) {// 略過那些沒有SSID的AP或者IBSS網絡
if (result.SSID == null || result.SSID.length() == 0 ||result.capabilities.contains("[IBSS]"))
continue;
boolean found = false;
// 如果之前保存的無線網絡包含在此次掃描結果中,則更新該無線網絡的一些信息
for (AccessPoint accessPoint : apMap.getAll(result.SSID))
if (accessPoint.update(result))
found = true;
if (!found) {
// 比較掃描結果和之前保存的無線網絡信息,如果是新發現的AP,則創建一個AP對象
AccessPoint accessPoint = new AccessPoint(getActivity(), result);
accessPoints.add(accessPoint);
apMap.put(accessPoint.ssid, accessPoint);
}
}
}
Collections.sort(accessPoints);
return accessPoints;
}
~~~
**③、加入目標無線網絡**
當WifiSettings界面顯示出周圍的無線網絡后,用戶下一步要做的就是從列表中選擇加入某個無線網絡。其中,處理用戶選擇AP事件的函數為onPreferenceTreeClick,代碼如下所示。
**WifiSettings.java::onPreferenceTreeClick**
~~~
public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
if (preference instanceof AccessPoint) {
mSelectedAccessPoint = (AccessPoint) preference;
if (......) {// 對于沒有安全設置的無線網絡,直接連接它即可
mSelectedAccessPoint.generateOpenNetworkConfig();
mWifiManager.connect(mSelectedAccessPoint.getConfig(), mConnectListener);
} else
showDialog(mSelectedAccessPoint, false);// 彈出圖5-5右圖所示的對話框
}......
return true;
}
// showDialog代碼
private void showDialog(AccessPoint accessPoint, boolean edit) {
......
mDlgAccessPoint = accessPoint;
mDlgEdit = edit;
showDialog(WIFI_DIALOG_ID);// showDilaog將創建一個WifiDialog對象
}
~~~
由于WifiDialog及配置控制類WifiConfigController比較簡單,故此處不討論它們。
當用戶設置完目標無線網絡的信息(例如輸入密碼)后,點擊圖5-5右圖所示對話框的“連接”按鈕。此動作將觸發WifiSettings的submit函數被調用,代碼如下所示。
**WifiSettings.java::submit**
~~~
void submit(WifiConfigController configController) {
final WifiConfiguration config = configController.getConfig();
if (config == null) {
......
} ......// 其他處理
} else {
if (configController.isEdit() || requireKeyStore(config)){
mWifiManager.save(config, mSaveListener);
}else mWifiManager.connect(config, mConnectListener);// 連接目標無線網絡
}
......
~~~
至此,WifiSettings的工作就告一段落,它的后續工作就是等待并處理廣播事件。如果一切順利,它將接收一個NETWORK_STATE_CHANGED_ACTION廣播事件以告知手機成功已經加入目標無線網絡。
**3、Settings操作Wi-Fi知識總結**
從本質上來說,WifiSettings的內容其實并不復雜。但根據筆者的經驗,初學者并不能很快把握WifiSettings的工作流程。原因有如下幾點。
自從Settings UI中引入Fragment以來,整個Settings的代碼比之前要復雜得多。對UI不熟悉的讀者可先閱讀SDK文檔中關于Fragment的介紹。
WifiSettings和WifiEnabler中的廣播接收對象互相干擾。這兩個廣播接收對象會處理一些相同的廣播。并且這些廣播將先由WifiEnabler處理,然后再由WifiSettings處理。不論是調試還是分析源碼,初學者需要在兩個類之間來回切換以研究它們是如何處理廣播消息的,使得精力很容易被分散。不過,結合前文的介紹,讀者完全可以忽略WifiEnabler中的廣播接收對象。
在本節所述的工作流程中,WifiSettings會接收到很多次廣播。這些廣播由WifiService及相關模塊發送。雖然發送廣播以及時反饋信息是一種正確的做法,但也應該控制廣播發送的次數。以WifiSettings中處理SUPPLICANT_STATE_CHANGED_ACTION廣播為例,只有在網絡未連接及SupplicantState處于握手階段時它才做一些有意義的事情。結合前文對SupplicantState狀
態的介紹,它一共有13個狀態,減去其中沒有地方使用的3個狀態(DORMANT、UNINITIALIZED、INVALID,參考5.2.3節關于WifiMonitor的介紹),WifiSetting將接收到最多10次(實際過程中還要減去一些沒有被觸發的狀態)SUPPLICANT_STATE_CHANGED_ACTION廣播。這10次廣播中,又有多少需要真正被處理呢(即處于未連接狀態并且SupplicantState處于握手階段)?
另外,筆者提煉了上述代碼中WifiSettings和WifiManager交互的幾個重要函數以作為下一節的分析重點,這些函數如下。
* setWifiEnabled:啟用Wi-Fi功能。
* startScanActive:啟動AP掃描。
* connect:連接至目標AP。
[^①]:對Android廣播機制實現原理感興趣的讀者不妨閱讀《深入理解Android:卷Ⅱ》6.4節。
- 前言
- 第1章 準備工作
- 1.1 Android系統架構
- 1.2 工具使用
- 1.2.1 Source Insight的使用
- 1.2.2 Eclipse的使用
- 1.2.3 BusyBox的使用
- 1.3 本書資源下載說明
- 第2章 深入理解Netd
- 2.1 概述
- 2.2 Netd工作流程
- 2.2.1 main函數分析
- 2.2.2 NetlinkManager分析
- 2.2.3 CommandListener分析
- 2.2.4 DnsProxyListener分析
- 2.2.5 MDnsSdListener分析
- 2.3 CommandListener中的命令
- 2.3.1 iptables、tc和ip命令
- 2.3.2 CommandListener構造函數和測試工具ndc
- 2.3.3 InterfaceCmd命令
- 2.3.4 IpFwd和FirewallCmd命令
- 2.3.5 ListTtysCmd和PppdCmd命令
- 2.3.6 BandwidthControlCmd和IdletimerControlCmd命令
- 2.3.7 NatCmd命令
- 2.3.8 TetherCmd和SoftapCmd命令
- 2.3.9 ResolverCmd命令
- 2.4 NetworkManagementService介紹
- 2.4.1 create函數詳解
- 2.4.2 systemReady函數詳解
- 2.5 本章總結和參考資料說明
- 2.5.1 本章總結
- 2.5.2 參考資料說明
- 第3章 Wi-Fi基礎知識
- 3.1 概述
- 3.2 無線電頻譜和802.11協議的發展歷程
- 3.2.1 無線電頻譜知識
- 3.2.2 IEEE 802.11發展歷程
- 3.3 802.11無線網絡技術
- 3.3.1 OSI基本參考模型及相關基本概念
- 3.3.2 802.11知識點導讀
- 3.3.3 802.11組件
- 3.3.4 802.11 Service介紹
- 3.3.5 802.11 MAC服務和幀
- 3.3.6 802.11 MAC管理實體
- 3.3.7 無線網絡安全技術知識點
- 3.4 Linux Wi-Fi編程API介紹
- 3.4.1 Linux Wireless Extensions介紹
- 3.4.2 nl80211介紹
- 3.5 本章總結和參考資料說明
- 3.5.1 本章總結
- 3.5.2 參考資料說明
- 第4章 深入理解wpa_supplicant
- 4.1 概述
- 4.2 初識wpa_supplicant
- 4.2.1 wpa_supplicant架構
- 4.2.2 wpa_supplicant編譯配置
- 4.2.3 wpa_supplicant命令和控制API
- 4.2.4 git的使用
- 4.3 wpa_supplicant初始化流程
- 4.3.1 main函數分析
- 4.3.2 wpa_supplicant_init函數分析
- 4.3.3 wpa_supplicant_add_iface函數分析
- 4.3.4 wpa_supplicant_init_iface函數分析
- 4.4 EAP和EAPOL模塊
- 4.4.1 EAP模塊分析
- 4.4.2 EAPOL模塊分析
- 4.5 wpa_supplicant連接無線網絡分析
- 4.5.1 ADD_NETWORK命令處理
- 4.5.2 SET_NETWORK命令處理
- 4.5.3 ENABLE_NETWORK命令處理
- 4.6 本章總結和參考資料說明
- 4.6.1 本章總結
- 4.6.2 參考資料說明
- 第5章 深入理解WifiService
- 5.1 概述
- 5.2 WifiService的創建及初始化
- 5.2.1 HSM和AsyncChannel介紹
- 5.2.2 WifiService構造函數分析
- 5.2.3 WifiStateMachine介紹
- 5.3 加入無線網絡分析
- 5.3.1 Settings操作Wi-Fi分析
- 5.3.2 WifiService操作Wi-Fi分析
- 5.4 WifiWatchdogStateMachine介紹
- 5.5 Captive Portal Check介紹
- 5.6 本章總結和參考資料說明
- 5.6.1 本章總結
- 5.6.2 參考資料說明
- 第6章 深入理解Wi-Fi Simple Configuration
- 6.1 概述
- 6.2 WSC基礎知識
- 6.2.1 WSC應用場景
- 6.2.2 WSC核心組件及接口
- 6.3 Registration Protocol詳解
- 6.3.1 WSC IE和Attribute介紹
- 6.3.2 802.11管理幀WSC IE設置
- 6.3.3 EAP-WSC介紹
- 6.4 WSC代碼分析
- 6.4.1 Settings中的WSC處理
- 6.4.2 WifiStateMachine的處理
- 6.4.3 wpa_supplicant中的WSC處理
- 6.4.4 EAP-WSC處理流程分析
- 6.5 本章總結和參考資料說明
- 6.5.1 本章總結
- 6.5.2 參考資料說明
- 第7章 深入理解Wi-Fi P2P
- 7.1 概述
- 7.2 P2P基礎知識
- 7.2.1 P2P架構
- 7.2.2 P2P Discovery技術
- 7.2.3 P2P工作流程
- 7.3 WifiP2pSettings和WifiP2pService介紹
- 7.3.1 WifiP2pSettings工作流程
- 7.3.2 WifiP2pService工作流程
- 7.4 wpa_supplicant中的P2P
- 7.4.1 P2P模塊初始化
- 7.4.2 P2P Device Discovery流程分析
- 7.4.3 Provision Discovery流程分析
- 7.4.4 GO Negotiation流程分析
- 7.5 本章總結和參考資料說明
- 7.5.1 本章總結
- 7.5.2 參考資料說明
- 第8章 深入理解NFC
- 8.1 概述
- 8.2 NFC基礎知識
- 8.2.1 NFC概述
- 8.2.2 NFC R/W運行模式
- 8.2.3 NFC P2P運行模式
- 8.2.4 NFC CE運行模式
- 8.2.5 NCI原理
- 8.2.6 NFC相關規范
- 8.3 Android中的NFC
- 8.3.1 NFC應用示例
- 8.3.2 NFC系統模塊
- 8.4 NFC HAL層討論
- 8.5 本章總結和參考資料說明
- 8.5.1 本章總結
- 8.5.2 參考資料說明
- 第9章 深入理解GPS
- 9.1 概述
- 9.2 GPS基礎知識
- 9.2.1 衛星導航基本原理
- 9.2.2 GPS系統組成及原理
- 9.2.3 OMA-SUPL協議
- 9.3 Android中的位置管理
- 9.3.1 LocationManager架構
- 9.3.2 LocationManager應用示例
- 9.3.3 LocationManager系統模塊
- 9.4 本章總結和參考資料說明
- 9.4.1 本章總結
- 9.4.2 參考資料說明
- 附錄