本節將圍繞setWifiEnabled、startScanActive和connect函數來介紹WifiService的工作流程。先來看setWifiEnabled函數。
**1、setWifiEnabled函數分析**
WifiService的setWifiEnabled函數將會調用WifiStateMachine的setWifiEnabled,故此處直接來看。
**WifiStateMachine.java::setWifiEnabled**
~~~
public void setWifiEnabled(boolean enable) {
mLastEnableUid.set(Binder.getCallingUid());
if (enable) {// 發送兩條消息
sendMessage(obtainMessage(CMD_LOAD_DRIVER, WIFI_STATE_ENABLING, 0));
sendMessage(CMD_START_SUPPLICANT);
} else {
sendMessage(CMD_STOP_SUPPLICANT);
sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_STATE_DISABLED, 0));
}
}
~~~
其中,CMD_LOAD_DRIVER和CMD_START_SUPPLICANT消息將交由WifiStateMachine來處理。由于WifiStateMachine此時還處于DriverUnloaded狀態,DriverUnloaded的函數processMessage將被調用。
**①、CMD_LOAD_DRIVER處理流程**
先來看它對CMD_LOAD_DRIVER的處理,相關代碼如下所示。
**WifiStateMachine.java::DriverUnloaded:processMessage**
~~~
public boolean processMessage(Message message) {
switch (message.what) {
case CMD_LOAD_DRIVER:
transitionTo(mDriverLoadingState);// 轉到DriverLoadingState
break;
default: return NOT_HANDLED;
}
return HANDLED;
}
~~~
>[info] 提示 由于篇幅原因,本章不討論狀態切換過程中所涉及的各狀態的exit函數。
先執行DriverLoadingState的enter函數,代碼如下所示。
**WifiStateMachine.java::DriverLoadingState:enter**
~~~
class DriverLoadingState extends State {
public void enter() {
final Message message = new Message();
message.copyFrom(getCurrentMessage());
// 復制當前消息,即上面的CMD_LOAD_DRIVER消息
new Thread(new Runnable() {// 單獨啟動一個線程來加載wlan驅動
public void run() {
mWakeLock.acquire();
switch(message.arg1) {
case WIFI_STATE_ENABLING:// CMD_LOAD_DRIVER攜帶了此信息
// 該函數內部將發送WIFI_STATE_CHANGED_ACTION廣播
setWifiState(WIFI_STATE_ENABLING);
break;
......
}
// 加載wlan驅動,如果成功則發送CMD_LOAD_DRIVER_SUCCESS消息
if(mWifiNative.loadDriver())
sendMessage(CMD_LOAD_DRIVER_SUCCESS);
else ......// 失敗的處理
mWakeLock.release();
}
}).start();
}
}
~~~
由上述代碼可知CMD_LOAD_DRIVER消息的處理流程如下。
* DriverUnloaded狀態直接切換到DriverLoading狀態。
* DriverLoading的enter函數中將創建一個工作線程來加載wlan driver。如果成功,它將發送CMD_LOAD_DRIVER_SUCCESS消息。
WifiNative的loadDriver將借助JNI調用以觸發wifi.c中的wifi_load_driver函數被調用,其代碼如下所示。
**Wifi.c::wifi_load_driver**
~~~
int wifi_load_driver()
{
/*
該宏定義了wlan driver的文件路徑名。在AOSP代碼中,沒有地方定義該宏。不過Galaxy Note2
對應的driver文件路徑是“/lib/modules/dhd.ko”。
*/
#ifdef WIFI_DRIVER_MODULE_PATH
char driver_status[PROPERTY_VALUE_MAX];
int count = 100;
if (is_wifi_driver_loaded()) return 0;
/*
DRIVER_MODULE_PATH變量保存了WIFI_DRIVER_MODULE_PATH宏定義的文件路徑名。
如果上面那個宏定義了,此處將通過insmod向內核添加wlan driver。
*/
if (insmod(DRIVER_MODULE_PATH, DRIVER_MODULE_ARG) < 0) return -1;
/*
FIRMWARE_LOADER變量指向WIFI_FIRMWARE_LOADER宏定義的wlan固件加載程序文件路徑名
DRIVER_PROP_NAME的值為“wlan.driver.status”。如果沒有指定wlan固件加載程序,
則直接設置“wlan.driver.status”屬性值為“ok”,否則通過“ctrl.start”方式來啟動wlan
固件加載程序。
*/
if (strcmp(FIRMWARE_LOADER,"") == 0) property_set(DRIVER_PROP_NAME, "ok");
else property_set("ctl.start", FIRMWARE_LOADER);
sched_yield();
while (count-- > 0) {// 判斷wlan driver是否加載成功
if (property_get(DRIVER_PROP_NAME, driver_status, NULL)) {
if (strcmp(driver_status, "ok") == 0) return 0;
else if (strcmp(DRIVER_PROP_NAME, "failed") == 0) {
wifi_unload_driver();
return -1;
}
}
usleep(200000);
}
property_set(DRIVER_PROP_NAME, "timeout");
wifi_unload_driver();
return -1;
#else // 如果沒有定義WIFI_DRIVER_MODULE_PATH宏,則直接設置“wlan.driver.status”屬性值為“ok”
property_set(DRIVER_PROP_NAME, "ok");
return 0;
#endif
}
~~~
**②、CMD_LOAD_DRIVER_SUCCESS處理流程**
下面來看DriverLoadingState是如何處理CMD_LOAD_DRIVER_SUCCESS消息的。
**WifiStateMachine.java::DriverLoadingState:processMessage**
~~~
public boolean processMessage(Message message) {
switch (message.what) {
case CMD_LOAD_DRIVER_SUCCESS:
transitionTo(mDriverLoadedState);// 轉到DriverLoadedState
break;
case CMD_LOAD_DRIVER_FAILURE:
transitionTo(mDriverFailedState);
break;
......
case CMD_START_SUPPLICANT:// DriverLoadingState不處理此消息
case ......// 其他消息
deferMessage(message);
// CMD_START_SUPPLICANT消息將放到下一個狀態中再去處理
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
~~~
由上述代碼可知,DriverLoadingState不處理CMD_START_SUPPLICANT消息,而是將其推遲到下一個狀態中再去處理。對于CMD_LOAD_DRIVER_SUCCESS,直接轉到DriverLoadedState。DriverLoadedState的enter函數僅僅打印一句簡單的日志輸出,而它對CMD_START_SUPPLICANT的處理卻比較復雜。
**③、CMD_START_SUPPLICANT處理流程**
CMD_START_SUPPLICANT消息將在DriverLoaded狀態中得到處理,相關代碼如下所示。
**WifiStateMachine.java::DriverLoadedState:processMessage**
~~~
public boolean processMessage(Message message) {
switch(message.what) {
......
case CMD_START_SUPPLICANT:
try {// 加載wlan固件。使用了netd的SoftAp命令,可參考2.3.8節
mNwService.wifiFirmwareReload(mInterfaceName, "STA");
} ......
try {// 下面這兩個函數對應netd的InterfaceCmd命令。可參考2.3.3節
mNwService.setInterfaceDown(mInterfaceName);
mNwService.setInterfaceIpv6PrivacyExtensions(mInterfaceName, true);
}......
// 啟動wpa_supplicant進程。請回顧5.2.3節中WifiNative介紹
if(mWifiNative.startSupplicant(mP2pSupported)){
mWifiMonitor.startMonitoring();// 啟動WifiMonitor的Monitor線程
transitionTo(mSupplicantStartingState);
// 轉到SupplicantStartingState
}......
break;
case CMD_START_AP:
......
default:
return NOT_HANDLED;
}
return HANDLED;
}
~~~
由上述代碼可知DriverLoadedState處理CMD_START_SUPPLICANT消息的結果。
* wlan固件被加載。
* wpa_supplicant進程被創建,并且WifiService通過WifiMonitor和它建立了交互關系。
* WifiStateMachine狀態切換至SupplicantStartingState。該狀態的enter函數沒有開展有意義的工作。
當WifiMonitor成功連接至WPAS進程后,它將發送SUP_CONNECTION_EVENT消息給WifiStateMachine(參考5.2.3節中關于WifiMonitor的介紹)。下面就來看該消息的處理流程。
**④、SUP_CONNECTION_EVENT處理流程**
SUP_CONNECTION_EVENT在SupplicantStartingState狀態中得到處理,相關代碼如下所示。
**WifiStateMachine.java::SupplicantStartingState:processMessage**
~~~
public boolean processMessage(Message message) {
switch(message.what) {
case WifiMonitor.SUP_CONNECTION_EVENT:
setWifiState(WIFI_STATE_ENABLED);// 發送WIFI_STATE_CHANGED_ACTION廣播
mSupplicantRestartCount = 0;
// 發送消息給SupplicantStateTracker狀態機。請讀者自行研究
mSupplicantStateTracker.sendMessage(CMD_RESET_SUPPLICANT_STATE);
mLastBssid = null; mLastNetworkId =
WifiConfiguration.INVALID_NETWORK_ID;
mLastSignalLevel = -1;
// 設置本機IP地址
mWifiInfo.setMacAddress(mWifiNative.getMacAddress());
mWifiConfigStore.initialize();
initializeWpsDetails();
// 初始化和WPS相關的一些內容。本章將略過和WPS/P2P相關的內容
// 發送SUPPLICANT_CONNECTION_CHANGE_ACTION廣播
sendSupplicantConnectionChangedBroadcast(true);
transitionTo(mDriverStartedState);// 轉到DriverStartedState
break;
......
}
return HANDLED;
}
~~~
結合HSM知識以及圖5-4中WifiStateMachine中各個狀態的層級關系,DriverStarted的父狀態是SupplicantStarted,所以上述代碼中transitionTo(mDriverStartedState)這一句函數調用將導致SupplicantStarted和DriverStarted的enter函數依次被調用。
首先調用的是SupplicantStarted的enter函數,相關代碼如下所示。
**WifiStateMachine.java::SupplicantStartedState:enter**
~~~
public void enter() {
mIsScanMode = false;// 該變量的作用見下文
mNetworkInfo.setIsAvailable(true);
// config_wifi_supplicant_scan_interval用于控制掃描間隔,默認是15000毫秒
int defaultInterval = mContext.getResources().getInteger(R.integer.config_wifi_supplicant_scan_interval);
mSupplicantScanIntervalMs = Settings.Global.getLong(mContext.getContentResolver(),
Settings.Global.WIFI_SUPPLICANT_SCAN_INTERVAL_MS,defaultInterval);
// 向WPAS發送“SCAN_INTERVAL 掃描間隔時間”命令
mWifiNative.setScanInterval((int)mSupplicantScanIntervalMs / 1000);
}
~~~
接著來看DriverStartedState的enter函數。
**WifiStateMachine.java::DriverStartedState:enter**
~~~
public void enter() {
mIsRunning = true; mInDelayedStop = false;
updateBatteryWorkSource(null);
/*
由于藍牙運行在2.4GHz頻率上,所以為了避免wlan和藍牙互相干擾,下面這個函數將告知
wlan driver藍牙是否啟用。如果是,wlan芯片會做適當調整。
*/
mWifiNative.setBluetoothCoexistenceScanMode(mBluetoothConnectionActive);
/*
下面這兩個函數用設置國家碼和頻段。其內部是通過發送消息的方式來觸發WifiNative
setCountryCode和setBand函數被調用。在WifiNative中,這兩個函數都會發送形如
“DRIVER XXX”命令給WPAS。DRIVER命令是Android平臺特有的,用于給wlan driver發送一些
定制的命令。
AOSP源碼中,hardware/broadcom/wlan/bcmdhd/wpa_supplicant_8_lib/driver_cmd_nl80211.c
中的wpa_driver_nl80211_driver_cmd函數可用于處理針對博通wlan driver的“DRIVER XXX”命令。
我們在第4章中沒有介紹相關的命令,不過它們難度并不大。請讀者以上述driver_cmd_nl80211.c
為參考文件,自行分析相關的DRIVER命令。
*/
setCountryCode();setFrequencyBand();
setNetworkDetailedState(DetailedState.DISCONNECTED);
// 下面三個函數都和WPAS中的“DRIVER XXX”命令有關
mWifiNative.stopFilteringMulticastV6Packets();
if (mFilteringMulticastV4Packets.get()){
mWifiNative.startFilteringMulticastV4Packets();
} else {
mWifiNative.stopFilteringMulticastV4Packets();
}
/*
mIsScanMode默認為FALSE。該變量只能通過CMD_SET_SCAN_TYPE消息來修改。mIsScanMode
和4.5.3節“wpa_supplicant_scan分析之一”中提到的ap_scan變量有關,該變量可取值如下。
值為1:表示WPAS來完成AP掃描和選擇的絕大部分工作(包括關聯、EAPOL認證等工作)。
值為0:表示驅動完成AP掃描和選擇的工作。
值為2:和0類似,不過在NDIS(Windows上的網絡設備驅動)中用得較多。
下面代碼中的SCAN_ONLY_MODE對應值為2,而CONNECT_MODE對應值為1。
*/
if (mIsScanMode) {
mWifiNative.setScanResultHandling(SCAN_ONLY_MODE);
mWifiNative.disconnect();
transitionTo(mScanModeState);
} else {
mWifiNative.setScanResultHandling(CONNECT_MODE);
mWifiNative.reconnect();// 發送“RECONNECT”命令給WPAS
mWifiNative.status();// 發送“STATUS”命令給WPAS
transitionTo(mDisconnectedState);// 進入DisconnectedState
}
if (mScreenBroadcastReceived.get() == false) {
PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
handleScreenStateChanged(powerManager.isScreenOn());
} else {
// 發送“DRIVER SETSUSPENDMODE”命令。該命令由Driver廠商提供的庫來實現
mWifiNative.setSuspendOptimizations(mSuspendOptNeedsDisabled == 0 && mUserWantsSuspendOpt.get());
}
mWifiNative.setPowerSave(true);// 和P2P PowerSave有關。本章不討論
// 如果支持P2P,則通過mWifiP2pChannel向WifiP2p模塊發送消息
if (mP2pSupported)
mWifiP2pChannel.sendMessage(WifiStateMachine.CMD_ENABLE_P2P);
}
~~~
上述代碼執行完后,WifiStateMachine將轉入DisconnectedState。由于DisconncedState的父狀態是ConnectModeState,它的enter函數沒有做任何有意義的工作,所以此處只介紹DisconnectedState的enter函數。
**WifiStateMachine.java::DisconnectedState:enter**
~~~
public void enter() {
// 下面這段代碼和P2P有關
if (mTemporarilyDisconnectWifi) {
mWifiP2pChannel.sendMessage(WifiP2pService.DISCONNECT_WIFI_RESPONSE);
return;
}
mFrameworkScanIntervalMs = Settings.Global.getLong(mContext.getContentResolver(),
Settings.Global.WIFI_FRAMEWORK_SCAN_INTERVAL_MS,
mDefaultFrameworkScanIntervalMs);
/*
當系統支持后臺掃描時,如果手機屏幕關閉,則設置mEnableBackgroudScan為true以啟動后臺掃描。
mScanResultIsPending用于表示WifiService是否在等待掃描請求的結果。由于啟動后臺掃描的時候
會先取消上一次的掃描請求,所以如果mScanResultIsPending為true的話,則先不啟用后臺掃描。
*/
if (mEnableBackgroundScan){
if (!mScanResultIsPending) {
mWifiNative.enableBackgroundScan(true);
}
else {// 設置定時掃描任務。到時間后,AlarmManager將發送一個"ACTION_START_SCAN"Intent
// 而WifiStateMachine對該Intent的處理就是調用startScan函數
setScanAlarm(true);
}
/*
如果當前沒有P2P連接,并且沒有之前保存的AP信息,則發送CMD_NO_NETWORKS_PERIODIC_SCAN消息
以觸發掃描。
*/
if (!mP2pConnected.get() && mWifiConfigStore.getConfiguredNetworks().size() == 0)
sendMessageDelayed(obtainMessage(CMD_NO_NETWORKS_PERIODIC_SCAN,++mPeriodicScanToken, 0), mSupplicantScanIntervalMs);
}
}
~~~
**⑤、setWifiEnabled流程總結**
筆者初次接觸setWifiEnabled函數的流程時,心中的感覺是“一個函數引發的一連串血案”。確實,WifiStateMachine的設計自有獨到之處,但是否有些過于復雜了呢?
沒找到一種合適的方法用圖來描述整個流程,只能用以下文字來描述。
1. WifiService的setWifiEnabled函數將調用WifiStateMachine中的同名函數。在WifiStateMachine中,CMD_LOAD_DRIVER和CMD_START_SUPPLICANT兩個消息將發送給狀態機去執行。WifiStateMachine最初的狀態是DriverUnloadedState。
2. DriverUnloadedState接收到CMD_LOAD_DRIVER消息后將轉入DriverLoadingState。而DriverLoadingState的enter函數將創建一個工作線程來執行WifiNative的loadDriver以加載Wlan驅動。如果driver加載成功,該線程會發送CMD_LOAD_DRIVER_SUCCESS消息給狀態機。
3. DriverLoadingState將在其processMessage中處理CMD_START_SUPPLICANT和CMD_LOAD_DRIVER_SUCCESS消息。其中,DriverLoadingState會延遲對CMD_START_SUPPLICANT的處理。而對于CMD_LOAD_DRIVER_SUCCESS,DriverLoadingState將直接轉入DriverLoadedState。
4. DriverLoadedState將繼續處理CMD_START_SUPPLICANT。在其processMessage中,wpa_supplicant進程將被啟動,并且WifiMonitor將建立WifiService和WPAS的關系。同時,狀態機將轉入SupplicantStartingState。另外,當WifiMonitor成功連接上WPAS后,它將發送一個SUP_CONNECTION_EVENT消息。
5. SupplicantStartingState將處理SUP_CONNECTION_EVENT消息。這些處理包括設置初始化WPS相關的信息、設置WifiState、發送消息給SupplicantStateTracker狀態機、初始化WifiConfigStore等。最后,SupplicantStartingState將轉入DriverStartedState。DriverStartedState的父狀態是SupplicantStartedState。所以這兩個狀態的enter函數均會被調用。
6. SupplicantStartedState在其enter函數中將設置掃描間隔。而DriverStartedState在其enter函數中將完成諸如Country Code、Frequency Band、Bluetooth共存模式等設置工作。有些工作需要發送形如"DRIVER XXX"的命令給WPAS。最后,SupplicantStartedState將轉入DisconnectedState。
7. DisconnectedState的enter函數將被執行(其父狀態ConnectModeState的enter函數沒有完成什么實質性的工作)。該函數主要完成了后臺掃描及定時掃描工作的一些設置。
接著來看第二個關鍵函數startScanActive。
**2、startScanActive函數分析**
startScanActive定義在WifiManager中,它將調用WifiService的startScan函數,而WifiService又會調用WifiStateMachine的startScan,所以本節直接從WifiStateMachine開始。
**WifiStateMachine.java::startScan**
~~~
public void startScan(boolean forceActive) {
// 對于startScanActive來說,forceActive的值為true
sendMessage(obtainMessage(CMD_START_SCAN, forceActive ?
SCAN_ACTIVE : SCAN_PASSIVE, 0));
}
~~~
**①、CMD_START_SCAN處理流程**
WifiStateMachine當前處于DisconnectedState,故其processMessage函數將被調用以處理CMD_START_SCAN消息。相關代碼如下所示。
**WifiStateMachine.java::DisconnectedState:processMessage**
~~~
public boolean processMessage(Message message) {
boolean ret = HANDLED;
switch (message.what) {
......
case CMD_START_SCAN:
// 取消后臺掃描
if (mEnableBackgroundScan) mWifiNative.enableBackgroundScan(false);
ret = NOT_HANDLED; // 注意返回值
break;
case WifiMonitor.SCAN_RESULTS_EVENT:// 掃描完畢后將收到此消息
if (mEnableBackgroundScan && mScanResultIsPending)
mWifiNative.enableBackgroundScan(true);
ret = NOT_HANDLED;// 注意返回值
break;
......
}
return ret;
}
~~~
上述代碼重點展示了CMD_START_SCAN和SCAN_RESULTS_EVENT消息的處理情況。可知DisconnectedState都將返回NOT_HANDLED。如此,其父狀態的processMessage將被調用。沿著圖5-4 WifiStateMachine各狀態層次關系圖并結合代碼,最終DisconnectedState的祖父DriverStartedState將被觸發,相關代碼如下所示。
**WifiStateMachine.java::DriverStartedState:processMessage**
~~~
public boolean processMessage(Message message) {
switch(message.what) {
......
case CMD_START_SCAN:
boolean forceActive = (message.arg1 == SCAN_ACTIVE);
// 對主動掃描(Active Scan)來說,setScanMode將發送“DRIVER SCAN-ACTIVE”命令
if (forceActive && !mSetScanActive){
mWifiNative.setScanMode(forceActive);
}
mWifiNative.scan();// 發送“SCAN”命令給WPAS以觸發掃描
if (forceActive && !mSetScanActive){
mWifiNative.setScanMode(mSetScanActive);
}
mScanResultIsPending = true;// 設置mScanResultIsPending為true
break;
......
}
return HANDLED;
}
~~~
當WPAS掃描完畢后,它將通知WifiMonitor。而WifiMonitor的handleEvent函數將向WifiStateMachine發送SCAN_RESULTS_EVENT消息(請參考5.2.3節介紹的WifiMonitor中的handleEvent函數)。下面來看該消息的處理流程。
**②、SCAN_RESULTS_EVENT處理流程**
WifiStateMachine的狀態是DisconnectedState,由上一節對該狀態processMessage函數的介紹可知,對于SCAN_RESULTS_EVENT消息,DisconnectedState將返回NOT_HANDLED。所以其父狀態ConnectModeState將接著處理此消息。
**WifiStateMachine.java::ConnectModeState:processMessage**
~~~
public boolean processMessage(Message message) {
......
switch(message.what) {
......
case WifiMonitor.SCAN_RESULTS_EVENT:
mWifiNative.setScanResultHandling(CONNECT_MODE);// 設置ap_scan
return NOT_HANDLED;// 仍然返回NOT_HANDLED
......
}
return HANDLED;
}
~~~
返回值NOT_HANDLED將導致ConnectModeState的父狀態DriverStartedState processMessage被調用,不過可惜的是DriverStartedState壓根就不處理該消息。所以還得來看DriverStartedState的父狀態SupplicantStartedState。相關代碼如下所示。
**WifiStateMachine.java::SupplicantStartedState:processMessage**
~~~
public boolean processMessage(Message message) {
......
switch(message.what) {
......
case WifiMonitor.SCAN_RESULTS_EVENT:
// 從WPAS中獲取掃描結果,并保存到mScanResults變量中
setScanResults(mWifiNative.scanResults());
// 發送SCAN_RESULTS_AVAILABLE_ACTION廣播
sendScanResultsAvailableBroadcast();
mScanResultIsPending = false;
break;
......
}
return HANDLED;
}
~~~
和setWifiEnabled函數相比,startScanActive涉及的代碼比較簡單。而且,與該流程相關狀態主要是DisconnectedState以及其祖先狀態。
下面來看最后一個函數即connect的處理流程。
**3、connect函數分析**
從WifiManager開始,相關代碼如下所示。
**WifiManager.java::connect**
~~~
public void connect(WifiConfiguration config, ActionListener listener) {
......// 參數檢查
// 通過AsyncChannel向WifiService發送消息
message的第二個參數為INVALID_NETWORK_ID,其值為-1
mAsyncChannel.sendMessage(CONNECT_NETWORK, WifiConfiguration.INVALID_NETWORK_ID,
putListener(listener), config);
}
~~~
WifiService接收到CONNECT_NETWORK消息后,將直接把它轉發給WifiStateMachine。下面來看WifiStateMachine是如何處理CONNECT_NETWORK的。
**①、CONNECT_NETWORK處理流程**
DisconnectedState的父狀態ConnectModeState將處理CONNECT_NETWORK消息,相關代碼如下所示。
**WifiStateMachine.java::ConnectModeState:processMessage**
~~~
public boolean processMessage(Message message) {
switch(message.what) {
......
case WifiManager.CONNECT_NETWORK:
int netId = message.arg1;// netId為INVALID_NETWORK_ID
WifiConfiguration config = (WifiConfiguration) message.obj;
if (config != null) {// saveNetwork是關鍵函數,見下文分析
NetworkUpdateResult result = mWifiConfigStore.saveNetwork(config);
netId = result.getNetworkId();
}
/*
下面這段代碼中:
selectNetwork:選擇netId對應的無線網絡。該函數的工作和上面的saveNetwork有些類似。
reconnect將發送“RECONNECT”命令給WPAS,而WPAS的處理就是調用
wpa_supplicant_req_scan,讀者可參考4.5.3節。
sendMessage:發送CONNECT_NETWORK消息給SupplicantSTateTracker。
replyToMessage:WifiStateMachine中也有一個AsyncChannel,不過它沒有
連接到任何Handler。該函數將把CONNECT_NETWORK_SUCCEEDED發給
CONNECT_NETWORK消息的發送者(即WifiManager)。
請讀者自行研究WifiManager對CONNECT_NETWORK_SUCCEEDED消息的處理流程。
*/
if (mWifiConfigStore.selectNetwork(netId) && mWifiNative.reconnect()) {
mSupplicantStateTracker.sendMessage(WifiManager.CONNECT_NETWORK);
replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED);
// 切換到DisconnectingState
// 考慮到手機之前可能連接到其他AP,所以此處要先進入DisconnectingState
transitionTo(mDisconnectingState);
} else {
......// 失敗處理
}
break;
......
}
return HANDLED;
}
~~~
上述代碼中,saveNetwork比較關鍵,其代碼如下所示。
**WifiConfigStore.java::saveNetwork**
~~~
NetworkUpdateResult saveNetwork(WifiConfiguration config) {
/*
如果networkId為-1,并且該無線網絡的SSID為空,則不能添加無線網絡。
用戶在WifiSettings選擇的目標無線網絡如果之前已經在wpa_supplicant.conf文件中有信息,
則它的networkid不為-1。
*/
if (config == null || (config.networkId == INVALID_NETWORK_ID && config.SSID == null))
return new NetworkUpdateResult(INVALID_NETWORK_ID);
// 假設本例中該無線網絡是新搜索到的,則newNetwork為true
boolean newNetwork = (config.networkId == INVALID_NETWORK_ID);
/*
addOrUpdateNetworkNative將觸發"ADD_NETWORK"、"SET_NEWTORK id param value"等一系列
命令。這些命令和4.5節開頭介紹的一樣。請讀者自行研究addOrUpdateNetworkNative函數。
*/
NetworkUpdateResult result = addOrUpdateNetworkNative(config);
int netId = result.getNetworkId();
if (newNetwork && netId != INVALID_NETWORK_ID) {
mWifiNative.enableNetwork(netId, false);// 發送“ENABLE_NETWORK id”給WPAS
mConfiguredNetworks.get(netId).status = Status.ENABLED;
}
mWifiNative.saveConfig();
// 發送“SAVE_CONFIG”命令,WPAS將保存wpa_config信息到配置文件
// 發送廣播
sendConfiguredNetworksChangedBroadcast(config, result.isNewNetwork() ?WifiManager.CHANGE_REASON_ADDED : WifiManager.CHANGE_REASON_CONFIG_CHANGE);
return result;
}
~~~
仔細研究selectNetwork和saveNetwork的代碼,感覺selectNetwork重復做了一些saveNetwork已經做過的工作,例如selectNetwork也會調用addOrUpdateNetworkNative函數,筆者覺得這段代碼應該有優化余地。
另外,WPAS在"ENABLE_NETWORK"過程中,會歷經一系列復雜的過程直到加入目標無線網絡(讀者可回顧4.5.3節ENABLE_NETWORK命令處理)。在這個過程中,WPAS的狀態(即wpa_sm狀態機的狀態)也會跟著發生變化。這些變化將觸發WifiMonitor向WifiStateMachine發送SUPPLICANT_STATE_CHANGE_EVENT消息。由于篇幅問題,本章不擬討論這些消息的處理過程。感興趣的讀者不妨學完本章后再來研究它們。
當WPAS成功加入目標無線網絡后,它將發送信息給WifiMonitor例如:
~~~
CTRL-EVENT-CONNECTED-Connection to 00:1e:58:ec:d5:6d completed(reauth)[id=1 id_str=]
~~~
而這個信息將觸發WifiMonitor發送NETWORK_CONNECTION_EVENT消息給WifiStateMachine。所以此處直接來分析NETWORK_CONNECTION_EVENT消息的處理流程即可。
**②、NETWORK_CONNECTION_EVENT消息處理流程**
此時WifiStateMachine處于DisconnectingState,不過它并不處理NETWORK_CONNECTION_EVENT消息,所以該消息最終將由其父狀態ConnectModeState處理。相關代碼如下所示。
**WifiStateMachine.java::ConnectModeState:processMessage**
~~~
public boolean processMessage(Message message) {
......
switch(message.what) {
......
case WifiMonitor.NETWORK_CONNECTION_EVENT:
mLastNetworkId = message.arg1;// arg1指向目標AP的ID
mLastBssid = (String) message.obj;// obj指向目標AP的bssid
mWifiInfo.setBSSID(mLastBssid);
mWifiInfo.setNetworkId(mLastNetworkId);
setNetworkDetailedState(DetailedState.OBTAINING_IPADDR);
// 發送NETWORK_STATE_CHANGED_ACTION廣播
sendNetworkStateChangeBroadcast(mLastBssid);
transitionTo(mObtainingIpState);// 進入ObtainingIpstate狀態
break;
......
}
return HANDLED;
}
~~~
來看ObtaingIpState的enter函數(注意,ObtaingIpState的父狀態是L2ConnectedState,故父狀態的enter函數先執行。L2ConnectedState的enter函數比較簡單,此處略),代碼如下所示。
**WifiStateMachine.java::ObtaingIpState:enter**
~~~
public void enter() {
// 判斷目標AP是否使用靜態IP配置
if (!mWifiConfigStore.isUsingStaticIp(mLastNetworkId)) {
// 本例中的目標AP用得是動態IP配置。所以下面還要創建一個DhcpStateMachine對象
if (mDhcpStateMachine == null){
mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine(mContext, WifiStateMachine.this, mInterfaceName);
}
mDhcpStateMachine.registerForPreDhcpNotification();
// 向DhcpStateMachine發送CMD_START_DHCP消息
mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP);
} else {
......// 靜態IP的處理流程
}
}
~~~
DhcpStateMachine是和DHCP相關的一個狀態機對象,由于其內容比較簡單,本章不詳述。在DhcpStateMachine運行過程中,它將向WifiStateMachine發送兩個消息CMD_PRE_DHCP_ACTION和CMD_POST_DHCP_ACTION,它們均由ObtainingIpState的父狀態L2ConnectedState處理。
**③、CMD_PRE/POST_DHCP_ACTION處理流程**
**WifiStateMachine.java::L2ConnectedState:processMessage**
~~~
public boolean processMessage(Message message) {
......
switch (message.what) {
case DhcpStateMachine.CMD_PRE_DHCP_ACTION:
// 處理dhcp交互之前的一些工作,例如設置藍牙共存模式,關閉p2p powersave等功能等
handlePreDhcpSetup();
// 向DhcpStateMachine發送CMD_PRE_DHCP_ACTION_COMPLETE消息。DhcpStateMachine將
// 啟動dhcpcd進程以從AP那獲取一個IP地址。如果一切順利,它將發送CMD_POST_DHCP_ACTION
// 消息給WifiStateMachine
mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_PRE_DHCP_ACTION_COMPLETE);
break;
case DhcpStateMachine.CMD_POST_DHCP_ACTION:
// 和handlePreDhcpSetup相對應,恢復藍牙共存模式及打開P2P PowerSave功能
handlePostDhcpSetup();
if (message.arg1 == DhcpStateMachine.DHCP_SUCCESS) {
// 下面這個函數將發送LINK_CONFIGURATION_CHANGED_ACTION廣播
// 本章不討論和該廣播相關的處理流程
handleSuccessfulIpConfiguration((DhcpInfoInternal) message.obj);
transitionTo(mVerifyingLinkState);// 轉入VerifyingLinkState
} ......
break;
......
}
return HANDLED;
}
~~~
在L2ConnectedState中,WPAS其實已經連接上了AP。當收到CMD_POST_DHCP_ACTION消息時,手機也從AP那得到了一個IP地址。不過,WifiService還沒有完成其最終的工作,它將轉入VerifyingLinkState。該狀態將會和一個名為WifiWatchdogStateMachine的對象交互。
>[info] 提示 WifiWatchdogStateMachine用于監控無線網絡的信號質量,5.4節將詳細介紹。
先來看VeryfingLinkState的代碼,如下所示。
**WifiStateMachine.java::VeryfingLinkState**
~~~
class VerifyingLinkState extends State {
public void enter() {
setNetworkDetailedState(DetailedState.VERIFYING_POOR_LINK);
mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.VERIFYING_POOR_LINK);
sendNetworkStateChangeBroadcast(mLastBssid);
}
public boolean processMessage(Message message) {
switch (message.what) {
case WifiWatchdogStateMachine.POOR_LINK_DETECTED:
break;
case WifiWatchdogStateMachine.GOOD_LINK_DETECTED:
// 如果WifiWatchdogStateMachine判斷此時無線網絡的信號良好
// 它將發送GOOD_LINK_DETECTED消息給WifiStateMachine
transitionTo(mCaptivePortalCheckState);// 轉入CaptivePortalCheckState
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
}
~~~
CaptivePortalCheckState是Android 4.2新引入的一個狀態。CaptivePortalCheckState和一種名為Captive Portal(強制網絡門戶)認證方法有關。它對應如下一種應用場景:當未認證用戶初次上網時,系統將強制用戶打開某個特定頁面,例如運營商指定的頁面。在該頁面中,用戶必須點擊“同意”按鈕后才能真正使用網絡。該認證方法也叫Portal認證。目前在一些公共場所(如機場、酒店)中被大量使用。關于Capitve Portal的詳細信息,讀者可閱讀參考資料[1]。
>[info] 提示 后面將詳細介紹Android 4.2代碼中對Captive Portal Check的處理流程。
下面來看CaptivePortalCheckState的代碼,如下所示。
**WifiStateMachine.java::CaptivePortalCheckState**
~~~
class CaptivePortalCheckState extends State {
public void enter() {
setNetworkDetailedState(DetailedState.CAPTIVE_PORTAL_CHECK);
// 設置DetailedState為CAPTIVE_PORTAL_CHECK
mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.CAPTIVE_PORTAL_CHECK);
// 發送NETWORK_STATE_CHANGED_ACTION廣播。請讀者記住此處的調用
sendNetworkStateChangeBroadcast(mLastBssid);
}
public boolean processMessage(Message message) {
switch (message.what) {
case CMD_CAPTIVE_CHECK_COMPLETE:
// 檢查完畢。詳情見“Captive Portal Check介紹”
try {
mNwService.enableIpv6(mInterfaceName);
} ......
setNetworkDetailedState(DetailedState.CONNECTED);
mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.CONNECTED);
sendNetworkStateChangeBroadcast(mLastBssid);
transitionTo(mConnectedState);// 終于進入ConnectedState
break;
default:
return NOT_HANDLED;
}
return HANDLED;
}
}
~~~
由上述代碼可知,當Captive Portal檢查完畢后,CaptivePortalCheckState將收到CMD_CAPTIVE_CHECK_COMPLETE消息。在該消息的處理過程中,WifiStateMachine終于轉入ConnectedState,也就是本次旅程的終點。
ConnectedState僅處理POOR_LINK_DETECTE消息,相關代碼比較簡單,不贅述。
下面來總結connect的流程。
**④、connect流程總結**
connect的流程包括如下幾個關鍵點。
* 整個流程起源于WifiManager向WifiStateMachine發送的CONNECT_NETWORK消息。
* ConnectModeState將處理此消息。在其處理過程中,它將發送一系列命令給WPAS,而WPAS將完成802.11規范中所定義的身份認證、關聯、四次握手等工作。ConnectModeState隨之轉入DisconnectingState。
* 當WPAS加入目標無線AP后,它將發送NETWORK_CONNECTION_EVENT給WifiStateMachine。DisconnectingState的父狀態ConnectModeState將處理此消息,具體處理過程比較簡單。最終,WifiStateMachine將轉入ObtaingIpState。
* 在ObtaingIpState的enter函數中,DhcpStateMachine對象將被創建。DhcpStateMachine和dpcpcd有關。相關代碼比較簡單,請讀者自行閱讀。在WifiStateMachine和DhcpStateMachine的交互過程中,DhcpStateMachine將向WifiStateMachine發送CMD_PRE_DHCP_ACTION和CMD_POST_DHCP_ACTION消息。在CMD_POST_DHCP_ACTION消息的處理過程中,WifiStateMachine將轉入VerifyingLinkState。
* VerifyingLinkState將和WifiWatchdogStateMachine交互。WifiWatchdogStateMachine用于監控無線網絡信號的好壞。如果一切正確,它將轉入CaptivePortalCheckState。
* CaptivePortalCheckState用于檢查目標無線網絡提供商是否需要Captive Portal Check。如果一切正常,WifiStateMachine最終將轉入ConnectedState。該狀態就是本章第二條分析路線的終點。
下面介紹本章最后兩個重要知識點,WifiWatchdogStateMachine和Captive Portal Check。
- 前言
- 第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 參考資料說明
- 附錄