(一百三十六)Android O WiFi短时间多次开启关闭的处理

2023-12-19 07:38

本文主要是介绍(一百三十六)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短时间多次开启关闭的处理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/511391

相关文章

解决docker目录内存不足扩容处理方案

《解决docker目录内存不足扩容处理方案》文章介绍了Docker存储目录迁移方法:因系统盘空间不足,需将Docker数据迁移到更大磁盘(如/home/docker),通过修改daemon.json配... 目录1、查看服务器所有磁盘的使用情况2、查看docker镜像和容器存储目录的空间大小3、停止dock

Java服务实现开启Debug远程调试

《Java服务实现开启Debug远程调试》文章介绍如何通过JVM参数开启Java服务远程调试,便于在线上排查问题,在IDEA中配置客户端连接,实现无需频繁部署的调试,提升效率... 目录一、背景二、相关图示说明三、具体操作步骤1、服务端配置2、客户端配置总结一、背景日常项目中,通常我们的代码都是部署到远程

5 种使用Python自动化处理PDF的实用方法介绍

《5种使用Python自动化处理PDF的实用方法介绍》自动化处理PDF文件已成为减少重复工作、提升工作效率的重要手段,本文将介绍五种实用方法,从内置工具到专业库,帮助你在Python中实现PDF任务... 目录使用内置库(os、subprocess)调用外部工具使用 PyPDF2 进行基本 PDF 操作使用

分析 Java Stream 的 peek使用实践与副作用处理方案

《分析JavaStream的peek使用实践与副作用处理方案》StreamAPI的peek操作是中间操作,用于观察元素但不终止流,其副作用风险包括线程安全、顺序混乱及性能问题,合理使用场景有限... 目录一、peek 操作的本质:有状态的中间操作二、副作用的定义与风险场景1. 并行流下的线程安全问题2. 顺

Python异常处理之避免try-except滥用的3个核心原则

《Python异常处理之避免try-except滥用的3个核心原则》在Python开发中,异常处理是保证程序健壮性的关键机制,本文结合真实案例与Python核心机制,提炼出避免异常滥用的三大原则,有需... 目录一、精准打击:只捕获可预见的异常类型1.1 通用异常捕获的陷阱1.2 精准捕获的实践方案1.3

90%的人第一步就错了! 顺利登录wifi路由器后台的技巧

《90%的人第一步就错了!顺利登录wifi路由器后台的技巧》登录Wi-Fi路由器,其实就是进入它的后台管理页面,很多朋友不知道该怎么进入路由器后台设置,感兴趣的朋友可以花3分钟了解一下... 你是不是也遇到过这种情况:家里网速突然变慢、想改WiFi密码却不知道从哪进路由器、新装宽带后完全不知道怎么设置?别慌

Pandas处理缺失数据的方式汇总

《Pandas处理缺失数据的方式汇总》许多教程中的数据与现实世界中的数据有很大不同,现实世界中的数据很少是干净且同质的,本文我们将讨论处理缺失数据的一些常规注意事项,了解Pandas如何表示缺失数据,... 目录缺失数据约定的权衡Pandas 中的缺失数据None 作为哨兵值NaN:缺失的数值数据Panda

C++中处理文本数据char与string的终极对比指南

《C++中处理文本数据char与string的终极对比指南》在C++编程中char和string是两种用于处理字符数据的类型,但它们在使用方式和功能上有显著的不同,:本文主要介绍C++中处理文本数... 目录1. 基本定义与本质2. 内存管理3. 操作与功能4. 性能特点5. 使用场景6. 相互转换核心区别

Python动态处理文件编码的完整指南

《Python动态处理文件编码的完整指南》在Python文件处理的高级应用中,我们经常会遇到需要动态处理文件编码的场景,本文将深入探讨Python中动态处理文件编码的技术,有需要的小伙伴可以了解下... 目录引言一、理解python的文件编码体系1.1 Python的IO层次结构1.2 编码问题的常见场景二

Python函数的基本用法、返回值特性、全局变量修改及异常处理技巧

《Python函数的基本用法、返回值特性、全局变量修改及异常处理技巧》本文将通过实际代码示例,深入讲解Python函数的基本用法、返回值特性、全局变量修改以及异常处理技巧,感兴趣的朋友跟随小编一起看看... 目录一、python函数定义与调用1.1 基本函数定义1.2 函数调用二、函数返回值详解2.1 有返