(一百三十六)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

相关文章

一文详解Python中数据清洗与处理的常用方法

《一文详解Python中数据清洗与处理的常用方法》在数据处理与分析过程中,缺失值、重复值、异常值等问题是常见的挑战,本文总结了多种数据清洗与处理方法,文中的示例代码简洁易懂,有需要的小伙伴可以参考下... 目录缺失值处理重复值处理异常值处理数据类型转换文本清洗数据分组统计数据分箱数据标准化在数据处理与分析过

mysql外键创建不成功/失效如何处理

《mysql外键创建不成功/失效如何处理》文章介绍了在MySQL5.5.40版本中,创建带有外键约束的`stu`和`grade`表时遇到的问题,发现`grade`表的`id`字段没有随着`studen... 当前mysql版本:SELECT VERSION();结果为:5.5.40。在复习mysql外键约

怎么关闭Ubuntu无人值守升级? Ubuntu禁止自动更新的技巧

《怎么关闭Ubuntu无人值守升级?Ubuntu禁止自动更新的技巧》UbuntuLinux系统禁止自动更新的时候,提示“无人值守升级在关机期间,请不要关闭计算机进程”,该怎么解决这个问题?详细请看... 本教程教你如何处理无人值守的升级,即 Ubuntu linux 的自动系统更新。来源:https://

Go语言使用Buffer实现高性能处理字节和字符

《Go语言使用Buffer实现高性能处理字节和字符》在Go中,bytes.Buffer是一个非常高效的类型,用于处理字节数据的读写操作,本文将详细介绍一下如何使用Buffer实现高性能处理字节和... 目录1. bytes.Buffer 的基本用法1.1. 创建和初始化 Buffer1.2. 使用 Writ

Python视频处理库VidGear使用小结

《Python视频处理库VidGear使用小结》VidGear是一个高性能的Python视频处理库,本文主要介绍了Python视频处理库VidGear使用小结,文中通过示例代码介绍的非常详细,对大家的... 目录一、VidGear的安装二、VidGear的主要功能三、VidGear的使用示例四、VidGea

Python结合requests和Cheerio处理网页内容的操作步骤

《Python结合requests和Cheerio处理网页内容的操作步骤》Python因其简洁明了的语法和强大的库支持,成为了编写爬虫程序的首选语言之一,requests库是Python中用于发送HT... 目录一、前言二、环境搭建三、requests库的基本使用四、Cheerio库的基本使用五、结合req

使用Python处理CSV和Excel文件的操作方法

《使用Python处理CSV和Excel文件的操作方法》在数据分析、自动化和日常开发中,CSV和Excel文件是非常常见的数据存储格式,ython提供了强大的工具来读取、编辑和保存这两种文件,满足从基... 目录1. CSV 文件概述和处理方法1.1 CSV 文件格式的基本介绍1.2 使用 python 内

idea如何开启菜单栏

《idea如何开启菜单栏》文章介绍了如何通过修改IntelliJIDEA的样式文件`ui.lnf.xml`来重新显示被关闭的菜单栏,并分享了解决问题的步骤... 目录ijsdea开启菜单栏第一步第二步总结idea开启菜单栏手贱关闭了idea的js菜单栏,花费了半个小时终于解决,记录并分享一下第一步找

SSID究竟是什么? WiFi网络名称及工作方式解析

《SSID究竟是什么?WiFi网络名称及工作方式解析》SID可以看作是无线网络的名称,类似于有线网络中的网络名称或者路由器的名称,在无线网络中,设备通过SSID来识别和连接到特定的无线网络... 当提到 Wi-Fi 网络时,就避不开「SSID」这个术语。简单来说,SSID 就是 Wi-Fi 网络的名称。比如

如何使用celery进行异步处理和定时任务(django)

《如何使用celery进行异步处理和定时任务(django)》文章介绍了Celery的基本概念、安装方法、如何使用Celery进行异步任务处理以及如何设置定时任务,通过Celery,可以在Web应用中... 目录一、celery的作用二、安装celery三、使用celery 异步执行任务四、使用celery