Android WiFi 扫描和连接热点

本章主要介绍用户手动的在Settings中点击Scan和Connect按钮,输入密码后的连接过程,先看整体流程图:


WiFi Scan过程分析

当用户进入Settings点击Scan后,就会调用到WifiManager的startScan()方法,当然在Settings里面有设置Scan的定时器,每隔一段时间就会去scan,在Wifi Framework中也有scan的定时器。对照上面的流程图,来看一下WifiManager的startScan()方法:

[java] view plain copy
  1. publicbooleanstartScan(WorkSourceworkSource){
  2. try{
  3. mService.startScan(workSource);
  4. returntrue;
  5. }catch(RemoteExceptione){
  6. returnfalse;
  7. }
  8. }
[java] view plain copy 在CODE上查看代码片 派生到我的代码片
  1. WifiService.java
  2. publicvoidstartScan(WorkSourceworkSource){
  3. enforceChangePermission();
  4. if(workSource!=null){
  5. enforceWorkSourcePermission();
  6. //WifiManagercurrentlydoesn'tusenames,soneedtoclearnamesoutofthe
  7. //suppliedWorkSourcetoallowfutureWorkSourcecombining.
  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 plain copy 在CODE上查看代码片 派生到我的代码片
  1. publicbooleanprocessMessage(Messagemessage){
  2. switch(message.what){
  3. caseCMD_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 plain copy 在CODE上查看代码片 派生到我的代码片
  1. caseWifiMonitor.SCAN_RESULTS_EVENT:
  2. setScanResults();
  3. sendScanResultsAvailableBroadcast();
  4. mScanResultIsPending=false;
  5. break;

这里主要做了两件事,一是去获取scanResults,另外会发送一个广播信息出去,如果有检测这个广播的receive收到这个广播后,就可以调用函数去获取到scanResults并显示到listview上面,例如WifiSettings。进入到setScanResults里面来分析:

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

这个函数看起来比较复杂,其实仔细分析,它只是循环的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 plain copy 在CODE上查看代码片 派生到我的代码片
  1. publicvoidconnect(WifiConfigurationconfig,ActionListenerlistener){
  2. if(config==null)thrownewIllegalArgumentException("configcannotbenull");
  3. validateChannel();
  4. //UseINVALID_NETWORK_IDforarg1whenpassingaconfigobject
  5. //arg1isusedtopassnetworkidwhenthenetworkalreadyexists
  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 plain copy 在CODE上查看代码片 派生到我的代码片
  1. caseWifiManager.CONNECT_NETWORK:
  2. caseWifiManager.SAVE_NETWORK:{
  3. WifiConfigurationconfig=(WifiConfiguration)msg.obj;
  4. intnetworkId=msg.arg1;
  5. if(config!=null&&config.isValid()){
  6. //ThisisrestrictedbecausethereisnoUIfortheuserto
  7. //monitor/controlPAC.
  8. if(config.proxySettings!=ProxySettings.PAC){
  9. if(DBG)Slog.d(TAG,"Connectwithconfig"+config);
  10. mWifiStateMachine.sendMessage(Message.obtain(msg));
  11. }reak;
  12. }

WifiService将这个消息传递给WifiStateMachine处理,由前面介绍的Scan的知识,这时候WifiStateMachine的ConnectModeState将处理CONNECT_NETWORK这个消息,代码如下:
[java] view plain copy 在CODE上查看代码片 派生到我的代码片
  1. caseWifiManager.CONNECT_NETWORK:
  2. intnetId=message.arg1;
  3. config=(WifiConfiguration)message.obj;
  4. /*Savethenetworkconfig*/
  5. if(config!=null){
  6. NetworkUpdateResultresult=mWifiConfigStore.saveNetwork(config);
  7. netId=result.getNetworkId();
  8. }
  9. if(mWifiConfigStore.selectNetwork(netId)&&
  10. mWifiNative.reconnect()){
  11. /*Thestatetrackerhandlesenablingnetworksuponcompletion/failure*/
  12. mSupplicantStateTracker.sendMessage(WifiManager.CONNECT_NETWORK);
  13. replyToMessage(message,WifiManager.CONNECT_NETWORK_SUCCEEDED);
  14. /*Expectadisconnectionfromtheoldconnection*/
  15. transitionTo(mDisconnectingState);
  16. }
  17. 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 plain copy 在CODE上查看代码片 派生到我的代码片
  1. classDisconnectingStateextendsState{
  2. @Override
  3. publicbooleanprocessMessage(Messagemessage){
  4. switch(message.what){
  5. caseCMD_SET_OPERATIONAL_MODE:
  6. if(message.arg1!=CONNECT_MODE){
  7. deferMessage(message);
  8. }
  9. break;
  10. caseWifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT:
  11. /*IfwegetaSUPPLICANT_STATE_CHANGE_EVENTbeforeNETWORK_DISCONNECTION_EVENT
  12. *wehavemissedthenetworkdisconnection,transitiontomDisconnectedState
  13. *andhandletherestoftheeventsthere
  14. */
  15. deferMessage(message);
  16. handleNetworkDisconnect();
  17. transitionTo(mDisconnectedState);
  18. break;
  19. default:
  20. returnNOT_HANDLED;
  21. }
  22. returnHANDLED;
  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 plain copy 在CODE上查看代码片 派生到我的代码片
  1. caseWifiMonitor.NETWORK_CONNECTION_EVENT:
  2. if(DBG)log("Networkconnectionestablished");
  3. mLastNetworkId=message.arg1;
  4. mLastBssid=(String)message.obj;
  5. mWifiInfo.setBSSID(mLastBssid);
  6. mWifiInfo.setNetworkId(mLastNetworkId);
  7. /*sendeventtoCM&networkchangebroadcast*/
  8. setNetworkDetailedState(DetailedState.OBTAINING_IPADDR);
  9. sendNetworkStateChangeBroadcast(mLastBssid);
  10. transitionTo(mObtainingIpState);
  11. break;

WifiStateMachine处理完这个消息后,会跳转到ObtainingIpState,进到到ObtainingIpState的enter函数看看:

[java] view plain copy 在CODE上查看代码片 派生到我的代码片
  1. classObtainingIpStateextendsState{
  2. @Override
  3. publicvoidenter(){
  4. if(!mWifiConfigStore.isUsingStaticIp(mLastNetworkId)){
  5. //TODO:Ifwe'reswitchingbetweenstaticIPconfigurationandDHCP,removethe
  6. //staticconfigurationfirst.
  7. startDhcp();
  8. }else{
  9. //stopanyrunningdhcpbeforeassigningstaticIP
  10. stopDhcp();
  11. DhcpResultsdhcpResults=newDhcpResults(
  12. mWifiConfigStore.getLinkProperties(mLastNetworkId));
  13. InterfaceConfigurationifcg=newInterfaceConfiguration();
  14. Iterator<LinkAddress>addrs=
  15. dhcpResults.linkProperties.getLinkAddresses().iterator();
  16. if(!addrs.hasNext()){
  17. loge("StaticIPlacksaddress");
  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("StaticIPconfigurationsucceeded");
  25. sendMessage(CMD_STATIC_IP_SUCCESS,dhcpResults);
  26. }catch(RemoteExceptionre){
  27. loge("StaticIPconfigurationfailed:"+re);
  28. sendMessage(CMD_STATIC_IP_FAILURE);
  29. }catch(IllegalStateExceptione){
  30. loge("StaticIPconfigurationfailed:"+e);
  31. sendMessage(CMD_STATIC_IP_FAILURE);
  32. }
  33. }
  34. }
  35. }

ObtainingIpState就是获取IP的状态,这里分为两种获取IP的方式,一种是用户静态配置的,另一种是通过DHCP动态分配。这里只看动态分配的,进到到startDhcp去分析:

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

首先去创建DhcpStateMachine的实例,然后向它发送一个CMD_START_DHCP的命令,DhcpStateMachine初始化完毕后,收到这个消息就会马上向WifiStateMachine发送CMD_PRE_DHCP_ACTION表示DhcpStateMachine马上就要开始发送discovery或者renew的封包了,来看WifiStateMachine收到这个消息的处理:

[java] view plain copy 在CODE上查看代码片 派生到我的代码片
  1. publicbooleanprocessMessage(Messagemessage){
  2. switch(message.what){
  3. caseDhcpStateMachine.CMD_PRE_DHCP_ACTION:
  4. handlePreDhcpSetup();
  5. break;
  6. caseDhcpStateMachine.CMD_POST_DHCP_ACTION:
  7. handlePostDhcpSetup();
  8. if(message.arg1==DhcpStateMachine.DHCP_SUCCESS){
  9. if(DBG)log("DHCPsuccessful");
  10. handleSuccessfulIpConfiguration((DhcpResults)message.obj);
  11. transitionTo(mVerifyingLinkState);
  12. }elseif(message.arg1==DhcpStateMachine.DHCP_FAILURE){
  13. if(DBG)log("DHCPfailed");
  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 plain copy 在CODE上查看代码片 派生到我的代码片
  1. classVerifyingLinkStateextendsState{
  2. @Override
  3. publicvoidenter(){
  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. publicbooleanprocessMessage(Messagemessage){
  11. switch(message.what){
  12. caseWifiWatchdogStateMachine.POOR_LINK_DETECTED:
  13. //stayhere
  14. log(getName()+"POOR_LINK_DETECTED:notransition");
  15. break;
  16. caseWifiWatchdogStateMachine.GOOD_LINK_DETECTED:
  17. log(getName()+"GOOD_LINK_DETECTED:transitiontocaptiveportalcheck");
  18. transitionTo(mCaptivePortalCheckState);
  19. break;
  20. default:
  21. if(DBG)log(getName()+"what="+message.what+"NOT_HANDLED");
  22. returnNOT_HANDLED;
  23. }
  24. returnHANDLED;
  25. }
  26. }

在VerifyingLinkState主要是来验证当前连接状况的,主要方式是通过统计信号强度以及丢包率,这些工作是交给WifiWatchdogStateMachine来做的,当WifiAP的信号强度增强或者变弱,会发送两种消息给WifiStateMachine,一种是WifiWatchdogStateMachine.GOOD_LINK_DETECTED,另一种是WifiWatchdogStateMachine.POOR_LINK_DETECTED。当收到GOOD_LINK_DETECTED消息后,就会跳转到CaptivePortalCheckState中;当收到的是POOR_LINK_DETECTED,则维持原来的状态不变。我们跳转到CaptivePortalCheckState去分析:
[java] view plain copy 在CODE上查看代码片 派生到我的代码片
  1. classCaptivePortalCheckStateextendsState{
  2. @Override
  3. publicvoidenter(){
  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. publicbooleanprocessMessage(Messagemessage){
  11. switch(message.what){
  12. caseCMD_CAPTIVE_CHECK_COMPLETE:
  13. log(getName()+"CMD_CAPTIVE_CHECK_COMPLETE");
  14. try{
  15. mNwService.enableIpv6(mInterfaceName);
  16. }catch(RemoteExceptionre){
  17. loge("FailedtoenableIPv6:"+re);
  18. }catch(IllegalStateExceptione){
  19. loge("FailedtoenableIPv6:"+e);
  20. }
  21. setNetworkDetailedState(DetailedState.CONNECTED);
  22. mWifiConfigStore.updateStatus(mLastNetworkId,DetailedState.CONNECTED);
  23. sendNetworkStateChangeBroadcast(mLastBssid);
  24. transitionTo(mConnectedState);
  25. break;
  26. default:
  27. returnNOT_HANDLED;
  28. }
  29. returnHANDLED;
  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表示连接过程的结束了


转自:http://blog.csdn.net/lilian0118/article/details/22408287