本节将围绕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。
- 前言
- 第1章 准备工作
- 1.1 Android系统架构
- 1.2 工具使用
- 1.2.1 Source Insight的使用
- 1.2.2 Eclipse的使用
- 1.2.3 BusyBox的使用
- 1.3 本书资源下载说明
- 第2章 深入理解Netd
- 2.1 概述
- 2.2 Netd工作流程
- 2.2.1 main函数分析
- 2.2.2 NetlinkManager分析
- 2.2.3 CommandListener分析
- 2.2.4 DnsProxyListener分析
- 2.2.5 MDnsSdListener分析
- 2.3 CommandListener中的命令
- 2.3.1 iptables、tc和ip命令
- 2.3.2 CommandListener构造函数和测试工具ndc
- 2.3.3 InterfaceCmd命令
- 2.3.4 IpFwd和FirewallCmd命令
- 2.3.5 ListTtysCmd和PppdCmd命令
- 2.3.6 BandwidthControlCmd和IdletimerControlCmd命令
- 2.3.7 NatCmd命令
- 2.3.8 TetherCmd和SoftapCmd命令
- 2.3.9 ResolverCmd命令
- 2.4 NetworkManagementService介绍
- 2.4.1 create函数详解
- 2.4.2 systemReady函数详解
- 2.5 本章总结和参考资料说明
- 2.5.1 本章总结
- 2.5.2 参考资料说明
- 第3章 Wi-Fi基础知识
- 3.1 概述
- 3.2 无线电频谱和802.11协议的发展历程
- 3.2.1 无线电频谱知识
- 3.2.2 IEEE 802.11发展历程
- 3.3 802.11无线网络技术
- 3.3.1 OSI基本参考模型及相关基本概念
- 3.3.2 802.11知识点导读
- 3.3.3 802.11组件
- 3.3.4 802.11 Service介绍
- 3.3.5 802.11 MAC服务和帧
- 3.3.6 802.11 MAC管理实体
- 3.3.7 无线网络安全技术知识点
- 3.4 Linux Wi-Fi编程API介绍
- 3.4.1 Linux Wireless Extensions介绍
- 3.4.2 nl80211介绍
- 3.5 本章总结和参考资料说明
- 3.5.1 本章总结
- 3.5.2 参考资料说明
- 第4章 深入理解wpa_supplicant
- 4.1 概述
- 4.2 初识wpa_supplicant
- 4.2.1 wpa_supplicant架构
- 4.2.2 wpa_supplicant编译配置
- 4.2.3 wpa_supplicant命令和控制API
- 4.2.4 git的使用
- 4.3 wpa_supplicant初始化流程
- 4.3.1 main函数分析
- 4.3.2 wpa_supplicant_init函数分析
- 4.3.3 wpa_supplicant_add_iface函数分析
- 4.3.4 wpa_supplicant_init_iface函数分析
- 4.4 EAP和EAPOL模块
- 4.4.1 EAP模块分析
- 4.4.2 EAPOL模块分析
- 4.5 wpa_supplicant连接无线网络分析
- 4.5.1 ADD_NETWORK命令处理
- 4.5.2 SET_NETWORK命令处理
- 4.5.3 ENABLE_NETWORK命令处理
- 4.6 本章总结和参考资料说明
- 4.6.1 本章总结
- 4.6.2 参考资料说明
- 第5章 深入理解WifiService
- 5.1 概述
- 5.2 WifiService的创建及初始化
- 5.2.1 HSM和AsyncChannel介绍
- 5.2.2 WifiService构造函数分析
- 5.2.3 WifiStateMachine介绍
- 5.3 加入无线网络分析
- 5.3.1 Settings操作Wi-Fi分析
- 5.3.2 WifiService操作Wi-Fi分析
- 5.4 WifiWatchdogStateMachine介绍
- 5.5 Captive Portal Check介绍
- 5.6 本章总结和参考资料说明
- 5.6.1 本章总结
- 5.6.2 参考资料说明
- 第6章 深入理解Wi-Fi Simple Configuration
- 6.1 概述
- 6.2 WSC基础知识
- 6.2.1 WSC应用场景
- 6.2.2 WSC核心组件及接口
- 6.3 Registration Protocol详解
- 6.3.1 WSC IE和Attribute介绍
- 6.3.2 802.11管理帧WSC IE设置
- 6.3.3 EAP-WSC介绍
- 6.4 WSC代码分析
- 6.4.1 Settings中的WSC处理
- 6.4.2 WifiStateMachine的处理
- 6.4.3 wpa_supplicant中的WSC处理
- 6.4.4 EAP-WSC处理流程分析
- 6.5 本章总结和参考资料说明
- 6.5.1 本章总结
- 6.5.2 参考资料说明
- 第7章 深入理解Wi-Fi P2P
- 7.1 概述
- 7.2 P2P基础知识
- 7.2.1 P2P架构
- 7.2.2 P2P Discovery技术
- 7.2.3 P2P工作流程
- 7.3 WifiP2pSettings和WifiP2pService介绍
- 7.3.1 WifiP2pSettings工作流程
- 7.3.2 WifiP2pService工作流程
- 7.4 wpa_supplicant中的P2P
- 7.4.1 P2P模块初始化
- 7.4.2 P2P Device Discovery流程分析
- 7.4.3 Provision Discovery流程分析
- 7.4.4 GO Negotiation流程分析
- 7.5 本章总结和参考资料说明
- 7.5.1 本章总结
- 7.5.2 参考资料说明
- 第8章 深入理解NFC
- 8.1 概述
- 8.2 NFC基础知识
- 8.2.1 NFC概述
- 8.2.2 NFC R/W运行模式
- 8.2.3 NFC P2P运行模式
- 8.2.4 NFC CE运行模式
- 8.2.5 NCI原理
- 8.2.6 NFC相关规范
- 8.3 Android中的NFC
- 8.3.1 NFC应用示例
- 8.3.2 NFC系统模块
- 8.4 NFC HAL层讨论
- 8.5 本章总结和参考资料说明
- 8.5.1 本章总结
- 8.5.2 参考资料说明
- 第9章 深入理解GPS
- 9.1 概述
- 9.2 GPS基础知识
- 9.2.1 卫星导航基本原理
- 9.2.2 GPS系统组成及原理
- 9.2.3 OMA-SUPL协议
- 9.3 Android中的位置管理
- 9.3.1 LocationManager架构
- 9.3.2 LocationManager应用示例
- 9.3.3 LocationManager系统模块
- 9.4 本章总结和参考资料说明
- 9.4.1 本章总结
- 9.4.2 参考资料说明
- 附录