Android -- Wifi连接流程分析
Android -- Wifi连接流程分析
当我们在Android手机上通过Settings连接一个AP时,间接调用WifiManager的connect()方法:
/** * 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连接的所有配置信息。 WifiManager的ServiceHandler和WifiService的ClientHandler通过异步通道进行通信。所以这里通过AsyncChannel机制,向WifiServiceImpl发送CONNECT_NETWORK消息,可知在WifiServiceImpl::ClientHandler中被处理:
/* 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;
通过阅读代码,可知主要的处理动作如下: - 将connect()传过来的AP信息保存到WifiConfigStore对象中
- 通过WifiConfigStore::selectNetwork()函数更新WifiConfigStore和config的Priority优先级属性,最后更新到wpa_s配置文件中;enable当前要连接的AP,disable其他的AP
- 通过WifiNative::reconnect()函数向wpa_s发送连接指令,连接选定的AP
连接选定的AP是通过调用WifiNative方法向wpa_supplicant发送connect指令,wpa_s通知驱动进行连接;当底层无线连接成功后,framework就能通过WifiMonitor接受到wpa_s上报的event消息:
/** * 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状态机中。 WifiMonitor接收到wpa_s的连接事件时,向WifiStateMachine发送NETWORK_CONNECTION_EVENT消息,通知状态机底层无线已经连接成功,下一步可以获取IP了。
转到WifiStateMachine中,ConnectModeState进行处理:
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); } }
我们再看ObtainingIpState::enter()方法看如何获取获取IP地址: @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的名字等等。 我们主要看动态获取IP的过程。进入DHCP流程之前,会先清除当前的IP地址信息。着重看startDhcp()函数处理:
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; } }
最后会根据DHCP是否成功发送CMD_IP_CONFIGURATION_SUCCESSFUL消息表明IP的配置已经完成(因为我们已经告知了ConnectifyService去更新网络配置,并附加了新的mLinkProperties对象),L2ConnectedState处理CMD_IP_CONFIGURATION_SUCCESSFUL消息: case CMD_IP_CONFIGURATION_SUCCESSFUL: handleSuccessfulIpConfiguration(); sendConnectedState(); transitionTo(mConnectedState);
handleSuccessfulIpConfiguration()函数处理: 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(); } } }
sendConnectedState()主要做一些网络状态的更新操作,发送NETWORK_STATE_CHANGED_ACTION通知当前的网络状态已经变化。此广播会附加WifiStateMachine中三个常用的字段值: /** * 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的连接流程到这里就分析完了。 PS:
4.4之后Android加入了AP评分机制。这个机制根据某个AP的很多配置信息一通处理,最后得出一个score,来表明某个AP的可连接性。它评判的属性有点多,后面在研究,现在本渣还是没有搞明白,囧....
在framework里面,其实屏蔽了很多复杂的操作,比如WifiNative里面的大多数方法都是要通过JNI调用wifi.c,再调到wpa_s。这些中间过程其实比framework的处理要复杂的多,代码也更晦涩。例如runDhcp()动作,有个专门的dhcpcd守护进程进行处理;它的实现跟wpa_s一样,都是很复杂的。即使了解DHCP协议的工作流程,要看懂它的代码实现也很痛苦。Android里还有的PPPoE拨号方式,它的守护进程pppd的实现也很复杂......;要做这方面的定制,对于偏向framework的人来说,是比较痛苦的。
更多相关文章
- Android Handler机制 - MessageQueue如何处理消息
- Android 网络编程基础之简单聊天程序
- Android 9.0网络权限适配
- 深入理解Android消息处理系统 Handler