本文主要是介绍(一百三十六)Android O WiFi短时间多次开启关闭的处理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
目录
1.Settings
2.framework
2.1 WifiServiceImpl
2.2 WifiController
2.2.1 打开WiFi
2.2.2 关闭WiFi
3.总结
1.Settings
settings主要就是个WiFi开关,依据开关状态下发打开WiFi或者关闭WiFi命令
对开关频繁进行点击是依次下发true-false-true-false,还是如界面显示,点击时开关打开就下发false,关闭时就下发true呢,会变成true-true-false-false
写了个小demo
public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);Switch switchtest = findViewById(R.id.switchtest);switchtest.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {@Overridepublic void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {Log.d("jiatai", "the isChecked is " + isChecked);}});}
}
靠手动频繁点击
2019-04-20 11:17:03.460 20716-20716/com.example.demo_137_switchtest D/jiatai: the isChecked is true
2019-04-20 11:17:04.263 20716-20716/com.example.demo_137_switchtest D/jiatai: the isChecked is false
2019-04-20 11:17:05.006 20716-20716/com.example.demo_137_switchtest D/jiatai: the isChecked is true
2019-04-20 11:17:06.468 20716-20716/com.example.demo_137_switchtest D/jiatai: the isChecked is false
2019-04-20 11:17:08.366 20716-20716/com.example.demo_137_switchtest D/jiatai: the isChecked is true
2019-04-20 11:17:08.545 20716-20716/com.example.demo_137_switchtest D/jiatai: the isChecked is false
2019-04-20 11:17:08.713 20716-20716/com.example.demo_137_switchtest D/jiatai: the isChecked is true
暂且来看是交替的,符合预期,后续需要看下源码
然后这边看下Settings对WiFi开关切换的处理
@Overridepublic boolean onSwitchToggled(boolean isChecked) {//Do nothing if called as a result of a state machine eventif (mStateMachineEvent) {return true;}// Show toast message if Wi-Fi is not allowed in airplane modeif (isChecked && !WirelessUtils.isRadioAllowed(mContext, Settings.Global.RADIO_WIFI)) {Toast.makeText(mContext, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show();// Reset switch to off. No infinite check/listenenr loop.mSwitchWidget.setChecked(false);return false;}// Disable tethering if enabling Wifiif (mayDisableTethering(isChecked)) {mConnectivityManager.stopTethering(ConnectivityManager.TETHERING_WIFI);}if (isChecked) {mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_WIFI_ON);} else {// Log if user was connected at the time of switching off.mMetricsFeatureProvider.action(mContext, MetricsEvent.ACTION_WIFI_OFF,mConnected.get());}if (!mWifiManager.setWifiEnabled(isChecked)) {// ErrormSwitchWidget.setEnabled(true);Toast.makeText(mContext, R.string.wifi_error, Toast.LENGTH_SHORT).show();}return true;}
先认为isChecked是true/false交替变化的,那么这边就相当于交替发送打开关闭命令到framework
2.framework
2.1 WifiServiceImpl
/*** 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.*/@Overridepublic synchronized boolean setWifiEnabled(String packageName, boolean enable)throws RemoteException {enforceChangePermission();Slog.d(TAG, "setWifiEnabled: " + enable + " pid=" + Binder.getCallingPid()+ ", uid=" + Binder.getCallingUid() + ", package=" + packageName);mLog.info("setWifiEnabled package=% uid=% enable=%").c(packageName).c(Binder.getCallingUid()).c(enable).flush();boolean isFromSettings = checkNetworkSettingsPermission(Binder.getCallingPid(), Binder.getCallingUid());// If Airplane mode is enabled, only Settings is allowed to toggle Wifiif (mSettingsStore.isAirplaneModeOn() && !isFromSettings) {mLog.info("setWifiEnabled in Airplane mode: only Settings can enable wifi").flush();return false;}// If SoftAp is enabled, only Settings is allowed to toggle wifiboolean apEnabled =mWifiStateMachine.syncGetWifiApState() != WifiManager.WIFI_AP_STATE_DISABLED;if (apEnabled && !isFromSettings) {mLog.info("setWifiEnabled SoftAp not disabled: only Settings can enable wifi").flush();return false;}/** 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 toggledreturn true;}} finally {Binder.restoreCallingIdentity(ident);}if (mPermissionReviewRequired) {final int wiFiEnabledState = getWifiEnabledState();if (enable) {if (wiFiEnabledState == WifiManager.WIFI_STATE_DISABLING|| wiFiEnabledState == WifiManager.WIFI_STATE_DISABLED) {if (startConsentUi(packageName, Binder.getCallingUid(),WifiManager.ACTION_REQUEST_ENABLE)) {return true;}}} else if (wiFiEnabledState == WifiManager.WIFI_STATE_ENABLING|| wiFiEnabledState == WifiManager.WIFI_STATE_ENABLED) {if (startConsentUi(packageName, Binder.getCallingUid(),WifiManager.ACTION_REQUEST_DISABLE)) {return true;}}}mWifiController.sendMessage(CMD_WIFI_TOGGLED);return true;}
这边看下实现细节,当WiFi关闭或者softap打开的情况下只有Settings才可以操作WiFi。
再继续看下SettingsStore.handleWifiToggled
public synchronized boolean handleWifiToggled(boolean wifiEnabled) {// Can Wi-Fi be toggled in airplane mode ?if (mAirplaneModeOn && !isAirplaneToggleable()) {return false;}if (wifiEnabled) {if (mAirplaneModeOn) {persistWifiState(WIFI_ENABLED_AIRPLANE_OVERRIDE);} else {persistWifiState(WIFI_ENABLED);}} else {// When wifi state is disabled, we do not care// if airplane mode is on or not. The scenario of// wifi being disabled due to airplane mode being turned on// is handled handleAirplaneModeToggled()persistWifiState(WIFI_DISABLED);}return true;}/* Is Wi-Fi allowed to be re-enabled while airplane mode is on ? */private boolean isAirplaneToggleable() {String toggleableRadios = Settings.Global.getString(mContext.getContentResolver(),Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS);return toggleableRadios != null&& toggleableRadios.contains(Settings.Global.RADIO_WIFI);}private void persistWifiState(int state) {final ContentResolver cr = mContext.getContentResolver();mPersistWifiState = state;Settings.Global.putInt(cr, Settings.Global.WIFI_ON, state);}
切换WiFi之前会保存一下WiFi将要切换的状态。
2.2 WifiController
2.2.1 打开WiFi
class ApStaDisabledState extends State {private int mDeferredEnableSerialNumber = 0;private boolean mHaveDeferredEnable = false;private long mDisabledTimestamp;@Overridepublic void enter() {mWifiStateMachine.setSupplicantRunning(false);// Supplicant can't restart right away, so not the time we switched offmDisabledTimestamp = SystemClock.elapsedRealtime();mDeferredEnableSerialNumber++;mHaveDeferredEnable = false;mWifiStateMachine.clearANQPCache();}@Overridepublic boolean processMessage(Message msg) {switch (msg.what) {case CMD_WIFI_TOGGLED:case CMD_AIRPLANE_TOGGLED:if (mSettingsStore.isWifiToggleEnabled()) {if (doDeferEnable(msg)) {if (mHaveDeferredEnable) {// have 2 toggles now, inc serial number an ignore bothmDeferredEnableSerialNumber++;}mHaveDeferredEnable = !mHaveDeferredEnable;break;}if (mDeviceIdle == false) {// wifi is toggled, we need to explicitly tell WifiStateMachine that we// are headed to connect mode before going to the DeviceActiveState// since that will start supplicant and WifiStateMachine may not know// what state to head to (it might go to scan mode).mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);transitionTo(mDeviceActiveState);} else {checkLocksAndTransitionWhenDeviceIdle();}} else if (mSettingsStore.isScanAlwaysAvailable()) {transitionTo(mStaDisabledWithScanState);}break;
打开WiFi的时候先判断一下WifiService是否将WiFi状态切换为打开状态,若是则继续进行WiFi打开的后续流程
public synchronized boolean isWifiToggleEnabled() {if (!mCheckSavedStateAtBoot) {mCheckSavedStateAtBoot = true;if (testAndClearWifiSavedState()) return true;}if (mAirplaneModeOn) {return mPersistWifiState == WIFI_ENABLED_AIRPLANE_OVERRIDE;} else {return mPersistWifiState != WIFI_DISABLED;}}/*** After a reboot, we restore wi-fi to be on if it was turned off temporarily for tethering.* The settings app tracks the saved state, but the framework has to check it at boot to* make sure the wi-fi is turned on in case it was turned off for the purpose of tethering.** Note that this is not part of the regular WIFI_ON setting because this only needs to* be controlled through the settings app and not the Wi-Fi public API.*/private boolean testAndClearWifiSavedState() {int wifiSavedState = getWifiSavedState();if (wifiSavedState == WIFI_ENABLED) {setWifiSavedState(WIFI_DISABLED);}return (wifiSavedState == WIFI_ENABLED);}/*** Allow callers to set the Settings.Global.WIFI_SAVED_STATE property.** When changing states, we need to remember what the wifi state was before switching. An* example of this is when WiFiController switches to APEnabledState. Before swtiching to the* new state, WifiController sets the current WiFi enabled/disabled state. When the AP is* turned off, the WIFI_SAVED_STATE setting is used to restore the previous wifi state.** @param state WiFi state to store with the Settings.Global.WIFI_SAVED_STATE property.*/public void setWifiSavedState(int state) {Settings.Global.putInt(mContext.getContentResolver(),Settings.Global.WIFI_SAVED_STATE, state);}/*** Allow callers to get the Settings.Global.WIFI_SAVED_STATE property.** When changing states we remember what the wifi state was before switching. This function is* used to get the saved state.** @return int Value for the previously saved state.*/public int getWifiSavedState() {try {return Settings.Global.getInt(mContext.getContentResolver(),Settings.Global.WIFI_SAVED_STATE);} catch (Settings.SettingNotFoundException e) {// If we have an error, return wifiSavedState off.return WIFI_DISABLED;}}
这边还牵扯出一个mCheckSavedStateAtBoot标志位
xref: /frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiServiceImpl.java
429 /**
430 * Check if we are ready to start wifi.
431 *
432 * First check if we will be restarting system services to decrypt the device. If the device is
433 * not encrypted, check if Wi-Fi needs to be enabled and start if needed
434 *
435 * This function is used only at boot time.
436 */
437 public void checkAndStartWifi() {
438 // First check if we will end up restarting WifiService
439 if (mFrameworkFacade.inStorageManagerCryptKeeperBounce()) {
440 Log.d(TAG, "Device still encrypted. Need to restart SystemServer. Do not start wifi.");
441 return;
442 }
443
444 // Check if wi-fi needs to be enabled
445 boolean wifiEnabled = mSettingsStore.isWifiToggleEnabled();
446 Slog.i(TAG, "WifiService starting up with Wi-Fi " +
447 (wifiEnabled ? "enabled" : "disabled"));
...
515 // If we are already disabled (could be due to airplane mode), avoid changing persist
516 // state here
517 if (wifiEnabled) {
518 try {
519 setWifiEnabled(mContext.getPackageName(), wifiEnabled);
520 } catch (RemoteException e) {
521 /* ignore - local call */
522 }
523 }xref: /frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiService.java
41 @Override
42 public void onBootPhase(int phase) {
43 if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
44 mImpl.checkAndStartWifi();
45 }
46 }
这边先补充3点
1)Android暂时是不支持“关机前wifi热点打开,则重启后自动打开热点”
2)当WiFi打开的情况下,打开热点,会暂时性先关闭WiFi,后续热点被关闭后会重新打开WiFi。(可部分参考https://blog.csdn.net/sinat_20059415/article/details/83035808)
3)当从WiFi切换到热点时,会将当前WiFi状态保存到Settings.Global.WIFI_SAVED_STATE中去,不同于之前WiFi打开时保存的Settings.Global.WIFI_ON
/*** Used to save the Wifi_ON state prior to tethering.* This state will be checked to restore Wifi after* the user turns off tethering.** @hide*/public static final String WIFI_SAVED_STATE = "wifi_saved_state";/*** Whether the Wi-Fi should be on. Only the Wi-Fi service should touch this.*/public static final String WIFI_ON = "wifi_on";
基于以上情况
系统启动的时候会重置一下Settings.Global.WIFI_SAVED_STATE为disable,并且根据Settings.Global.WIFI_ON将WiFi打开或者保持关闭状态。
再回头看下WifiController对CMD_WIFI_TOGGLED的处理
@Overridepublic boolean processMessage(Message msg) {switch (msg.what) {case CMD_WIFI_TOGGLED:case CMD_AIRPLANE_TOGGLED:if (mSettingsStore.isWifiToggleEnabled()) {if (doDeferEnable(msg)) {if (mHaveDeferredEnable) {// have 2 toggles now, inc serial number an ignore bothmDeferredEnableSerialNumber++;}mHaveDeferredEnable = !mHaveDeferredEnable;break;}if (mDeviceIdle == false) {// wifi is toggled, we need to explicitly tell WifiStateMachine that we// are headed to connect mode before going to the DeviceActiveState// since that will start supplicant and WifiStateMachine may not know// what state to head to (it might go to scan mode).mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE);transitionTo(mDeviceActiveState);} else {checkLocksAndTransitionWhenDeviceIdle();}} else if (mSettingsStore.isScanAlwaysAvailable()) {transitionTo(mStaDisabledWithScanState);}break;private boolean doDeferEnable(Message msg) {long delaySoFar = SystemClock.elapsedRealtime() - mDisabledTimestamp;if (delaySoFar >= mReEnableDelayMillis) {return false;}log("WifiController msg " + msg + " deferred for " +(mReEnableDelayMillis - delaySoFar) + "ms");// need to defer this action.Message deferredMsg = obtainMessage(CMD_DEFERRED_TOGGLE);deferredMsg.obj = Message.obtain(msg);deferredMsg.arg1 = ++mDeferredEnableSerialNumber;sendMessageDelayed(deferredMsg, mReEnableDelayMillis - delaySoFar + DEFER_MARGIN_MS);return true;}
手机性能毕竟有限,操作WiFi必须要有一定的时间才能完成,无法连续处理频繁的WiFi操作请求,Android原生通过doDeferEnable对消息进行了延迟处理,当刚关闭了WiFi,这时在500ms之内又来了一条打开命令,就会被补足到关闭WiFi之后505ms后继续处理
/*** See {@link Settings.Global#WIFI_REENABLE_DELAY_MS}. This is the default value if a* Settings.Global value is not present. This is the minimum time after wifi is disabled* we'll act on an enable. Enable requests received before this delay will be deferred.*/private static final long DEFAULT_REENABLE_DELAY_MS = 500;
如果在500ms之内下发了两条命令,则我们认为是进行了一次打开一次关闭操作,那么两条操作抵消,消息均不做处理。
if (doDeferEnable(msg)) {if (mHaveDeferredEnable) {// have 2 toggles now, inc serial number an ignore bothmDeferredEnableSerialNumber++;}mHaveDeferredEnable = !mHaveDeferredEnable;break;}case CMD_DEFERRED_TOGGLE:if (msg.arg1 != mDeferredEnableSerialNumber) {log("DEFERRED_TOGGLE ignored due to serial mismatch");break;}log("DEFERRED_TOGGLE handled");sendMessage((Message)(msg.obj));break;
抵消操作是通过将mDeferredEnableSerialNumber++实现,当处理到了CMD_DEFERRED_TOGGLE会判断消息的arg1和mDeferredEnableSerialNumber是否匹配,匹配才做处理。
2.2.2 关闭WiFi
本来以为会和打开WiFi的处理一样呢,看下代码是直接处理关闭了。
class StaEnabledState extends State {@Overridepublic void enter() {mWifiStateMachine.setSupplicantRunning(true);}@Overridepublic boolean processMessage(Message msg) {switch (msg.what) {case CMD_WIFI_TOGGLED:if (! mSettingsStore.isWifiToggleEnabled()) {if (mSettingsStore.isScanAlwaysAvailable()) {transitionTo(mStaDisabledWithScanState);} else {transitionTo(mApStaDisabledState);}}break;
3.总结
当WiFi短时间内多次进行打开关闭操作
- 打开时,若距离上次关闭时间极短(小于500ms),则做补足505ms延迟处理。
- 关闭时,直接进行关闭。
这篇关于(一百三十六)Android O WiFi短时间多次开启关闭的处理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!