目录
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延迟处理。
- 关闭时,直接进行关闭。