Android WiFi 掃描和連接熱點

原文地址:http://blog.csdn.net/lilian0118/article/details/22408287 (找了很久,這是Android 4.4版本的,更多可以去作者博客觀看)

本章主要介紹用戶手動的在Settings中點擊Scan和Connect按鈕,輸入密碼後的連接過程,先看整體流程圖:


WiFi Scan過程分析

當用戶進入Settings點擊Scan後,就會調用到WifiManager的startScan()方法,當然在Settings裏面有設置Scan的定時器,每隔一段時間就會去scan,在Wifi Framework中也有scan的定時器。對照上面的流程圖,來看一下WifiManager的startScan()方法:

[java] view plaincopy
  1. public boolean startScan(WorkSource workSource) {  
  2.     try {  
  3.         mService.startScan(workSource);  
  4.         return true;  
  5.     } catch (RemoteException e) {  
  6.         return false;  
  7.     }  
  8. }  
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. WifiService.java  
  2.     public void startScan(WorkSource workSource) {  
  3.         enforceChangePermission();  
  4.         if (workSource != null) {  
  5.             enforceWorkSourcePermission();  
  6.             // WifiManager currently doesn't use names, so need to clear names out of the  
  7.             // supplied WorkSource to allow future WorkSource combining.  
  8.             workSource.clearNames();  
  9.         }  
  10.         mWifiStateMachine.startScan(Binder.getCallingUid(), workSource);  
  11.     }  

WiFiStateMachine的startScan方法會給自己發送一個CMD_START_SCAN的message,由前面toggle on wifi的知識,這個消息將由DisconnectedState及其父State來處理,從代碼中可以很容易的分析到,CMD_START_SCAN將會被DriverStartedState 來處理,進入到處理的代碼中:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public boolean processMessage(Message message) {  
  2.     switch(message.what) {  
  3.         case CMD_START_SCAN:  
  4.             noteScanStart(message.arg1, (WorkSource) message.obj);  
  5.             startScanNative(WifiNative.SCAN_WITH_CONNECTION_SETUP);  
  6.             break;  

noteScanStart是用於通知電量統計用;startScanNative會向wpa_supplicant發送SCAN的命令,當wpa_suppliant執行完SCAN併成功找到一些AP後,就會給WifiMonitor發送CTRL-EVENT-SCAN-RESULTS的event,WifiMonitor會parse出這個event,並向WifiStateMachine發送SCAN_RESULTS_EVENT消息,WifiStateMachine的SupplicantStartedState會處理這個消息,如下:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. case WifiMonitor.SCAN_RESULTS_EVENT:  
  2.     setScanResults();  
  3.     sendScanResultsAvailableBroadcast();  
  4.     mScanResultIsPending = false;  
  5.     break;  

這裏主要做了兩件事,一是去獲取scanResults,另外會發送一個廣播信息出去,如果有檢測這個廣播的receive收到這個廣播後,就可以調用函數去獲取到scanResults並顯示到listview上面,例如WifiSettings。進入到setScanResults裏面來分析:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. private void setScanResults() {  
  2.   
  3.     while (true) {  
  4.         tmpResults = mWifiNative.scanResults(sid);  
  5.         if (TextUtils.isEmpty(tmpResults)) break;  
  6.         scanResultsBuf.append(tmpResults);  
  7.         scanResultsBuf.append("\n");  
  8.         String[] lines = tmpResults.split("\n");  
  9.         sid = -1;  
  10.         for (int i=lines.length - 1; i >= 0; i--) {  
  11.             if (lines[i].startsWith(END_STR)) {  
  12.                 break;  
  13.             } else if (lines[i].startsWith(ID_STR)) {  
  14.                 try {  
  15.                     sid = Integer.parseInt(lines[i].substring(ID_STR.length())) + 1;  
  16.                 } catch (NumberFormatException e) {  
  17.                     // Nothing to do  
  18.                 }  
  19.                 break;  
  20.             }  
  21.         }  
  22.         if (sid == -1break;  
  23.     }  
  24.   
  25.     scanResults = scanResultsBuf.toString();  
  26.     if (TextUtils.isEmpty(scanResults)) {  
  27.        return;  
  28.     }  
  29.   
  30.     synchronized(mScanResultCache) {  
  31.   
  32.         for (String line : lines) {  
  33.             if (line.startsWith(BSSID_STR)) {  
  34.                 bssid = new String(line.getBytes(), bssidStrLen, line.length() - bssidStrLen);  
  35.             } else if (line.startsWith(FREQ_STR)) {  
  36.                 try {  
  37.                     freq = Integer.parseInt(line.substring(FREQ_STR.length()));  
  38.                 } catch (NumberFormatException e) {  
  39.                     freq = 0;  
  40.                 }  
  41.             }  
  42.             else if (line.startsWith(SSID_STR)) {  
  43.                 wifiSsid = WifiSsid.createFromAsciiEncoded(  
  44.                         line.substring(SSID_STR.length()));  
  45.             } else if (line.startsWith(DELIMITER_STR) || line.startsWith(END_STR)) {  
  46.                 if (bssid != null) {  
  47.                     String ssid = (wifiSsid != null) ? wifiSsid.toString() : WifiSsid.NONE;  
  48.                     String key = bssid + ssid;  
  49.                     ScanResult scanResult = mScanResultCache.get(key);  
  50.                     if (scanResult != null) {  
  51.                         scanResult.level = level;  
  52.                         scanResult.wifiSsid = wifiSsid;  
  53.                         // Keep existing API  
  54.                         scanResult.SSID = (wifiSsid != null) ? wifiSsid.toString() :  
  55.                                 WifiSsid.NONE;  
  56.                         scanResult.capabilities = flags;  
  57.                         scanResult.frequency = freq;  
  58.                         scanResult.timestamp = tsf;  
  59.                     } else {  
  60.                         scanResult =  
  61.                             new ScanResult(  
  62.                                     wifiSsid, bssid, flags, level, freq, tsf);  
  63.                         mScanResultCache.put(key, scanResult);  
  64.                     }  
  65.                     mScanResults.add(scanResult);  
  66.                 }  

這個函數看起來比較複雜,其實仔細分析,它只是循環的parse從WifiNative獲取到AP列表信息,WifiNative.scanResut的返回結果如下,每個AP之間用"===="分割,末尾以“####”來表示結束。

     id=1
     bssid=68:7f:76:d7:1a:6e
     freq=2412
     level=-44
     tsf=1344626243700342
     flags=[WPA2-PSK-CCMP][WPS][ESS]
     ssid=zfdy
     ====
    id=2
    bssid=68:5f:74:d7:1a:6f
    req=5180
    level=-73
    tsf=1344626243700373
    flags=[WPA2-PSK-CCMP][WPS][ESS]
    ssid=zuby
    ####
當所有的結果都被parse出來後,會被存到mScanResults這個ArrayList當中,另外會用bssid+ssid做key值,將這個scanResult存到mScanResultCache這個LRU(最近最少使用) cache當中。當然隨着wifi driver不斷的scan,發現新的AP,mScanResults和mScanResultCache中的數據也在不斷的變化。

當應用程序收到sendScanResultsAvailableBroadcast發送的WifiManager.SCAN_RESULTS_AVAILABLE_ACTION這個broadcast後,就可以去獲取上面提供的mScanResults信息了,獲取過程很簡單,直接複製mScanResults這個ArrayList裏面的成員,然後返回。,值得注意的是,sendScanResultsAvailableBroadcast設置了Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT這個屬性,所以只有動態註冊的broadcastReceive纔會收到這個broadcast。

連接AP的流程

當用戶點擊AP列表中一項並輸入正確的密碼後,就可以開始AP的連接過程了,主要調用的是WifiManager的connect函數,如下:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public void connect(WifiConfiguration config, ActionListener listener) {  
  2.     if (config == nullthrow new IllegalArgumentException("config cannot be null");  
  3.     validateChannel();  
  4.     // Use INVALID_NETWORK_ID for arg1 when passing a config object  
  5.     // arg1 is used to pass network id when the network already exists  
  6.     sAsyncChannel.sendMessage(CONNECT_NETWORK, WifiConfiguration.INVALID_NETWORK_ID,  
  7.             putListener(listener), config);  
  8. }  

其中第一個參數WifiConfiguration是當前需要連接的AP的配置信息,包括SSID、BSSID、密碼以及加密方式等信息;ActionListern作爲callback來通知客戶程序connect方法是否調用成功,這裏的調用成功只是指參數是否正確,並不表示AP是否連接成功。由前面介紹的AsyncChannel的知識,到WifiService中去看看如果處理CONNECT_NETWORK這個消息:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. case WifiManager.CONNECT_NETWORK:  
  2. case WifiManager.SAVE_NETWORK: {  
  3.     WifiConfiguration config = (WifiConfiguration) msg.obj;  
  4.     int networkId = msg.arg1;  
  5.     if (config != null && config.isValid()) {  
  6.         // This is restricted because there is no UI for the user to  
  7.         // monitor/control PAC.  
  8.         if (config.proxySettings != ProxySettings.PAC) {  
  9.             if (DBG) Slog.d(TAG, "Connect with config" + config);  
  10.             mWifiStateMachine.sendMessage(Message.obtain(msg));  
  11.         } reak;  
  12. }  

WifiService將這個消息傳遞給WifiStateMachine處理,由前面介紹的Scan的知識,這時候WifiStateMachine的ConnectModeState將處理CONNECT_NETWORK這個消息,代碼如下:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. case WifiManager.CONNECT_NETWORK:  
  2.      
  3.     int netId = message.arg1;  
  4.     config = (WifiConfiguration) message.obj;  
  5.   
  6.     /* Save the network config */  
  7.     if (config != null) {  
  8.         NetworkUpdateResult result = mWifiConfigStore.saveNetwork(config);  
  9.         netId = result.getNetworkId();  
  10.     }  
  11.   
  12.     if (mWifiConfigStore.selectNetwork(netId) &&  
  13.             mWifiNative.reconnect()) {  
  14.         /* The state tracker handles enabling networks upon completion/failure */  
  15.         mSupplicantStateTracker.sendMessage(WifiManager.CONNECT_NETWORK);  
  16.         replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED);  
  17.         /* Expect a disconnection from the old connection */  
  18.         transitionTo(mDisconnectingState);  
  19.     }  
  20.     break;  

這裏主要調用了下面幾個函數來進行AP的連接:WifiConfigStore.saveNetwok(config)將AP的配置信息寫入到wpa_supplicant.conf中;WifiConfigStore.selectNetwork(netId)用於enable即將要連接的AP,而disable掉其它的AP;WifiNative.reconnect()發起重新連接的請求給wpa_supplicant。接着transition到DisconnectingState中,來看看DisconnectingState,這個狀態code比較少:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. class DisconnectingState extends State {  
  2.     @Override  
  3.     public boolean processMessage(Message message) {  
  4.         switch (message.what) {  
  5.             case CMD_SET_OPERATIONAL_MODE:  
  6.                 if (message.arg1 != CONNECT_MODE) {  
  7.                     deferMessage(message);  
  8.                 }  
  9.                 break;  
  10.             case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:  
  11.                 /* If we get a SUPPLICANT_STATE_CHANGE_EVENT before NETWORK_DISCONNECTION_EVENT 
  12.                  * we have missed the network disconnection, transition to mDisconnectedState 
  13.                  * and handle the rest of the events there 
  14.                  */  
  15.                 deferMessage(message);  
  16.                 handleNetworkDisconnect();  
  17.                 transitionTo(mDisconnectedState);  
  18.                 break;  
  19.             default:  
  20.                 return NOT_HANDLED;  
  21.         }  
  22.         return HANDLED;  
  23.     }  
  24. }  

當執行完WifiNative.reconnect(),wpa_supplicant會不斷的往WifiMonitor發送包括CTRL-EVENT-STATE-CHANGE、ASSOCIATING、ASSOCIATED、FOUR_WAY_HANDSHARK、GROUP_HANDSHARK等event,WifiMonitor會不斷的去parse這些event並向WifiStatemachine發送消息,其中一個比較重要的消息就是當wpa_supplicant的狀態改變是會發送WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT,上面的DisconnectiongState 收到這個消息後,會transition到DisconnectedState。

當Wifi和AP之間已經連接成功後,就會收到wpa_supplicant發送上來的CTRL-EVENT-CONNECTED這個event,WifiMonitor收到這個消息後,會向WifiStateMachine發送NETWORK_CONNECTION_EVENT表示已經和AP之間成功的連線,WifiStateMachine的ConnectModeState會來處理這個消息,代碼如下:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. case WifiMonitor.NETWORK_CONNECTION_EVENT:  
  2.     if (DBG) log("Network connection established");  
  3.     mLastNetworkId = message.arg1;  
  4.     mLastBssid = (String) message.obj;  
  5.   
  6.     mWifiInfo.setBSSID(mLastBssid);  
  7.     mWifiInfo.setNetworkId(mLastNetworkId);  
  8.     /* send event to CM & network change broadcast */  
  9.     setNetworkDetailedState(DetailedState.OBTAINING_IPADDR);  
  10.     sendNetworkStateChangeBroadcast(mLastBssid);  
  11.     transitionTo(mObtainingIpState);  
  12.     break;  

WifiStateMachine處理完這個消息後,會跳轉到ObtainingIpState,進到到ObtainingIpState的enter函數看看:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. class ObtainingIpState extends State {  
  2.     @Override  
  3.     public void enter() {  
  4.         if (!mWifiConfigStore.isUsingStaticIp(mLastNetworkId)) {  
  5.             // TODO: If we're switching between static IP configuration and DHCP, remove the  
  6.             // static configuration first.  
  7.             startDhcp();  
  8.         } else {  
  9.             // stop any running dhcp before assigning static IP  
  10.             stopDhcp();  
  11.             DhcpResults dhcpResults = new DhcpResults(  
  12.                     mWifiConfigStore.getLinkProperties(mLastNetworkId));  
  13.             InterfaceConfiguration ifcg = new InterfaceConfiguration();  
  14.             Iterator<LinkAddress> addrs =  
  15.                     dhcpResults.linkProperties.getLinkAddresses().iterator();  
  16.             if (!addrs.hasNext()) {  
  17.                 loge("Static IP lacks address");  
  18.                 sendMessage(CMD_STATIC_IP_FAILURE);  
  19.             } else {  
  20.                 ifcg.setLinkAddress(addrs.next());  
  21.                 ifcg.setInterfaceUp();  
  22.                 try {  
  23.                     mNwService.setInterfaceConfig(mInterfaceName, ifcg);  
  24.                     if (DBG) log("Static IP configuration succeeded");  
  25.                     sendMessage(CMD_STATIC_IP_SUCCESS, dhcpResults);  
  26.                 } catch (RemoteException re) {  
  27.                     loge("Static IP configuration failed: " + re);  
  28.                     sendMessage(CMD_STATIC_IP_FAILURE);  
  29.                 } catch (IllegalStateException e) {  
  30.                     loge("Static IP configuration failed: " + e);  
  31.                     sendMessage(CMD_STATIC_IP_FAILURE);  
  32.                 }  
  33.             }  
  34.         }  
  35.     }  

ObtainingIpState就是獲取IP的狀態,這裏分爲兩種獲取IP的方式,一種是用戶靜態配置的,另一種是通過DHCP動態分配。這裏只看動態分配的,進到到startDhcp去分析:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. void startDhcp() {  
  2.     if (mDhcpStateMachine == null) {  
  3.         mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine(  
  4.                 mContext, WifiStateMachine.this, mInterfaceName);  
  5.   
  6.     }  
  7.     mDhcpStateMachine.registerForPreDhcpNotification();  
  8.     mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP);  
  9. }  

首先去創建DhcpStateMachine的實例,然後向它發送一個CMD_START_DHCP的命令,DhcpStateMachine初始化完畢後,收到這個消息就會馬上向WifiStateMachine發送CMD_PRE_DHCP_ACTION表示DhcpStateMachine馬上就要開始發送discovery或者renew的封包了,來看WifiStateMachine收到這個消息的處理:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. public boolean processMessage(Message message) {  
  2.     switch (message.what) {  
  3.       case DhcpStateMachine.CMD_PRE_DHCP_ACTION:  
  4.           handlePreDhcpSetup();  
  5.           break;  
  6.       case DhcpStateMachine.CMD_POST_DHCP_ACTION:  
  7.           handlePostDhcpSetup();  
  8.           if (message.arg1 == DhcpStateMachine.DHCP_SUCCESS) {  
  9.               if (DBG) log("DHCP successful");  
  10.               handleSuccessfulIpConfiguration((DhcpResults) message.obj);  
  11.               transitionTo(mVerifyingLinkState);  
  12.           } else if (message.arg1 == DhcpStateMachine.DHCP_FAILURE) {  
  13.               if (DBG) log("DHCP failed");  
  14.               handleFailedIpConfiguration();  
  15.               transitionTo(mDisconnectingState);  
  16.           }  
  17.           break;  

在處理完CMD_PRE_DHCP_ACTION後,WifiStateMachine會向DhcpStateMachine發送CMD_PRE_DHCP_ACTION_COMPLETE,用於指示前期準備工作已經做好了,這是就可以開始dhcp的discovery/reponse了,用於兩端來獲取IP,當IP成功獲取後,DhcpStateMachine會給WifiStateMachine發送CMD_POST_DHCP_ACTION消息,其中arg1表示是成功還是失敗,如果成功,就會調用handleSuccessfulIpConfiguration來處理,並transition 到VerifyingLinkState中;如果失敗則會transition到DisconectingState中。這裏只看成功獲取IP的情況,進入到VerifyingLinkState中:

[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. class VerifyingLinkState extends State {  
  2.     @Override  
  3.     public void enter() {  
  4.         log(getName() + " enter");  
  5.         setNetworkDetailedState(DetailedState.VERIFYING_POOR_LINK);  
  6.         mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.VERIFYING_POOR_LINK);  
  7.         sendNetworkStateChangeBroadcast(mLastBssid);  
  8.     }  
  9.     @Override  
  10.     public boolean processMessage(Message message) {  
  11.         switch (message.what) {  
  12.             case WifiWatchdogStateMachine.POOR_LINK_DETECTED:  
  13.                 //stay here  
  14.                 log(getName() + " POOR_LINK_DETECTED: no transition");  
  15.                 break;  
  16.             case WifiWatchdogStateMachine.GOOD_LINK_DETECTED:  
  17.                 log(getName() + " GOOD_LINK_DETECTED: transition to captive portal check");  
  18.                 transitionTo(mCaptivePortalCheckState);  
  19.                 break;  
  20.             default:  
  21.                 if (DBG) log(getName() + " what=" + message.what + " NOT_HANDLED");  
  22.                 return NOT_HANDLED;  
  23.         }  
  24.         return HANDLED;  
  25.     }  
  26. }  

在VerifyingLinkState主要是來驗證當前連接狀況的,主要方式是通過統計信號強度以及丟包率,這些工作是交給WifiWatchdogStateMachine來做的,當WifiAP的信號強度增強或者變弱,會發送兩種消息給WifiStateMachine,一種是WifiWatchdogStateMachine.GOOD_LINK_DETECTED,另一種是WifiWatchdogStateMachine.POOR_LINK_DETECTED。當收到GOOD_LINK_DETECTED消息後,就會跳轉到CaptivePortalCheckState中;當收到的是POOR_LINK_DETECTED,則維持原來的狀態不變。我們跳轉到CaptivePortalCheckState去分析:
[java] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. class CaptivePortalCheckState extends State {  
  2.     @Override  
  3.     public void enter() {  
  4.         log(getName() + " enter");  
  5.         setNetworkDetailedState(DetailedState.CAPTIVE_PORTAL_CHECK);  
  6.         mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.CAPTIVE_PORTAL_CHECK);  
  7.         sendNetworkStateChangeBroadcast(mLastBssid);  
  8.     }  
  9.     @Override  
  10.     public boolean processMessage(Message message) {  
  11.         switch (message.what) {  
  12.             case CMD_CAPTIVE_CHECK_COMPLETE:  
  13.                 log(getName() + " CMD_CAPTIVE_CHECK_COMPLETE");  
  14.                 try {  
  15.                     mNwService.enableIpv6(mInterfaceName);  
  16.                 } catch (RemoteException re) {  
  17.                     loge("Failed to enable IPv6: " + re);  
  18.                 } catch (IllegalStateException e) {  
  19.                     loge("Failed to enable IPv6: " + e);  
  20.                 }  
  21.                 setNetworkDetailedState(DetailedState.CONNECTED);  
  22.                 mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.CONNECTED);  
  23.                 sendNetworkStateChangeBroadcast(mLastBssid);  
  24.                 transitionTo(mConnectedState);  
  25.                 break;  
  26.             default:  
  27.                 return NOT_HANDLED;  
  28.         }  
  29.         return HANDLED;  
  30.     }  
  31. }  

首先會發送CAPTIVE_PORTAL_CHECK的broadcast,這個會被WifiStateTracker接收並處理,然後調用ConnectivityService的接口去處理captive portal相關的內容,與captive portal相關的只是可以參考:http://en.wikipedia.org/wiki/Captive_portal
當ConnectivityService完成captive portal check後,就會給WifiStateMachine發送CMD_CAPTIVE_CHECK_COMPLETE消息,就會跳轉到ConnectedState表示連接過程的結束了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章