5.3.2 WifiService操作Wi-Fi分析
本节将围绕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将被调用。
(1)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;
- }
提示 由于篇幅原因,本章不讨论状态切换过程中所涉及的各状态的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
- }
(2)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的处理却比较复杂。
(3)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的介绍)。下面就来看该消息的处理流程。
(4)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);
- }
(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]
- public void startScan(boolean forceActive) {
- // 对于startScanActive来说,forceActive的值为true
- sendMessage(obtainMessage(CMD_START_SCAN, forceActive ?
- SCAN_ACTIVE : SCAN_PASSIVE, 0));
- }
(1)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函数)。下面来看该消息的处理流程。
(2)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 process Message被调用,不过可惜的是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的。
(1)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消息的处理流程即可。
(2)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处理。
(3)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的对象交互。
提示 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]。
提示 后面将详细介绍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的流程。
(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。