Android -- Wifi的断开及关闭流程简介
当我们连接上一个AP时,Wifi的断开及关闭都会导致设备与AP之间的连接中断;关闭Wifi同时会导致Wifi断开。下面就简单介绍Wifi断开及关闭的流程。
一、Wifi的断开
我们断开Wifi,调用WifiManager::disconnect()方法,嵌套调用WifiServiceImpl的同名方法:
调用Wifi状态机的内部方法,发送断开Wifi的指令CMD_DISCONNECT。由于之前网络连接成功时,State Machine停在ConnectedState,所以由它的父状态L2ConnectedState处理该消息:
通过WifiNative::disconnect()方法下发断开指令,当驱动断开AP连接后,wpa_s反馈断开事件,WifiMonitor接收来自wpa_s的event,并向WifiStateMachine反馈NETWORK_DISCONNECTION_EVENT消息(该过程参考前面的博文)。切换到DisconnectingState状态,断开的消息尤其父状态ConnectModeState处理:
由代码中的注释可知,我们或许会在别的地方已经调用过handleNetworkDisconnect(),事实也确实如此;当我们离开L2ConnectedState状态时,会调用它的exit()方法:
我们发现该方法中确实可能会调用handleNetworkDisconnect(),接着看该函数做了哪些处理:
当一个AP连接断开时,主要会做如下几处理:
- /**
- * see {@link android.net.wifi.WifiManager#disconnect()}
- */
- public void disconnect() {
- enforceChangePermission();
- mWifiStateMachine.disconnectCommand();
- }
- case CMD_DISCONNECT:
- mWifiNative.disconnect();
- transitionTo(mDisconnectingState);
- break;
- case WifiMonitor.NETWORK_DISCONNECTION_EVENT:
- // Calling handleNetworkDisconnect here is redundant because we might already
- // have called it when leaving L2ConnectedState to go to disconnecting state
- // or thru other path
- // We should normally check the mWifiInfo or mLastNetworkId so as to check
- // if they are valid, and only in this case call handleNEtworkDisconnect,
- // TODO: this should be fixed for a L MR release
- // The side effect of calling handleNetworkDisconnect twice is that a bunch of
- // idempotent commands are executed twice (stopping Dhcp, enabling the SPS mode
- // at the chip etc...
- if (DBG) log("ConnectModeState: Network connection lost ");
- handleNetworkDisconnect();
- transitionTo(mDisconnectedState);
- break;
- @Override
- public void exit() {
- if (mIpReachabilityMonitor != null) {
- mIpReachabilityMonitor.stop();
- mIpReachabilityMonitor = null;
- }
- // This is handled by receiving a NETWORK_DISCONNECTION_EVENT in ConnectModeState
- // Bug: 15347363
- // For paranoia's sake, call handleNetworkDisconnect
- // only if BSSID is null or last networkId
- // is not invalid.
- if (DBG) {
- StringBuilder sb = new StringBuilder();
- sb.append("leaving L2ConnectedState state nid=" + Integer.toString(mLastNetworkId));
- if (mLastBssid !=null) {
- sb.append(" ").append(mLastBssid);
- }
- }
- if (mLastBssid != null || mLastNetworkId != WifiConfiguration.INVALID_NETWORK_ID) {
- handleNetworkDisconnect();//call the function,执行忘了断开时的处理
- }
- }
- /**
- * Resets the Wi-Fi Connections by clearing any state, resetting any sockets
- * using the interface, stopping DHCP & disabling interface
- */
- private void handleNetworkDisconnect() {
- clearCurrentConfigBSSID("handleNetworkDisconnect");
- stopDhcp();
- try {
- mNwService.clearInterfaceAddresses(mInterfaceName);
- mNwService.disableIpv6(mInterfaceName);
- } catch (Exception e) {
- loge("Failed to clear addresses or disable ipv6" + e);
- }
- /* Reset data structures */
- mBadLinkspeedcount = 0;
- mWifiInfo.reset();
- linkDebouncing = false;
- /* Reset roaming parameters */
- mAutoRoaming = WifiAutoJoinController.AUTO_JOIN_IDLE;
- /**
- * fullBandConnectedTimeIntervalMilli:
- * - start scans at mWifiConfigStore.wifiAssociatedShortScanIntervalMilli seconds interval
- * - exponentially increase to mWifiConfigStore.associatedFullScanMaxIntervalMilli
- * Initialize to sane value = 20 seconds
- */
- fullBandConnectedTimeIntervalMilli = 20 * 1000;
- setNetworkDetailedState(DetailedState.DISCONNECTED);
- if (mNetworkAgent != null) {
- mNetworkAgent.sendNetworkInfo(mNetworkInfo);
- mNetworkAgent = null;
- }
- mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.DISCONNECTED);
- /* Clear network properties */
- clearLinkProperties();
- /* Cend event to CM & network change broadcast */
- sendNetworkStateChangeBroadcast(mLastBssid);
- /* Cancel auto roam requests */
- autoRoamSetBSSID(mLastNetworkId, "any");
- mLastBssid = null;
- registerDisconnected();
- mLastNetworkId = WifiConfiguration.INVALID_NETWORK_ID;
- }
- stopDhcp():停止DHCP过程,即停止dhcpcd进程
- mNwService.clearInterfaceAddresses(mInterfaceName):通过NetworkManagementService通知Netd清除wlan0的地址信息
- mWifiInfo.reset():重置WifiInfo对象的状态
- setNetworkDetailedState(DetailedState.DISCONNECTED):将mNetworkInfo的连接状态设为DISCONNECTED
- mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.DISCONNECTED):将mLastNetworkId对应的 WifiConfiguration对象状态设为DISCONNECTED
- clearLinkProperties():清除mLinkProperties对象的配置信息
- sendNetworkStateChangeBroadcast():发广播通知上层网络状态变化
- registerDisconnected():做一些清理、记录工作
最后状态切换到DisconnectedState,等待下一次连接的发起,断开流程结束。
二、Wifi的关闭
从前面的博文可知,Wifi的开启、关闭都是调用WifiManager::setWifiEnabled(),嵌套调用WifiServiceImpl中的同名方法,向WifiController发送CMD_WIFI_TOGGLED消息:
前面的博文介绍过Wifi的启动流程,当WifiController向Wifi状态机发送启动wpa_s消息的时候,状态停留在DeviceActiveState。DeviceActiveState不处理CMD_WIFI_TOGGLED消息,交由其父状态StaEnabledState处理:
结合前面的所讲的内容,可知状态切换到ApStaDisabledState,进行关闭流程,调用它的enter()方法:
调用WifiStateMachine::setSupplicantRunning(false)向WifiStateMachine发送停止wpa_s的消息:CMD_STOP_SUPPLICANT。
- /**
- * see {@link android.net.wifi.WifiManager#setWifiEnabled(boolean)}
- * @param enable {@code true} to enable, {@code false} to disable.
- * @return {@code true} if the enable/disable operation was
- * started or is already in the queue.
- */
- public synchronized boolean setWifiEnabled(boolean enable) {
- enforceChangePermission();
- Slog.d(TAG, "setWifiEnabled: " + enable + " pid=" + Binder.getCallingPid()
- + ", uid=" + Binder.getCallingUid());
- if (DBG) {
- Slog.e(TAG, "Invoking mWifiStateMachine.setWifiEnabled\n");
- }
- /*
- * Caller might not have WRITE_SECURE_SETTINGS,
- * only CHANGE_WIFI_STATE is enforced
- */
- long ident = Binder.clearCallingIdentity();
- try {
- if (! mSettingsStore.handleWifiToggled(enable)) {
- // Nothing to do if wifi cannot be toggled
- return true;
- }
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
- mWifiController.sendMessage(CMD_WIFI_TOGGLED);
- return true;
- }
- case CMD_WIFI_TOGGLED:
- if (! mSettingsStore.isWifiToggleEnabled()) {
- if (mSettingsStore.isScanAlwaysAvailable()) {
- transitionTo(mStaDisabledWithScanState);
- } else {
- transitionTo(mApStaDisabledState);
- }
- }
- break;
- @Override
- public void enter() {
- mWifiStateMachine.setSupplicantRunning(false);
- // Supplicant can't restart right away, so not the time we switched off
- mDisabledTimestamp = SystemClock.elapsedRealtime();
- mDeferredEnableSerialNumber++;
- mHaveDeferredEnable = false;
- mWifiStateMachine.clearANQPCache();
- }
SupplicantStartedState接收、处理该消息:
切换到SupplicantStoppingState,进入enter()方法:
- case CMD_STOP_SUPPLICANT: /* Supplicant stopped by user */
- if (mP2pSupported) {
- transitionTo(mWaitForP2pDisableState);
- } else {
- transitionTo(mSupplicantStoppingState);
- }
- break;
- public void enter() {
- /* Send any reset commands to supplicant before shutting it down */
- handleNetworkDisconnect();
- if (mDhcpStateMachine != null) {
- mDhcpStateMachine.doQuit();
- }
- String suppState = System.getProperty("init.svc.wpa_supplicant");
- if (suppState == null) suppState = "unknown";
- String p2pSuppState = System.getProperty("init.svc.p2p_supplicant");
- if (p2pSuppState == null) p2pSuppState = "unknown";
- logd("SupplicantStoppingState: stopSupplicant "
- + " init.svc.wpa_supplicant=" + suppState
- + " init.svc.p2p_supplicant=" + p2pSuppState);
- mWifiMonitor.stopSupplicant();
- /* Send ourselves a delayed message to indicate failure after a wait time */
- sendMessageDelayed(obtainMessage(CMD_STOP_SUPPLICANT_FAILED,
- ++mSupplicantStopFailureToken, 0), SUPPLICANT_RESTART_INTERVAL_MSECS);
- setWifiState(WIFI_STATE_DISABLING);
- mSupplicantStateTracker.sendMessage(CMD_RESET_SUPPLICANT_STATE);
- }
- 调用handleNetworkDisconnect()处理AP连接的断开部分,第一部分已经讲过。
- 设置DhcpStateMachine的状态,停止DhcpStateMachine
- 调用WifiMonitor::stopSupplicant()停掉wpa_s
- 将wifi状态设置为disabling;setWifiState()函数会设置当前Wifi的状态,比如disabled、disabling、enabled等;我们通过监听函数发送的WifiManager.WIFI_STATE_CHANGED_ACTION广播,可以得知当前Wifi的具体状态。
WifiMonitor中下发终止指令后,会收到wpa_s断开的event:
WifiMonitor会向wifi状态机发送SUP_DISCONNECTION_EVENT消息并最终退出,等待下一次重新与wpa_s建立通信。SupplicantStoppingState处理该消息:
handleSupplicantConnectionLoss()函数中,因为之前已经执行过stopSupplicant()的操作,killSupplicant传入false,所以不会重复执行关闭wpa_s的killSupplicant()函数;随后关闭wpa_s与wifi之间传递指令、事件的socket连接,最后设置当前Wifi状态为disabled,代表Wifi已经关闭完成。
- else if (event == TERMINATING) {
- /**
- * Close the supplicant connection if we see
- * too many recv errors
- */
- if (eventData.startsWith(WPA_RECV_ERROR_STR)) {
- if (++sRecvErrors > MAX_RECV_ERRORS) {
- if (DBG) {
- Log.d(TAG, "too many recv errors, closing connection");
- }
- } else {
- eventLogCounter++;
- return false;
- }
- }
- // Notify and exit
- mStateMachine.sendMessage(SUP_DISCONNECTION_EVENT, eventLogCounter);
- return true;
- case WifiMonitor.SUP_DISCONNECTION_EVENT:
- if (DBG) log("Supplicant connection lost");
- handleSupplicantConnectionLoss(false);
- transitionTo(mInitialState);
- break;
- private void handleSupplicantConnectionLoss(boolean killSupplicant) {
- /* Socket connection can be lost when we do a graceful shutdown
- * or when the driver is hung. Ensure supplicant is stopped here.
- */
- if (killSupplicant) {
- mWifiMonitor.killSupplicant(mP2pSupported);
- }
- mWifiNative.closeSupplicantConnection();
- sendSupplicantConnectionChangedBroadcast(false);
- setWifiState(WIFI_STATE_DISABLED);
- }
handleSupplicantConnectionLoss()执行完毕后,wpa_s的状态已经被彻底清除掉,随之将Wifi状态机切换到InitialState:
在enter()函数中,比较重要的操作就是mWifiNative.unloadDriver(),当Wifi完全关闭后,将驱动也卸载掉。当下一次Wifi启动流程启动时,再重新加载驱动、启动wpa_s。
- @Override
- public void enter() {
- WifiNative.stopHal();
- mWifiNative.unloadDriver();
- if (mWifiP2pChannel == null) {
- mWifiP2pChannel = new AsyncChannel();
- mWifiP2pChannel.connect(mContext, getHandler(),
- mWifiP2pServiceImpl.getP2pStateMachineMessenger());
- }
- if (mWifiApConfigChannel == null) {
- mWifiApConfigChannel = new AsyncChannel();
- mWifiApConfigStore = WifiApConfigStore.makeWifiApConfigStore(
- mContext, getHandler());
- mWifiApConfigStore.loadApConfiguration();
- mWifiApConfigChannel.connectSync(mContext, getHandler(),
- mWifiApConfigStore.getMessenger());
- }
- if (mWifiConfigStore.enableHalBasedPno.get()) {
- // make sure developer Settings are in sync with the config option
- mHalBasedPnoEnableInDevSettings = true;
- }
- }
到此,Wifi关闭的流程就结束了。