/**
* Connect to a network with the given configuration. The network also
* gets added to the supplicant configuration.
*
* For a new network, this function is used instead of a
* sequence of addNetwork(), enableNetwork(), saveConfiguration() and
* reconnect()
*
* @param config the set of variables that describe the configuration,
* contained in a {@link WifiConfiguration} object.
* @param listener for callbacks on success or failure. Can be null.
* @throws IllegalStateException if the WifiManager instance needs to be
* initialized again
*
* @hide
*/
public void connect(WifiConfiguration config, ActionListener listener) {
if (config == null) throw new IllegalArgumentException("config cannot be null");
validateChannel();
// Use INVALID_NETWORK_ID for arg1 when passing a config object
// arg1 is used to pass network id when the network already exists
sAsyncChannel.sendMessage(CONNECT_NETWORK, WifiConfiguration.INVALID_NETWORK_ID,
putListener(listener), config);
}
/**
* Connect to a network with the given networkId.
*
* This function is used instead of a enableNetwork(), saveConfiguration() and
* reconnect()
*
* @param networkId the network id identifiying the network in the
* supplicant configuration list
* @param listener for callbacks on success or failure. Can be null.
* @throws IllegalStateException if the WifiManager instance needs to be
* initialized again
* @hide
*/
public void connect(int networkId, ActionListener listener) {
if (networkId < 0) throw new IllegalArgumentException("Network id cannot be negative");
validateChannel();
sAsyncChannel.sendMessage(CONNECT_NETWORK, networkId, putListener(listener));
}
connect()方法有两种形式,一种接受WifiConfiguration对象,一种接受某个AP的networkID。WifiConfiguration描述了一个Wifi连接的所有配置信息。
/* Client commands are forwarded to state machine */
case WifiManager.CONNECT_NETWORK:
case WifiManager.SAVE_NETWORK: {
WifiConfiguration config = (WifiConfiguration) msg.obj;
int networkId = msg.arg1;
if (msg.what == WifiManager.SAVE_NETWORK) {
Slog.e("WiFiServiceImpl ", "SAVE"
+ " nid=" + Integer.toString(networkId)
+ " uid=" + msg.sendingUid
+ " name="
+ mContext.getPackageManager().getNameForUid(msg.sendingUid));
}
if (msg.what == WifiManager.CONNECT_NETWORK) {
Slog.e("WiFiServiceImpl ", "CONNECT "
+ " nid=" + Integer.toString(networkId)
+ " uid=" + msg.sendingUid
+ " name="
+ mContext.getPackageManager().getNameForUid(msg.sendingUid));
}
if (config != null && isValid(config)) {
if (DBG) Slog.d(TAG, "Connect with config" + config);
mWifiStateMachine.sendMessage(Message.obtain(msg));
} else if (config == null
&& networkId != WifiConfiguration.INVALID_NETWORK_ID) {
if (DBG) Slog.d(TAG, "Connect with networkId" + networkId);
mWifiStateMachine.sendMessage(Message.obtain(msg));
} else {
Slog.e(TAG, "ClientHandler.handleMessage ignoring invalid msg=" + msg);
if (msg.what == WifiManager.CONNECT_NETWORK) {
replyFailed(msg, WifiManager.CONNECT_NETWORK_FAILED,
WifiManager.INVALID_ARGS);
} else {
replyFailed(msg, WifiManager.SAVE_NETWORK_FAILED,
WifiManager.INVALID_ARGS);
}
}
break;
}
ClientHandler中并不做具体的连接动作,主要将CONNECT_NETWORK消息被转发到WifiStateMachine中,通过Wifi状态机来驱动连接和DHCP过程 。ConnectModeState处理:
case WifiManager.CONNECT_NETWORK:
/**
* The connect message can contain a network id passed as arg1 on message or
* or a config passed as obj on message.
* For a new network, a config is passed to create and connect.
* For an existing network, a network id is passed
*/
netId = message.arg1;
config = (WifiConfiguration) message.obj;
mWifiConnectionStatistics.numWifiManagerJoinAttempt++;
boolean updatedExisting = false;
/* Save the network config */
if (config != null) {
// When connecting to an access point, WifiStateMachine wants to update the
// relevant config with administrative data. This update should not be
// considered a 'real' update, therefore lockdown by Device Owner must be
// disregarded.
if (!recordUidIfAuthorized(config, message.sendingUid,
/* onlyAnnotate */ true)) {
logw("Not authorized to update network "
+ " config=" + config.SSID
+ " cnid=" + config.networkId
+ " uid=" + message.sendingUid);
replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
WifiManager.NOT_AUTHORIZED);
break;
}
String configKey = config.configKey(true /* allowCached */);
WifiConfiguration savedConfig =
mWifiConfigStore.getWifiConfiguration(configKey);
if (savedConfig != null) {
// There is an existing config with this netId, but it wasn't exposed
// (either AUTO_JOIN_DELETED or ephemeral; see WifiConfigStore#
// getConfiguredNetworks). Remove those bits and update the config.
config = savedConfig;
logd("CONNECT_NETWORK updating existing config with id=" +
config.networkId + " configKey=" + configKey);
config.ephemeral = false;
config.autoJoinStatus = WifiConfiguration.AUTO_JOIN_ENABLED;
updatedExisting = true;
}
result = mWifiConfigStore.saveNetwork(config, message.sendingUid);
netId = result.getNetworkId();
}
config = mWifiConfigStore.getWifiConfiguration(netId);
if (config == null) {
logd("CONNECT_NETWORK no config for id=" + Integer.toString(netId) + " "
+ mSupplicantStateTracker.getSupplicantStateName() + " my state "
+ getCurrentState().getName());
replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
WifiManager.ERROR);
break;
} else {
String wasSkipped = config.autoJoinBailedDueToLowRssi ? " skipped" : "";
logd("CONNECT_NETWORK id=" + Integer.toString(netId)
+ " config=" + config.SSID
+ " cnid=" + config.networkId
+ " supstate=" + mSupplicantStateTracker.getSupplicantStateName()
+ " my state " + getCurrentState().getName()
+ " uid = " + message.sendingUid
+ wasSkipped);
}
autoRoamSetBSSID(netId, "any");
if (message.sendingUid == Process.WIFI_UID
|| message.sendingUid == Process.SYSTEM_UID) {
// As a sanity measure, clear the BSSID in the supplicant network block.
// If system or Wifi Settings want to connect, they will not
// specify the BSSID.
// If an app however had added a BSSID to this configuration, and the BSSID
// was wrong, Then we would forever fail to connect until that BSSID
// is cleaned up.
clearConfigBSSID(config, "CONNECT_NETWORK");
}
if (deferForUserInput(message, netId, true)) {
break;
} else if (mWifiConfigStore.getWifiConfiguration(netId).userApproved ==
WifiConfiguration.USER_BANNED) {
replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
WifiManager.NOT_AUTHORIZED);
break;
}
mAutoRoaming = WifiAutoJoinController.AUTO_JOIN_IDLE;
/* Tell autojoin the user did try to connect to that network if from settings */
boolean persist =
mWifiConfigStore.checkConfigOverridePermission(message.sendingUid);
mWifiAutoJoinController.updateConfigurationHistory(netId, true, persist);
mWifiConfigStore.setLastSelectedConfiguration(netId);
didDisconnect = false;
if (mLastNetworkId != WifiConfiguration.INVALID_NETWORK_ID
&& mLastNetworkId != netId) {
/** Supplicant will ignore the reconnect if we are currently associated,
* hence trigger a disconnect
*/
didDisconnect = true;
mWifiNative.disconnect();
}
// Make sure the network is enabled, since supplicant will not reenable it
mWifiConfigStore.enableNetworkWithoutBroadcast(netId, false);
if (mWifiConfigStore.selectNetwork(config, /* updatePriorities = */ true,
message.sendingUid) && mWifiNative.reconnect()) {
lastConnectAttemptTimestamp = System.currentTimeMillis();
targetWificonfiguration = mWifiConfigStore.getWifiConfiguration(netId);
/* The state tracker handles enabling networks upon completion/failure */
mSupplicantStateTracker.sendMessage(WifiManager.CONNECT_NETWORK);
replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED);
if (didDisconnect) {
/* Expect a disconnection from the old connection */
transitionTo(mDisconnectingState);
} else if (updatedExisting && getCurrentState() == mConnectedState &&
getCurrentWifiConfiguration().networkId == netId) {
// Update the current set of network capabilities, but stay in the
// current state.
updateCapabilities(config);
} else {
/**
* Directly go to disconnected state where we
* process the connection events from supplicant
**/
transitionTo(mDisconnectedState);
}
} else {
loge("Failed to connect config: " + config + " netId: " + netId);
replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED,
WifiManager.ERROR);
break;
}
break;
通过阅读代码,可知主要的处理动作如下:
/**
* Handle all supplicant events except STATE-CHANGE
* @param event the event type
* @param remainder the rest of the string following the
* event name and " — "
*/
void handleEvent(int event, String remainder) {
if (DBG) {
logDbg("handleEvent " + Integer.toString(event) + " " + remainder);
}
switch (event) {
case DISCONNECTED:
handleNetworkStateChange(NetworkInfo.DetailedState.DISCONNECTED, remainder);
break;
case CONNECTED:
handleNetworkStateChange(NetworkInfo.DetailedState.CONNECTED, remainder);
break;
case SCAN_RESULTS:
mStateMachine.sendMessage(SCAN_RESULTS_EVENT);
break;
case SCAN_FAILED:
mStateMachine.sendMessage(SCAN_FAILED_EVENT);
break;
case UNKNOWN:
if (DBG) {
logDbg("handleEvent unknown: " + Integer.toString(event) + " " + remainder);
}
break;
default:
break;
}
}
private void handleNetworkStateChange(NetworkInfo.DetailedState newState, String data) {
String BSSID = null;
int networkId = -1;
int reason = 0;
int ind = -1;
int local = 0;
Matcher match;
if (newState == NetworkInfo.DetailedState.CONNECTED) {
match = mConnectedEventPattern.matcher(data);
if (!match.find()) {
if (DBG) Log.d(TAG, "handleNetworkStateChange: Couldnt find BSSID in event string");
} else {
BSSID = match.group(1);
try {
networkId = Integer.parseInt(match.group(2));
} catch (NumberFormatException e) {
networkId = -1;
}
}
notifyNetworkStateChange(newState, BSSID, networkId, reason);
} else if (newState == NetworkInfo.DetailedState.DISCONNECTED) {
match = mDisconnectedEventPattern.matcher(data);
if (!match.find()) {
if (DBG) Log.d(TAG, "handleNetworkStateChange: Could not parse disconnect string");
} else {
BSSID = match.group(1);
try {
reason = Integer.parseInt(match.group(2));
} catch (NumberFormatException e) {
reason = -1;
}
try {
local = Integer.parseInt(match.group(3));
} catch (NumberFormatException e) {
local = -1;
}
}
notifyNetworkStateChange(newState, BSSID, local, reason);
}
}
/**
* Send the state machine a notification that the state of Wifi connectivity
* has changed.
* @param newState the new network state
* @param BSSID when the new state is {@link NetworkInfo.DetailedState#CONNECTED},
* this is the MAC address of the access point. Otherwise, it
* is {@code null}.
* @param netId the configured network on which the state change occurred
*/
void notifyNetworkStateChange(NetworkInfo.DetailedState newState,
String BSSID, int netId, int reason) {
if (newState == NetworkInfo.DetailedState.CONNECTED) {
Message m = mStateMachine.obtainMessage(NETWORK_CONNECTION_EVENT,
netId, reason, BSSID);
mStateMachine.sendMessage(m);
} else {
Message m = mStateMachine.obtainMessage(NETWORK_DISCONNECTION_EVENT,
netId, reason, BSSID);
if (DBG) logDbg("WifiMonitor notify network disconnect: "
+ BSSID
+ " reason=" + Integer.toString(reason));
mStateMachine.sendMessage(m);
}
}
WifiMonitor与wpa_s之间的通信是通过socket建立的,如前几篇博客所介绍的那样:WifiMonitor通过建立socket连接与wpa_s通信;每当wpa_s有事件要上报时,WiFiMonitor会解析该event,并转发到Wifi状态机中。
case WifiMonitor.NETWORK_CONNECTION_EVENT:
if (DBG) log("Network connection established");
mLastNetworkId = message.arg1; //成功加入到某无线网络中的AP的NetworkId
mLastBssid = (String) message.obj;
mWifiInfo.setBSSID(mLastBssid);
mWifiInfo.setNetworkId(mLastNetworkId);
sendNetworkStateChangeBroadcast(mLastBssid);
transitionTo(mObtainingIpState);
break;
这里会将这次连接的AP的networkID保存下来,并进入到ObtainingIpState状态去真正触发DHCP动作。L2ConnectedState是ObtainingIpState的父状态,看它的enter()函数:
public void enter() {
mRssiPollToken++;
if (mEnableRssiPolling) {
sendMessage(CMD_RSSI_POLL, mRssiPollToken, 0);
}
if (mNetworkAgent != null) {
loge("Have NetworkAgent when entering L2Connected");
setNetworkDetailedState(DetailedState.DISCONNECTED);
}
setNetworkDetailedState(DetailedState.CONNECTING);//更新当前的网络连接状态
if (!TextUtils.isEmpty(mTcpBufferSizes)) {
mLinkProperties.setTcpBufferSizes(mTcpBufferSizes);
}
mNetworkAgent = new WifiNetworkAgent(getHandler().getLooper(), mContext,
"WifiNetworkAgent", mNetworkInfo, mNetworkCapabilitiesFilter,
mLinkProperties, 60);//此处创建一个NetworkAgent对象用于向ConnectivityService通知相应的网络配置更新操作
// We must clear the config BSSID, as the wifi chipset may decide to roam
// from this point on and having the BSSID specified in the network block would
// cause the roam to faile and the device to disconnect
clearCurrentConfigBSSID("L2ConnectedState");
try {
mIpReachabilityMonitor = new IpReachabilityMonitor(
mInterfaceName,
new IpReachabilityMonitor.Callback() {
@Override
public void notifyLost(InetAddress ip, String logMsg) {
sendMessage(CMD_IP_REACHABILITY_LOST, logMsg);
}
});
} catch (IllegalArgumentException e) {
Log.wtf("Failed to create IpReachabilityMonitor", e);
}
}
@Override
public void enter() {
if (DBG) {
String key = "";
if (getCurrentWifiConfiguration() != null) {
key = getCurrentWifiConfiguration().configKey();
}
log("enter ObtainingIpState netId=" + Integer.toString(mLastNetworkId)
+ " " + key + " "
+ " roam=" + mAutoRoaming
+ " static=" + mWifiConfigStore.isUsingStaticIp(mLastNetworkId)
+ " watchdog= " + obtainingIpWatchdogCount);
}
// Reset link Debouncing, indicating we have successfully re-connected to the AP
// We might still be roaming
linkDebouncing = false;
// Send event to CM & network change broadcast
setNetworkDetailedState(DetailedState.OBTAINING_IPADDR);
// We must clear the config BSSID, as the wifi chipset may decide to roam
// from this point on and having the BSSID specified in the network block would
// cause the roam to faile and the device to disconnect
clearCurrentConfigBSSID("ObtainingIpAddress");
try {
mNwService.enableIpv6(mInterfaceName);
} catch (RemoteException re) {
loge("Failed to enable IPv6: " + re);
} catch (IllegalStateException e) {
loge("Failed to enable IPv6: " + e);
}
if (!mWifiConfigStore.isUsingStaticIp(mLastNetworkId)) {
if (isRoaming()) {
renewDhcp();
} else {
// Remove any IP address on the interface in case we're switching from static
// IP configuration to DHCP. This is safe because if we get here when not
// roaming, we don't have a usable address.
clearIPv4Address(mInterfaceName);
startDhcp();// DHCP过程启动
}
obtainingIpWatchdogCount++;
logd("Start Dhcp Watchdog " + obtainingIpWatchdogCount);
// Get Link layer stats so as we get fresh tx packet counters
getWifiLinkLayerStats(true);
sendMessageDelayed(obtainMessage(CMD_OBTAINING_IP_ADDRESS_WATCHDOG_TIMER,
obtainingIpWatchdogCount, 0), OBTAINING_IP_ADDRESS_GUARD_TIMER_MSEC);
} else {
// stop any running dhcp before assigning static IP
stopDhcp();
StaticIpConfiguration config = mWifiConfigStore.getStaticIpConfiguration(
mLastNetworkId);
if (config.ipAddress == null) {
logd("Static IP lacks address");
sendMessage(CMD_STATIC_IP_FAILURE);
} else {
InterfaceConfiguration ifcg = new InterfaceConfiguration();
ifcg.setLinkAddress(config.ipAddress);
ifcg.setInterfaceUp();
try {
mNwService.setInterfaceConfig(mInterfaceName, ifcg);
if (DBG) log("Static IP configuration succeeded");
DhcpResults dhcpResults = new DhcpResults(config);
sendMessage(CMD_STATIC_IP_SUCCESS, dhcpResults);
} catch (RemoteException re) {
loge("Static IP configuration failed: " + re);
sendMessage(CMD_STATIC_IP_FAILURE);
} catch (IllegalStateException e) {
loge("Static IP configuration failed: " + e);
sendMessage(CMD_STATIC_IP_FAILURE);
}
}
}
}
这里Wifi分了两种连接方式,Static IP和DHCP。这里区分静态和DHCP是通过WifiConfiguration对象来处理的。WifiConfiguration代表一个配置过的AP连接,主要包含IpAssignment(标识上层的连接方式是静态还是DHCP)、AP的networkID、AP的名字等等。
void startDhcp() {
maybeInitDhcpStateMachine(); //确保初始化WifiStateMachine持有的mDhcpStateMachine对象,会传入当前的WifiStateMachine对象,用来回发消息
mDhcpStateMachine.registerForPreDhcpNotification();//注册mRegisteredForPreDhcpNotification字段为true,表明我们在发送DHCP包之前需要做一些准备工作
mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP);//发送开始DHCP的消息
}
DhcpStateMachine是一个小状态机,它主要处理DHCP下的IP地址获取过程,并将获取到的DHCP结果告知WifiStateMachine。看DhcpStateMachine处理该消息的过程:
case CMD_START_DHCP:
if (mRegisteredForPreDhcpNotification) {
/* Notify controller before starting DHCP */
mController.sendMessage(CMD_PRE_DHCP_ACTION);
transitionTo(mWaitBeforeStartState);
} else {
if (runDhcpStart()) {
transitionTo(mRunningState);
}
}
break;
由于我们之前设置了mRegisteredForPreDhcpNotification为true,这里会向WifiStateMachine发送CMD_PRE_DHCP_ACTION消息,告知Wifi状态机做一些DHCP之前的预处理工作。L2ConnectedState处理该消息:
case DhcpStateMachine.CMD_PRE_DHCP_ACTION:
handlePreDhcpSetup();
break;
void handlePreDhcpSetup() {
mDhcpActive = true;
if (!mBluetoothConnectionActive) {
/*
* There are problems setting the Wi-Fi driver's power
* mode to active when bluetooth coexistence mode is
* enabled or sense.
*
* We set Wi-Fi to active mode when
* obtaining an IP address because we've found
* compatibility issues with some routers with low power
* mode.
*
* In order for this active power mode to properly be set,
* we disable coexistence mode until we're done with
* obtaining an IP address. One exception is if we
* are currently connected to a headset, since disabling
* coexistence would interrupt that connection.
*/
// Disable the coexistence mode
mWifiNative.setBluetoothCoexistenceMode(
mWifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED);
}
// Disable power save and suspend optimizations during DHCP
// Note: The order here is important for now. Brcm driver changes
// power settings when we control suspend mode optimizations.
// TODO: Remove this comment when the driver is fixed.
setSuspendOptimizationsNative(SUSPEND_DUE_TO_DHCP, false);
mWifiNative.setPowerSave(false);
// Update link layer stats
getWifiLinkLayerStats(false);
/* P2p discovery breaks dhcp, shut it down in order to get through this */
Message msg = new Message();
msg.what = WifiP2pServiceImpl.BLOCK_DISCOVERY;
msg.arg1 = WifiP2pServiceImpl.ENABLED;
msg.arg2 = DhcpStateMachine.CMD_PRE_DHCP_ACTION_COMPLETE;
msg.obj = mDhcpStateMachine;
mWifiP2pChannel.sendMessage(msg);
}
从注释可知,为了保证Wifi DHCP过程的顺利进行,对Bluetooth、Power和P2p都做了处理工作,其中:蓝牙会Disable the coexistence mode;停止p2p的discovery过程,防止影响DHCP流程。MD_PRE_DHCP_ACTION_COMPLETE消息会在WifiP2pServiceImpl设置完p2p部分后,被转发到DhcpStateMachine,告知预处理工作已经结束,可以进行DHCP了。看DhcpStateMachine中的消息处理:
case CMD_PRE_DHCP_ACTION_COMPLETE:
if (runDhcpStart()) {
transitionTo(mRunningState);
} else {
transitionTo(mPollingState);
}
break;
private boolean runDhcpStart() {
/* Stop any existing DHCP daemon before starting new */
NetworkUtils.stopDhcp(mInterfaceName);//在启动新的DHCP流程之前,停止当前正在进行的DHCP过程
mDhcpResults = null;
if (DBG) Log.d(TAG, "DHCP request on " + mInterfaceName);
if (!NetworkUtils.startDhcp(mInterfaceName) || !dhcpSucceeded()) {
Log.e(TAG, "DHCP request failed on " + mInterfaceName + ": " +
NetworkUtils.getDhcpError());
mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_FAILURE, 0)
.sendToTarget();
return false;
}
return true;
}
调用NetworkUtils.startDhcp()方法启动DHCP流程去获取IP地址,调用dhcpSucceeded()方法获取该次DHCP的DhcpResults对象,它包含了IP地址、网关、DNS等等地址信息。
private boolean dhcpSucceeded() {
DhcpResults dhcpResults = new DhcpResults();
if (!NetworkUtils.getDhcpResults(mInterfaceName, dhcpResults)) {
return false;
}
if (DBG) Log.d(TAG, "DHCP results found for " + mInterfaceName);
long leaseDuration = dhcpResults.leaseDuration; //int to long conversion
//Sanity check for renewal
if (leaseDuration >= 0) {
//TODO: would be good to notify the user that his network configuration is
//bad and that the device cannot renew below MIN_RENEWAL_TIME_SECS
if (leaseDuration < MIN_RENEWAL_TIME_SECS) {
leaseDuration = MIN_RENEWAL_TIME_SECS;
}
//Do it a bit earlier than half the lease duration time
//to beat the native DHCP client and avoid extra packets
//48% for one hour lease time = 29 minutes
mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime() +
leaseDuration * 480, //in milliseconds
mDhcpRenewalIntent);
} else {
//infinite lease time, no renewal needed
}
// Fill in any missing fields in dhcpResults from the previous results.
// If mDhcpResults is null (i.e. this is the first server response),
// this is a noop.
dhcpResults.updateFromDhcpRequest(mDhcpResults);
mDhcpResults = dhcpResults;
mController.obtainMessage(CMD_POST_DHCP_ACTION, DHCP_SUCCESS, 0, dhcpResults)
.sendToTarget();
return true;
}
如果getDhcpResults()函数执行成功,DhcpStateMachine就会发送CMD_POST_DHCP_ACTION消息给WifiStateMachine,状态位是DHCP_SUCCESS,并附加runDhcp()获取到的DhcpResult对象;失败时则附加DHCP_FAILURE。L2ConnectedState处理CMD_POST_DHCP_ACTION消息:
case DhcpStateMachine.CMD_POST_DHCP_ACTION:
handlePostDhcpSetup();
if (message.arg1 == DhcpStateMachine.DHCP_SUCCESS) {
if (DBG) log("DHCP successful");
handleIPv4Success((DhcpResults) message.obj, DhcpStateMachine.DHCP_SUCCESS);
// We advance to mConnectedState because handleIPv4Success will call
// updateLinkProperties, which then sends CMD_IP_CONFIGURATION_SUCCESSFUL.
} else if (message.arg1 == DhcpStateMachine.DHCP_FAILURE) {
mWifiLogger.captureBugReportData(WifiLogger.REPORT_REASON_DHCP_FAILURE);
if (DBG) {
int count = -1;
WifiConfiguration config = getCurrentWifiConfiguration();
if (config != null) {
count = config.numConnectionFailures;
}
log("DHCP failure count=" + count);
}
handleIPv4Failure(DhcpStateMachine.DHCP_FAILURE);
// As above, we transition to mDisconnectingState via updateLinkProperties.
}
break;
首先调用handlePostDhcpSetup()重置之前所做的预处理动作;再处理附加的状态位,成功则调用handleIPv4Success()更新网络的配置信息:
private void handleIPv4Success(DhcpResults dhcpResults, int reason) {
if (PDBG) {
logd("handleIPv4Success <" + dhcpResults.toString() + ">");
logd("link address " + dhcpResults.ipAddress);
}
Inet4Address addr;
synchronized (mDhcpResultsLock) {
mDhcpResults = dhcpResults;//保存当前的DHCP结果
addr = (Inet4Address) dhcpResults.ipAddress.getAddress();
}
if (isRoaming()) {
int previousAddress = mWifiInfo.getIpAddress();
int newAddress = NetworkUtils.inetAddressToInt(addr);
if (previousAddress != newAddress) {
logd("handleIPv4Success, roaming and address changed" +
mWifiInfo + " got: " + addr);
}
}
mWifiInfo.setInetAddress(addr);
mWifiInfo.setMeteredHint(dhcpResults.hasMeteredHint());
updateLinkProperties(reason);//更新网络配置信息
}
updateLinkProperties()更新当前的网络配置信息:
private void updateLinkProperties(int reason) {
LinkProperties newLp = makeLinkProperties();//根据DHCP的结果创建新的LinkProperties对象,用于对比DHCP前后的网络配置是否已经改变
final boolean linkChanged = !newLp.equals(mLinkProperties);
final boolean wasProvisioned = isProvisioned(mLinkProperties);
final boolean isProvisioned = isProvisioned(newLp);
// TODO: Teach LinkProperties how to understand static assignment
// and simplify all this provisioning change detection logic by
// unifying it under LinkProperties.compareProvisioning().
final boolean lostProvisioning =
(wasProvisioned && !isProvisioned) ||
(mLinkProperties.hasIPv4Address() && !newLp.hasIPv4Address()) ||
(mLinkProperties.isIPv6Provisioned() && !newLp.isIPv6Provisioned());
final DetailedState detailedState = getNetworkDetailedState();
if (linkChanged) { //网络配置信息改变
if (DBG) {
log("Link configuration changed for netId: " + mLastNetworkId
+ " old: " + mLinkProperties + " new: " + newLp);
}
mLinkProperties = newLp;//将新的配置信息保存到该字段中
if (mIpReachabilityMonitor != null) {
mIpReachabilityMonitor.updateLinkProperties(mLinkProperties);
}
if (mNetworkAgent != null) mNetworkAgent.sendLinkProperties(mLinkProperties);//通过NetworkAgent对象通知ConnectifyService更新网络配置信息
}
if (lostProvisioning) {
log("Lost IP layer provisioning!" +
" was: " + mLinkProperties +
" now: " + newLp);
}
// If we just configured or lost IP configuration, do the needful.
// We don't just call handleSuccessfulIpConfiguration() or handleIpConfigurationLost()
// here because those should only be called if we're attempting to connect or already
// connected, whereas updateLinkProperties can be called at any time.
switch (reason) {
case DhcpStateMachine.DHCP_SUCCESS:
case CMD_STATIC_IP_SUCCESS:
// IPv4 provisioning succeded. Advance to connected state.
sendMessage(CMD_IP_CONFIGURATION_SUCCESSFUL);
if (!isProvisioned) {
// Can never happen unless DHCP reports success but isProvisioned thinks the
// resulting configuration is invalid (e.g., no IPv4 address, or the state in
// mLinkProperties is out of sync with reality, or there's a bug in this code).
// TODO: disconnect here instead. If our configuration is not usable, there's no
// point in staying connected, and if mLinkProperties is out of sync with
// reality, that will cause problems in the future.
logd("IPv4 config succeeded, but not provisioned");
}
break;
case DhcpStateMachine.DHCP_FAILURE:
// DHCP failed. If we're not already provisioned, or we had IPv4 and now lost it,
// give up and disconnect.
// If we're already provisioned (e.g., IPv6-only network), stay connected.
if (!isProvisioned || lostProvisioning) {
sendMessage(CMD_IP_CONFIGURATION_LOST);
} else {
// DHCP failed, but we're provisioned (e.g., if we're on an IPv6-only network).
sendMessage(CMD_IP_CONFIGURATION_SUCCESSFUL);
// To be sure we don't get stuck with a non-working network if all we had is
// IPv4, remove the IPv4 address from the interface (since we're using DHCP,
// and DHCP failed). If we had an IPv4 address before, the deletion of the
// address will cause a CMD_UPDATE_LINKPROPERTIES. If the IPv4 address was
// necessary for provisioning, its deletion will cause us to disconnect.
//
// This shouldn't be needed, because on an IPv4-only network a DHCP failure will
// have empty DhcpResults and thus empty LinkProperties, and isProvisioned will
// not return true if we're using DHCP and don't have an IPv4 default route. So
// for now it's only here for extra redundancy. However, it will increase
// robustness if we move to getting IPv4 routes from netlink as well.
loge("DHCP failure: provisioned, clearing IPv4 address.");
if (!clearIPv4Address(mInterfaceName)) {
sendMessage(CMD_IP_CONFIGURATION_LOST);
}
}
break;
case CMD_STATIC_IP_FAILURE:
// Static configuration was invalid, or an error occurred in applying it. Give up.
sendMessage(CMD_IP_CONFIGURATION_LOST);
break;
case CMD_UPDATE_LINKPROPERTIES:
// IP addresses, DNS servers, etc. changed. Act accordingly.
if (lostProvisioning) {
// We no longer have a usable network configuration. Disconnect.
sendMessage(CMD_IP_CONFIGURATION_LOST);
} else if (!wasProvisioned && isProvisioned) {
// We have a usable IPv6-only config. Advance to connected state.
sendMessage(CMD_IP_CONFIGURATION_SUCCESSFUL);
}
if (linkChanged && getNetworkDetailedState() == DetailedState.CONNECTED) {
// If anything has changed and we're already connected, send out a notification.
sendLinkConfigurationChangedBroadcast();
}
break;
}
}
case CMD_IP_CONFIGURATION_SUCCESSFUL:
handleSuccessfulIpConfiguration();
sendConnectedState();
transitionTo(mConnectedState);
private void handleSuccessfulIpConfiguration() {
mLastSignalLevel = -1; // Force update of signal strength
WifiConfiguration c = getCurrentWifiConfiguration();//获取代表当前连接的网络的WifiConfiguration对象
if (c != null) {
// Reset IP failure tracking
c.numConnectionFailures = 0;
//重置numConnectionFailures字段,因为在WifiConfigStore中,会根据numConnectionFailures字段判断当前网络的连接失败次数,默认失败10次时,会终止重连操作
// Tell the framework whether the newly connected network is trusted or untrusted.
updateCapabilities(c);
}
if (c != null) {
ScanResult result = getCurrentScanResult();
if (result == null) {
logd("WifiStateMachine: handleSuccessfulIpConfiguration and no scan results" +
c.configKey());
} else {
// Clear the per BSSID failure count
result.numIpConfigFailures = 0;
// Clear the WHOLE BSSID blacklist, which means supplicant is free to retry
// any BSSID, even though it may already have a non zero ip failure count,
// this will typically happen if the user walks away and come back to his arrea
// TODO: implement blacklisting based on a timer, i.e. keep BSSID blacklisted
// in supplicant for a couple of hours or a day
mWifiConfigStore.clearBssidBlacklist();
}
}
}
/**
* The link properties of the wifi interface.
* Do not modify this directly; use updateLinkProperties instead.
*/
private LinkProperties mLinkProperties; //保存当前网络连接的配置信息,包括IP地址集、DNS地址集、路由地址集等
// NOTE: Do not return to clients - use #getWiFiInfoForUid(int)
private WifiInfo mWifiInfo;//描述了一个Wifi连接的状态,通过该对象我们可以得知当前Wifi连接的SSID、netId、IP地址、Mac地址等信息
private NetworkInfo mNetworkInfo;//表示一个网络接口(wlan0/eth0)的连接状态,通过该对象可以得知当前Wifi连接处于哪一步、是否连接等状态
最后切换到ConnectedState,至此AP已经连接、IP也已获取到,并且网络状态也已更新。后续的过程貌似没什么大用了。一个Wifi的连接流程到这里就分析完了。