5.3.2 WifiService操作Wi-Fi分析

本节将围绕setWifiEnabled、startScanActive和connect函数来介绍WifiService的工作流程。先来看setWifiEnabled函数。

1.setWifiEnabled函数分析

WifiService的setWifiEnabled函数将会调用WifiStateMachine的setWifiEnabled,故此处直接来看。

[—>WifiStateMachine.java::setWifiEnabled]

  1. public void setWifiEnabled(boolean enable) {
  2. mLastEnableUid.set(Binder.getCallingUid());
  3. if (enable) {// 发送两条消息
  4. sendMessage(obtainMessage(CMD_LOAD_DRIVER, WIFI_STATE_ENABLING, 0));
  5. sendMessage(CMD_START_SUPPLICANT);
  6. } else {
  7. sendMessage(CMD_STOP_SUPPLICANT);
  8. sendMessage(obtainMessage(CMD_UNLOAD_DRIVER, WIFI_STATE_DISABLED, 0));
  9. }
  10. }

其中,CMD_LOAD_DRIVER和CMD_START_SUPPLICANT消息将交由WifiStateMachine来处理。由于WifiStateMachine此时还处于DriverUnloaded状态,DriverUnloaded的函数processMessage将被调用。

(1)CMD_LOAD_DRIVER处理流程

先来看它对CMD_LOAD_DRIVER的处理,相关代码如下所示。

[—>WifiStateMachine.java::DriverUnloaded:processMessage]

  1. public boolean processMessage(Message message) {
  2. switch (message.what) {
  3. case CMD_LOAD_DRIVER:
  4. transitionTo(mDriverLoadingState);// 转到DriverLoadingState
  5. break;
  6. default: return NOT_HANDLED;
  7. }
  8. return HANDLED;
  9. }

提示 由于篇幅原因,本章不讨论状态切换过程中所涉及的各状态的exit函数。

先执行DriverLoadingState的enter函数,代码如下所示。

[—>WifiStateMachine.java::DriverLoadingState:enter]

  1. class DriverLoadingState extends State {
  2. public void enter() {
  3. final Message message = new Message();
  4. message.copyFrom(getCurrentMessage());
  5. // 复制当前消息,即上面的CMD_LOAD_DRIVER消息
  6. new Thread(new Runnable() {// 单独启动一个线程来加载wlan驱动
  7. public void run() {
  8. mWakeLock.acquire();
  9. switch(message.arg1) {
  10. case WIFI_STATE_ENABLING:// CMD_LOAD_DRIVER携带了此信息
  11. // 该函数内部将发送WIFI_STATE_CHANGED_ACTION广播
  12. setWifiState(WIFI_STATE_ENABLING);
  13. break;
  14. ......
  15. }
  16. // 加载wlan驱动,如果成功则发送CMD_LOAD_DRIVER_SUCCESS消息
  17. if(mWifiNative.loadDriver()) sendMessage(CMD_LOAD_DRIVER_SUCCESS);
  18. else ......// 失败的处理
  19. mWakeLock.release();
  20. }
  21. }).start();
  22. }

由上述代码可知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]

  1. int wifi_load_driver()
  2. {
  3. /*
  4. 该宏定义了wlan driver的文件路径名。在AOSP代码中,没有地方定义该宏。不过Galaxy Note2
  5. 对应的driver文件路径是“/lib/modules/dhd.ko”。
  6. */
  7. #ifdef WIFI_DRIVER_MODULE_PATH
  8. char driver_status[PROPERTY_VALUE_MAX];
  9. int count = 100;
  10. if (is_wifi_driver_loaded()) return 0;
  11.  
  12. /*
  13. DRIVER_MODULE_PATH变量保存了WIFI_DRIVER_MODULE_PATH宏定义的文件路径名。
  14. 如果上面那个宏定义了,此处将通过insmod向内核添加wlan driver。
  15. */
  16. if (insmod(DRIVER_MODULE_PATH, DRIVER_MODULE_ARG) < 0) return -1;
  17.  
  18. /*
  19. FIRMWARE_LOADER变量指向WIFI_FIRMWARE_LOADER宏定义的wlan固件加载程序文件路径名
  20. DRIVER_PROP_NAME的值为“wlan.driver.status”。如果没有指定wlan固件加载程序,
  21. 则直接设置“wlan.driver.status”属性值为“ok”,否则通过“ctrl.start”方式来启动wlan
  22. 固件加载程序。
  23. */
  24. if (strcmp(FIRMWARE_LOADER,"") == 0) property_set(DRIVER_PROP_NAME, "ok");
  25. else property_set("ctl.start", FIRMWARE_LOADER);
  26.  
  27. sched_yield();
  28. while (count-- > 0) {// 判断wlan driver是否加载成功
  29. if (property_get(DRIVER_PROP_NAME, driver_status, NULL)) {
  30. if (strcmp(driver_status, "ok") == 0) return 0;
  31. else if (strcmp(DRIVER_PROP_NAME, "failed") == 0) {
  32. wifi_unload_driver();
  33. return -1;
  34. }
  35. }
  36. usleep(200000);
  37. }
  38. property_set(DRIVER_PROP_NAME, "timeout");
  39. wifi_unload_driver();
  40. return -1;
  41. #else // 如果没有定义WIFI_DRIVER_MODULE_PATH宏,则直接设置“wlan.driver.status”属性值为“ok”
  42. property_set(DRIVER_PROP_NAME, "ok");
  43. return 0;
  44. #endif
  45. }

(2)CMD_LOAD_DRIVER_SUCCESS处理流程

下面来看DriverLoadingState是如何处理CMD_LOAD_DRIVER_SUCCESS消息的。

[—>WifiStateMachine.java::DriverLoadingState:processMessage]

  1. public boolean processMessage(Message message) {
  2. switch (message.what) {
  3. case CMD_LOAD_DRIVER_SUCCESS:
  4. transitionTo(mDriverLoadedState);// 转到DriverLoadedState
  5. break;
  6. case CMD_LOAD_DRIVER_FAILURE:
  7. transitionTo(mDriverFailedState);
  8. break;
  9. ......
  10. case CMD_START_SUPPLICANT:// DriverLoadingState不处理此消息
  11. case ......// 其他消息
  12. deferMessage(message);
  13. // CMD_START_SUPPLICANT消息将放到下一个状态中再去处理
  14. break;
  15. default:
  16. return NOT_HANDLED;
  17. }
  18. return HANDLED;
  19. }
  20. }

由上述代码可知,DriverLoadingState不处理CMD_START_SUPPLICANT消息,而是将其推迟到下一个状态中再去处理。对于CMD_LOAD_DRIVER_SUCCESS,直接转到DriverLoadedState。DriverLoadedState的enter函数仅仅打印一句简单的日志输出,而它对CMD_START_SUPPLICANT的处理却比较复杂。

(3)CMD_START_SUPPLICANT处理流程

CMD_START_SUPPLICANT消息将在DriverLoaded状态中得到处理,相关代码如下所示。

[—>WifiStateMachine.java::DriverLoadedState:processMessage]

  1. public boolean processMessage(Message message) {
  2. switch(message.what) {
  3. ......
  4. case CMD_START_SUPPLICANT:
  5. try {// 加载wlan固件。使用了netd的SoftAp命令,可参考2.3.8节
  6. mNwService.wifiFirmwareReload(mInterfaceName, "STA");
  7. } ......
  8. try {// 下面这两个函数对应netd的InterfaceCmd命令。可参考2.3.3节
  9. mNwService.setInterfaceDown(mInterfaceName);
  10. mNwService.setInterfaceIpv6PrivacyExtensions
  11. (mInterfaceName, true);
  12. }......
  13. // 启动wpa_supplicant进程。请回顾5.2.3节中WifiNative介绍
  14. if(mWifiNative.startSupplicant(mP2pSupported)){
  15. mWifiMonitor.startMonitoring();// 启动WifiMonitor的Monitor线程
  16. transitionTo(mSupplicantStartingState);
  17. // 转到SupplicantStartingState
  18. }......
  19. break;
  20. case CMD_START_AP:
  21. ......
  22. default:
  23. return NOT_HANDLED;
  24. }
  25. return HANDLED;
  26. }

由上述代码可知DriverLoadedState处理CMD_START_SUPPLICANT消息的结果。

·wlan固件被加载。

·wpa_supplicant进程被创建,并且WifiService通过WifiMonitor和它建立了交互关系。

·WifiStateMachine状态切换至SupplicantStartingState。该状态的enter函数没有开展有意义的工作。

当WifiMonitor成功连接至WPAS进程后,它将发送SUP_CONNECTION_EVENT消息给WifiStateMachine(参考5.2.3节中关于WifiMonitor的介绍)。下面就来看该消息的处理流程。

(4)SUP_CONNECTION_EVENT处理流程

SUP_CONNECTION_EVENT在SupplicantStartingState状态中得到处理,相关代码如下所示。

[—>WifiStateMachine.java::SupplicantStartingState:processMessage]

  1. public boolean processMessage(Message message) {
  2. switch(message.what) {
  3. case WifiMonitor.SUP_CONNECTION_EVENT:
  4. setWifiState(WIFI_STATE_ENABLED);// 发送WIFI_STATE_CHANGED_ACTION广播
  5. mSupplicantRestartCount = 0;
  6. // 发送消息给SupplicantStateTracker状态机。请读者自行研究
  7. mSupplicantStateTracker.sendMessage(CMD_RESET_SUPPLICANT_STATE);
  8. mLastBssid = null; mLastNetworkId =
  9. WifiConfiguration.INVALID_NETWORK_ID;
  10. mLastSignalLevel = -1;
  11. // 设置本机IP地址
  12. mWifiInfo.setMacAddress(mWifiNative.getMacAddress());
  13. mWifiConfigStore.initialize();
  14.  
  15. initializeWpsDetails();
  16. // 初始化和WPS相关的一些内容。本章将略过和WPS/P2P相关的内容
  17.  
  18. // 发送SUPPLICANT_CONNECTION_CHANGE_ACTION广播
  19. sendSupplicantConnectionChangedBroadcast(true);
  20.  
  21. transitionTo(mDriverStartedState);// 转到DriverStartedState
  22. break;
  23. ......
  24. }
  25. return HANDLED;
  26. }

结合HSM知识以及图5-4中WifiStateMachine中各个状态的层级关系,DriverStarted的父状态是SupplicantStarted,所以上述代码中transitionTo(mDriverStartedState)这一句函数调用将导致SupplicantStarted和DriverStarted的enter函数依次被调用。

首先调用的是SupplicantStarted的enter函数,相关代码如下所示。

[—>WifiStateMachine.java::SupplicantStartedState:enter]

  1. public void enter() {
  2. mIsScanMode = false;// 该变量的作用见下文
  3. mNetworkInfo.setIsAvailable(true);
  4. // config_wifi_supplicant_scan_interval用于控制扫描间隔,默认是15000毫秒
  5. int defaultInterval = mContext.getResources().getInteger(
  6. R.integer.config_wifi_supplicant_scan_interval);
  7. mSupplicantScanIntervalMs = Settings.Global.getLong(mContext.getContentResolver(),
  8. Settings.Global.WIFI_SUPPLICANT_SCAN_INTERVAL_MS,defaultInterval);
  9.  
  10. // 向WPAS发送“SCAN_INTERVAL 扫描间隔时间”命令
  11. mWifiNative.setScanInterval((int)mSupplicantScanIntervalMs / 1000);
  12. }

接着来看DriverStartedState的enter函数。

[—>WifiStateMachine.java::DriverStartedState:enter]

  1. public void enter() {
  2. mIsRunning = true; mInDelayedStop = false;
  3. updateBatteryWorkSource(null);
  4. /*
  5. 由于蓝牙运行在2.4GHz频率上,所以为了避免wlan和蓝牙互相干扰,下面这个函数将告知
  6. wlan driver蓝牙是否启用。如果是,wlan芯片会做适当调整。
  7. */
  8. mWifiNative.setBluetoothCoexistenceScanMode(mBluetoothConnectionActive);
  9.  
  10. /*
  11. 下面这两个函数用设置国家码和频段。其内部是通过发送消息的方式来触发WifiNative
  12. setCountryCode和setBand函数被调用。在WifiNative中,这两个函数都会发送形如
  13. “DRIVER XXX”命令给WPAS。DRIVER命令是Android平台特有的,用于给wlan driver发送一些
  14. 定制的命令。
  15. AOSP源码中,hardware/broadcom/wlan/bcmdhd/wpa_supplicant_8_lib/driver_cmd_nl80211.c
  16. 中的wpa_driver_nl80211_driver_cmd函数可用于处理针对博通wlan driver的“DRIVER XXX”命令。
  17. 我们在第4章中没有介绍相关的命令,不过它们难度并不大。请读者以上述driver_cmd_nl80211.c
  18. 为参考文件,自行分析相关的DRIVER命令。
  19. */
  20. setCountryCode();setFrequencyBand();
  21.  
  22. setNetworkDetailedState(DetailedState.DISCONNECTED);
  23.  
  24. // 下面三个函数都和WPAS中的“DRIVER XXX”命令有关
  25. mWifiNative.stopFilteringMulticastV6Packets();
  26. if (mFilteringMulticastV4Packets.get())
  27. mWifiNative.startFilteringMulticastV4Packets();
  28. } else mWifiNative.stopFilteringMulticastV4Packets();
  29.  
  30. /*
  31. mIsScanMode默认为FALSE。该变量只能通过CMD_SET_SCAN_TYPE消息来修改。mIsScanMode
  32. 和4.5.3节“wpa_supplicant_scan分析之一”中提到的ap_scan变量有关,该变量可取值如下。
  33. 值为1:表示WPAS来完成AP扫描和选择的绝大部分工作(包括关联、EAPOL认证等工作)。
  34. 值为0:表示驱动完成AP扫描和选择的工作。
  35. 值为2:和0类似,不过在NDIS(Windows上的网络设备驱动)中用得较多。
  36. 下面代码中的SCAN_ONLY_MODE对应值为2,而CONNECT_MODE对应值为1。
  37. */
  38. if (mIsScanMode) {
  39. mWifiNative.setScanResultHandling(SCAN_ONLY_MODE);
  40. mWifiNative.disconnect();
  41. transitionTo(mScanModeState);
  42. } else {
  43. mWifiNative.setScanResultHandling(CONNECT_MODE);
  44. mWifiNative.reconnect();// 发送“RECONNECT”命令给WPAS
  45. mWifiNative.status();// 发送“STATUS”命令给WPAS
  46. transitionTo(mDisconnectedState);// 进入DisconnectedState
  47. }
  48.  
  49. if (mScreenBroadcastReceived.get() == false) {
  50. PowerManager powerManager = (PowerManager)mContext.getSystemService(
  51. Context.POWER_SERVICE);
  52. handleScreenStateChanged(powerManager.isScreenOn());
  53. } else {
  54. // 发送“DRIVER SETSUSPENDMODE”命令。该命令由Driver厂商提供的库来实现
  55. mWifiNative.setSuspendOptimizations(mSuspendOptNeedsDisabled == 0
  56. && mUserWantsSuspendOpt.get());
  57. }
  58. mWifiNative.setPowerSave(true);// 和P2P PowerSave有关。本章不讨论
  59. // 如果支持P2P,则通过mWifiP2pChannel向WifiP2p模块发送消息
  60. if (mP2pSupported) mWifiP2pChannel.sendMessage(WifiStateMachine.CMD_ENABLE_P2P);
  61. }

上述代码执行完后,WifiStateMachine将转入DisconnectedState。由于DisconncedState的父状态是ConnectModeState,它的enter函数没有做任何有意义的工作,所以此处只介绍DisconnectedState的enter函数。

[—>WifiStateMachine.java::DisconnectedState:enter]

  1. public void enter() {
  2. // 下面这段代码和P2P有关
  3. if (mTemporarilyDisconnectWifi) {
  4. mWifiP2pChannel.sendMessage(WifiP2pService.DISCONNECT_WIFI_RESPONSE);
  5. return;
  6. }
  7.  
  8. mFrameworkScanIntervalMs = Settings.Global.getLong(mContext.getContentResolver(),
  9. Settings.Global.WIFI_FRAMEWORK_SCAN_INTERVAL_MS,
  10. mDefaultFrameworkScanIntervalMs);
  11.  
  12. /*
  13. 当系统支持后台扫描时,如果手机屏幕关闭,则设置mEnableBackgroudScan为true以启动后台扫描。
  14. mScanResultIsPending用于表示WifiService是否在等待扫描请求的结果。由于启动后台扫描的时候
  15. 会先取消上一次的扫描请求,所以如果mScanResultIsPending为true的话,则先不启用后台扫描。
  16. */
  17. if (mEnableBackgroundScan){
  18. if (!mScanResultIsPending) mWifiNative.enableBackgroundScan(true);
  19. else {// 设置定时扫描任务。到时间后,AlarmManager将发送一个"ACTION_START_SCAN"Intent
  20. // 而WifiStateMachine对该Intent的处理就是调用startScan函数
  21. setScanAlarm(true);
  22. }
  23. /*
  24. 如果当前没有P2P连接,并且没有之前保存的AP信息,则发送CMD_NO_NETWORKS_PERIODIC_SCAN消息
  25. 以触发扫描。
  26. */
  27. if (!mP2pConnected.get() && mWifiConfigStore.getConfiguredNetworks().size() == 0)
  28. sendMessageDelayed(obtainMessage(CMD_NO_NETWORKS_PERIODIC_SCAN,
  29. ++mPeriodicScanToken, 0), mSupplicantScanIntervalMs);
  30. }

(5)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]

  1. public void startScan(boolean forceActive) {
  2. // 对于startScanActive来说,forceActive的值为true
  3. sendMessage(obtainMessage(CMD_START_SCAN, forceActive ?
  4. SCAN_ACTIVE : SCAN_PASSIVE, 0));
  5. }

(1)CMD_START_SCAN处理流程

WifiStateMachine当前处于DisconnectedState,故其processMessage函数将被调用以处理CMD_START_SCAN消息。相关代码如下所示。

[—>WifiStateMachine.java::DisconnectedState:processMessage]

  1. public boolean processMessage(Message message) {
  2. boolean ret = HANDLED;
  3. switch (message.what) {
  4. ......
  5. case CMD_START_SCAN:
  6. // 取消后台扫描
  7. if (mEnableBackgroundScan) mWifiNative.enableBackgroundScan(false);
  8. ret = NOT_HANDLED; // 注意返回值
  9. break;
  10. case WifiMonitor.SCAN_RESULTS_EVENT:// 扫描完毕后将收到此消息
  11. if (mEnableBackgroundScan && mScanResultIsPending)
  12. mWifiNative.enableBackgroundScan(true);
  13. ret = NOT_HANDLED;// 注意返回值
  14. break;
  15. ......
  16. }
  17. return ret;
  18. }

上述代码重点展示了CMD_START_SCAN和SCAN_RESULTS_EVENT消息的处理情况。可知DisconnectedState都将返回NOT_HANDLED。如此,其父状态的processMessage将被调用。沿着图5-4 WifiStateMachine各状态层次关系图并结合代码,最终DisconnectedState的祖父DriverStartedState将被触发,相关代码如下所示。

[—>WifiStateMachine.java::DriverStartedState:processMessage]

  1. public boolean processMessage(Message message) {
  2. switch(message.what) {
  3. ......
  4. case CMD_START_SCAN:
  5. boolean forceActive = (message.arg1 == SCAN_ACTIVE);
  6. // 对主动扫描(Active Scan)来说,setScanMode将发送“DRIVER SCAN-ACTIVE”命令
  7. if (forceActive && !mSetScanActive) mWifiNative.setScanMode(forceActive);
  8. mWifiNative.scan();// 发送“SCAN”命令给WPAS以触发扫描
  9.  
  10. if (forceActive && !mSetScanActive)mWifiNative
  11. .setScanMode(mSetScanActive);
  12.  
  13. mScanResultIsPending = true;// 设置mScanResultIsPending为true
  14. break;
  15. ......
  16. }
  17. return HANDLED;
  18. }

当WPAS扫描完毕后,它将通知WifiMonitor。而WifiMonitor的handleEvent函数将向WifiStateMachine发送SCAN_RESULTS_EVENT消息(请参考5.2.3节介绍的WifiMonitor中的handleEvent函数)。下面来看该消息的处理流程。

(2)SCAN_RESULTS_EVENT处理流程

WifiStateMachine的状态是DisconnectedState,由上一节对该状态processMessage函数的介绍可知,对于SCAN_RESULTS_EVENT消息,DisconnectedState将返回NOT_HANDLED。所以其父状态ConnectModeState将接着处理此消息。

[—>WifiStateMachine.java::ConnectModeState:processMessage]

  1. public boolean processMessage(Message message) {
  2. ......
  3. switch(message.what) {
  4. ......
  5. case WifiMonitor.SCAN_RESULTS_EVENT:
  6. mWifiNative.setScanResultHandling(CONNECT_MODE);// 设置ap_scan
  7. return NOT_HANDLED;// 仍然返回NOT_HANDLED
  8. ......
  9. }
  10. return HANDLED;
  11. }

返回值NOT_HANDLED将导致ConnectModeState的父状态DriverStartedState process Message被调用,不过可惜的是DriverStartedState压根就不处理该消息。所以还得来看DriverStartedState的父状态SupplicantStartedState。相关代码如下所示。

[—>WifiStateMachine.java::SupplicantStartedState:processMessage]

  1. public boolean processMessage(Message message) {
  2. ......
  3. switch(message.what) {
  4. ......
  5. case WifiMonitor.SCAN_RESULTS_EVENT:
  6. // 从WPAS中获取扫描结果,并保存到mScanResults变量中
  7. setScanResults(mWifiNative.scanResults());
  8. // 发送SCAN_RESULTS_AVAILABLE_ACTION广播
  9. sendScanResultsAvailableBroadcast();
  10. mScanResultIsPending = false;
  11. break;
  12. ......
  13. }
  14. return HANDLED;
  15. }

和setWifiEnabled函数相比,startScanActive涉及的代码比较简单。而且,与该流程相关状态主要是DisconnectedState以及其祖先状态。

下面来看最后一个函数即connect的处理流程。

3.connect函数分析

从WifiManager开始,相关代码如下所示。

[—>WifiManager.java::connect]

  1. public void connect(WifiConfiguration config, ActionListener listener) {
  2. ......// 参数检查
  3. // 通过AsyncChannel向WifiService发送消息
  4. message的第二个参数为INVALID_NETWORK_ID,其值为-1
  5. mAsyncChannel.sendMessage(CONNECT_NETWORK, WifiConfiguration.INVALID_NETWORK_ID,
  6. putListener(listener), config);
  7. }

WifiService接收到CONNECT_NETWORK消息后,将直接把它转发给WifiStateMachine。下面来看WifiStateMachine是如何处理CONNECT_NETWORK的。

(1)CONNECT_NETWORK处理流程

DisconnectedState的父状态ConnectModeState将处理CONNECT_NETWORK消息,相关代码如下所示。

[—>WifiStateMachine.java::ConnectModeState:processMessage]

  1. public boolean processMessage(Message message) {
  2. switch(message.what) {
  3. ......
  4. case WifiManager.CONNECT_NETWORK:
  5. int netId = message.arg1;// netId为INVALID_NETWORK_ID
  6. WifiConfiguration config = (WifiConfiguration) message.obj;
  7.  
  8. if (config != null) {// saveNetwork是关键函数,见下文分析
  9. NetworkUpdateResult result = mWifiConfigStore.saveNetwork(config);
  10. netId = result.getNetworkId();
  11. }
  12. /*
  13. 下面这段代码中:
  14. selectNetwork:选择netId对应的无线网络。该函数的工作和上面的saveNetwork有些类似。
  15. reconnect将发送“RECONNECT”命令给WPAS,而WPAS的处理就是调用
  16. wpa_supplicant_req_scan,读者可参考4.5.3节。
  17. sendMessage:发送CONNECT_NETWORK消息给SupplicantSTateTracker。
  18. replyToMessage:WifiStateMachine中也有一个AsyncChannel,不过它没有
  19. 连接到任何Handler。该函数将把CONNECT_NETWORK_SUCCEEDED发给
  20. CONNECT_NETWORK消息的发送者(即WifiManager)。
  21. 请读者自行研究WifiManager对CONNECT_NETWORK_SUCCEEDED消息的处理流程。
  22. */
  23. if (mWifiConfigStore.selectNetwork(netId) && mWifiNative.reconnect()) {
  24. mSupplicantStateTracker.sendMessage(WifiManager.CONNECT_NETWORK);
  25. replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED);
  26. // 切换到DisconnectingState
  27. // 考虑到手机之前可能连接到其他AP,所以此处要先进入DisconnectingState
  28. transitionTo(mDisconnectingState);
  29. } else {......// 失败处理}
  30. break;
  31. ......
  32. }
  33. return HANDLED;
  34. }

上述代码中,saveNetwork比较关键,其代码如下所示。

[—>WifiConfigStore.java::saveNetwork]

  1. NetworkUpdateResult saveNetwork(WifiConfiguration config) {
  2. /*
  3. 如果networkId为-1,并且该无线网络的SSID为空,则不能添加无线网络。
  4. 用户在WifiSettings选择的目标无线网络如果之前已经在wpa_supplicant.conf文件中有信息,
  5. 则它的networkid不为-1。
  6. */
  7. if (config == null || (config.networkId == INVALID_NETWORK_ID && config.SSID == null))
  8. return new NetworkUpdateResult(INVALID_NETWORK_ID);
  9.  
  10. // 假设本例中该无线网络是新搜索到的,则newNetwork为true
  11. boolean newNetwork = (config.networkId == INVALID_NETWORK_ID);
  12.  
  13. /*
  14. addOrUpdateNetworkNative将触发"ADD_NETWORK"、"SET_NEWTORK id param value"等一系列
  15. 命令。这些命令和4.5节开头介绍的一样。请读者自行研究addOrUpdateNetworkNative函数。
  16. */
  17. NetworkUpdateResult result = addOrUpdateNetworkNative(config);
  18. int netId = result.getNetworkId();
  19.  
  20. if (newNetwork && netId != INVALID_NETWORK_ID) {
  21. mWifiNative.enableNetwork(netId, false);// 发送“ENABLE_NETWORK id”给WPAS
  22. mConfiguredNetworks.get(netId).status = Status.ENABLED;
  23. }
  24. mWifiNative.saveConfig();
  25. // 发送“SAVE_CONFIG”命令,WPAS将保存wpa_config信息到配置文件
  26.  
  27. // 发送广播
  28. sendConfiguredNetworksChangedBroadcast(config, result.isNewNetwork() ?
  29. WifiManager.CHANGE_REASON_ADDED : WifiManager.CHANGE_REASON_CONFIG_CHANGE);
  30. return result;
  31. }

仔细研究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例如:

  1. CTRL-EVENT-CONNECTED-Connection to 001e58ecd56d completedreauth)[id=1 id_str=]

而这个信息将触发WifiMonitor发送NETWORK_CONNECTION_EVENT消息给WifiStateMachine。所以此处直接来分析NETWORK_CONNECTION_EVENT消息的处理流程即可。

(2)NETWORK_CONNECTION_EVENT消息处理流程

此时WifiStateMachine处于DisconnectingState,不过它并不处理NETWORK_CONNECTION_EVENT消息,所以该消息最终将由其父状态ConnectModeState处理。相关代码如下所示。

[—>WifiStateMachine.java::ConnectModeState:processMessage]

  1. public boolean processMessage(Message message) {
  2. ......
  3. switch(message.what) {
  4. ......
  5. case WifiMonitor.NETWORK_CONNECTION_EVENT:
  6. mLastNetworkId = message.arg1;// arg1指向目标AP的ID
  7. mLastBssid = (String) message.obj;// obj指向目标AP的bssid
  8. mWifiInfo.setBSSID(mLastBssid);
  9. mWifiInfo.setNetworkId(mLastNetworkId);
  10. setNetworkDetailedState(DetailedState.OBTAINING_IPADDR);
  11.  
  12. // 发送NETWORK_STATE_CHANGED_ACTION广播
  13. sendNetworkStateChangeBroadcast(mLastBssid);
  14. transitionTo(mObtainingIpState);// 进入ObtainingIpstate状态
  15. break;
  16. ......
  17. }
  18. return HANDLED;
  19. }

来看ObtaingIpState的enter函数(注意,ObtaingIpState的父状态是L2ConnectedState,故父状态的enter函数先执行。L2ConnectedState的enter函数比较简单,此处略),代码如下所示。

[—>WifiStateMachine.java::ObtaingIpState:enter]

  1. public void enter() {
  2. // 判断目标AP是否使用静态IP配置
  3. if (!mWifiConfigStore.isUsingStaticIp(mLastNetworkId)) {
  4. // 本例中的目标AP用得是动态IP配置。所以下面还要创建一个DhcpStateMachine对象
  5. if (mDhcpStateMachine == null)
  6. mDhcpStateMachine = DhcpStateMachine.makeDhcpStateMachine(
  7. mContext, WifiStateMachine.this, mInterfaceName);
  8. mDhcpStateMachine.registerForPreDhcpNotification();
  9.  
  10. // 向DhcpStateMachine发送CMD_START_DHCP消息
  11. mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP);
  12. } else {......// 静态IP的处理流程 }
  13. }

DhcpStateMachine是和DHCP相关的一个状态机对象,由于其内容比较简单,本章不详述。在DhcpStateMachine运行过程中,它将向WifiStateMachine发送两个消息CMD_PRE_DHCP_ACTION和CMD_POST_DHCP_ACTION,它们均由ObtainingIpState的父状态L2ConnectedState处理。

(3)CMD_PRE/POST_DHCP_ACTION处理流程

处理流程如下。

[—>WifiStateMachine.java::L2ConnectedState:processMessage]

  1. public boolean processMessage(Message message) {
  2. ......
  3. switch (message.what) {
  4. case DhcpStateMachine.CMD_PRE_DHCP_ACTION:
  5. // 处理dhcp交互之前的一些工作,例如设置蓝牙共存模式,关闭p2p powersave等功能等
  6. handlePreDhcpSetup();
  7.  
  8. // 向DhcpStateMachine发送CMD_PRE_DHCP_ACTION_COMPLETE消息。DhcpStateMachine将
  9. // 启动dhcpcd进程以从AP那获取一个IP地址。如果一切顺利,它将发送CMD_POST_DHCP_ACTION
  10. // 消息给WifiStateMachine
  11. mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_PRE_DHCP_ACTION_COMPLETE);
  12. break;
  13. case DhcpStateMachine.CMD_POST_DHCP_ACTION:
  14. // 和handlePreDhcpSetup相对应,恢复蓝牙共存模式及打开P2P PowerSave功能
  15. handlePostDhcpSetup();
  16. if (message.arg1 == DhcpStateMachine.DHCP_SUCCESS) {
  17. // 下面这个函数将发送LINK_CONFIGURATION_CHANGED_ACTION广播
  18. // 本章不讨论和该广播相关的处理流程
  19. handleSuccessfulIpConfiguration((DhcpInfoInternal) message.obj);
  20. transitionTo(mVerifyingLinkState);// 转入VerifyingLinkState
  21. } ......
  22. break;
  23. ......
  24. }
  25. return HANDLED;
  26. }

在L2ConnectedState中,WPAS其实已经连接上了AP。当收到CMD_POST_DHCP_ACTION消息时,手机也从AP那得到了一个IP地址。不过,WifiService还没有完成其最终的工作,它将转入VerifyingLinkState。该状态将会和一个名为WifiWatchdogStateMachine的对象交互。

提示 WifiWatchdogStateMachine用于监控无线网络的信号质量,5.4节将详细介绍。

先来看VeryfingLinkState的代码,如下所示。

[—>WifiStateMachine.java::VeryfingLinkState]

  1. class VerifyingLinkState extends State {
  2. public void enter() {
  3. setNetworkDetailedState(DetailedState.VERIFYING_POOR_LINK);
  4. mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.VERIFYING_POOR_LINK);
  5. sendNetworkStateChangeBroadcast(mLastBssid);
  6. }
  7. public boolean processMessage(Message message) {
  8. switch (message.what) {
  9. case WifiWatchdogStateMachine.POOR_LINK_DETECTED:
  10. break;
  11. case WifiWatchdogStateMachine.GOOD_LINK_DETECTED:
  12. // 如果WifiWatchdogStateMachine判断此时无线网络的信号良好
  13. // 它将发送GOOD_LINK_DETECTED消息给WifiStateMachine
  14. transitionTo(mCaptivePortalCheckState);// 转入CaptivePortalCheckState
  15. break;
  16. default:
  17. return NOT_HANDLED;
  18. }
  19. return HANDLED;
  20. }
  21. }

CaptivePortalCheckState是Android 4.2新引入的一个状态。CaptivePortalCheckState和一种名为Captive Portal(强制网络门户)认证方法有关。它对应如下一种应用场景:当未认证用户初次上网时,系统将强制用户打开某个特定页面,例如运营商指定的页面。在该页面中,用户必须点击“同意”按钮后才能真正使用网络。该认证方法也叫Portal认证。目前在一些公共场所(如机场、酒店)中被大量使用。关于Capitve Portal的详细信息,读者可阅读参考资料[1]。

提示 后面将详细介绍Android 4.2代码中对Captive Portal Check的处理流程。

下面来看CaptivePortalCheckState的代码,如下所示。

[—>WifiStateMachine.java::CaptivePortalCheckState]

  1. class CaptivePortalCheckState extends State {
  2. public void enter() {
  3. setNetworkDetailedState(DetailedState.CAPTIVE_PORTAL_CHECK);
  4. // 设置DetailedState为CAPTIVE_PORTAL_CHECK
  5. mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.CAPTIVE_PORTAL_CHECK);
  6. // 发送NETWORK_STATE_CHANGED_ACTION广播。请读者记住此处的调用
  7. sendNetworkStateChangeBroadcast(mLastBssid);
  8. }
  9. public boolean processMessage(Message message) {
  10. switch (message.what) {
  11. case CMD_CAPTIVE_CHECK_COMPLETE:
  12. // 检查完毕。详情见“Captive Portal Check介绍”
  13. try {
  14. mNwService.enableIpv6(mInterfaceName);
  15. } ......
  16. setNetworkDetailedState(DetailedState.CONNECTED);
  17. mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.CONNECTED);
  18. sendNetworkStateChangeBroadcast(mLastBssid);
  19. transitionTo(mConnectedState);// 终于进入ConnectedState
  20. break;
  21. default:
  22. return NOT_HANDLED;
  23. }
  24. return HANDLED;
  25. }
  26. }

由上述代码可知,当Captive Portal检查完毕后,CaptivePortalCheckState将收到CMD_CAPTIVE_CHECK_COMPLETE消息。在该消息的处理过程中,WifiStateMachine终于转入ConnectedState,也就是本次旅程的终点。

ConnectedState仅处理POOR_LINK_DETECTE消息,相关代码比较简单,不赘述。

下面来总结connect的流程。

(4)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。