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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Switch switchtest = findViewById(R.id.switchtest);
        switchtest.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public 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开关切换的处理

    @Override
    public boolean onSwitchToggled(boolean isChecked) {
        //Do nothing if called as a result of a state machine event
        if (mStateMachineEvent) {
            return true;
        }
        // Show toast message if Wi-Fi is not allowed in airplane mode
        if (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 Wifi
        if (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)) {
            // Error
            mSwitchWidget.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.
     */
    @Override
    public 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 Wifi
        if (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 wifi
        boolean 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 toggled
                return 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;

        @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();
        }
        @Override
        public 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 both
                                mDeferredEnableSerialNumber++;
                            }
                            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的处理

        @Override
        public 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 both
                                mDeferredEnableSerialNumber++;
                            }
                            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 both
                                mDeferredEnableSerialNumber++;
                            }
                            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 {
        @Override
        public void enter() {
            mWifiStateMachine.setSupplicantRunning(true);
        }
        @Override
        public 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延迟处理。
  • 关闭时,直接进行关闭。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值