ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
本节将围绕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。