Android -- Wifi连接流程分析
当我们在Android手机上连接一个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。
通过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; }
这里CONNECT_NETWORK消息被转发到WifiStateMachine中,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中
- enable即将要连接的AP
- 连接选定的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); } }
向WifiStateMachine发送NETWORK_CONNECTION_EVENT消息,告知底层已经连接成功,可以获取IP了。
ConnectModeState进行处理:
case WifiMonitor.NETWORK_CONNECTION_EVENT: if (DBG) log("Network connection established"); mLastNetworkId = message.arg1; mLastBssid = (String) message.obj; mWifiInfo.setBSSID(mLastBssid); mWifiInfo.setNetworkId(mLastNetworkId); sendNetworkStateChangeBroadcast(mLastBssid); transitionTo(mObtainingIpState); break;
这里会将这次连接的AP的networkID保存下来,进入到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(); } 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); } } } }
这里分了两种连接方式,Static IP和DHCP。这里区分静态和DHCP是通过WifiConfiguration对象来处理的。WifiConfiguration代表一个配置过的AP连接,主要包含IpAssignment(标识上层配置是静态还是DHCP)、AP的networkID、AP的名字等等。进入DHCP流程之前,会先清除当前的IP地址信息:
void startDhcp() { maybeInitDhcpStateMachine(); mDhcpStateMachine.registerForPreDhcpNotification(); mDhcpStateMachine.sendMessage(DhcpStateMachine.CMD_START_DHCP); }
DhcpStateMachine也是一个小状态机,它主要处理DHCP下的IP地址获取:
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;
向WifiStateMachine发送CMD_PRE_DHCP_ACTION消息,告知状态机做一些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. * <p> * 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. * <p> * 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都做了处理工作,CMD_PRE_DHCP_ACTION_COMPLETE消息会在WifiP2pServiceImpl被发送到DhcpStateMachine,告知预处理工作已经结束,可以进行DHCP了:
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); 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地址。如果成功:
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; }
就会发送CMD_POST_DHCP_ACTION消息给WifiStateMachine,并附加runDhcp()获取到的DhcpResult对象。L2ConnectedState处理:
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; 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()更新当前的网络配置信息,并根据DHCP是否成功发送CMD_IP_CONFIGURATION_SUCCESSFUL消息,L2ConnectedState处理:
case CMD_IP_CONFIGURATION_SUCCESSFUL: handleSuccessfulIpConfiguration(); sendConnectedState(); transitionTo(mConnectedState);
主要做一些网络状态的更新操作,通知网络状态已经变化,有网络已经连上了。切换到ConnectedState,至此AP已经连接、IP也已获取到,并且网络状态也以更新。后续的过程貌似没什么大用。。
4.4之后Android加入了AP评分机制。这个机制根据某个AP的很多配置信息一通处理,最后得出一个score,来表明某个AP的可连接性。它评判的属性太多,后面在研究,现在本渣还是没有搞明白,囧....
在framework里面,其实屏蔽了很多操作,比如WifiNative里面的大多数方法都是要通过JNI调用wifi.c,再调到wpa_s里面去的。这些中间过程其实比framework的处理要复杂的多,代码也更难看些。还有runDhcp(),这些网络操作有个专门的守护进程进行处理,就是dhcpcd;它的实现个人感觉跟wpa_s一样,都是很复杂的。即使了解DHCP协议的工作流程,要看懂它的代码实现也很痛苦。Android里还有PPPoE拨号方式,它的守护进程pppd的实现也很复杂......