我来分析 Android 开关飞行模式的流程。
好的,现在我已经收集到足够的信息。让我为用户详细分析 Android 开关飞行模式的完整流程。
Android 开关飞行模式流程详解
飞行模式(Airplane Mode)是 Android 中一个重要的系统功能,用于同时关闭所有无线通信功能(蜂窝、Wi-Fi、蓝牙等)。下面详细分析其实现原理。
一、飞行模式的触发入口
1. 用户界面触发
用户可以通过以下方式开关飞行模式:
- 快捷设置面板(下拉通知栏)
- 设置应用 → 网络与互联网 → 飞行模式
- 长按电源键 → 全局操作菜单
2. 核心操作:修改 Settings 数据库
所有触发入口最终都会执行两个关键操作:
Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
intent.putExtra("state", on);
mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
步骤:
-
修改 Settings 数据库
Settings.Global.putInt( getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, on ? 1 : 0 ); -
发送系统广播
Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); intent.putExtra("state", on); // true=开启, false=关闭 context.sendBroadcastAsUser(intent, UserHandle.ALL);
二、电话模块的处理流程
1. ServiceStateTracker 监听初始化
mCr = phone.getContext().getContentResolver();
// system setting property AIRPLANE_MODE_ON is set in Settings.
int airplaneMode = Settings.Global.getInt(mCr, Settings.Global.AIRPLANE_MODE_ON, 0);
int enableCellularOnBoot = Settings.Global.getInt(mCr,
Settings.Global.ENABLE_CELLULAR_ON_BOOT, getDefaultEnableCellularOnBoot());
mDesiredPowerState = (enableCellularOnBoot > 0) && ! (airplaneMode > 0);
if (!mDesiredPowerState) {
mRadioPowerOffReasons.add(TelephonyManager.RADIO_POWER_REASON_USER);
}
mRadioPowerLog.log("init : airplane mode = " + airplaneMode + " enableCellularOnBoot = " +
enableCellularOnBoot);
ServiceStateTracker 在初始化时:
- 读取飞行模式状态 (
AIRPLANE_MODE_ON) - 计算射频的期望状态
mDesiredPowerState = !airplaneMode - 如果飞行模式开启,则添加关闭原因
RADIO_POWER_REASON_USER
2. 注册 Settings 监听(通过 ContentObserver)
其他模块(如 CarrierActionAgent)会监听 Settings 变化:
mSettingsObserver.observe(
Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON),
EVENT_APM_SETTINGS_CHANGED);
当飞行模式设置变化时,触发 EVENT_APM_SETTINGS_CHANGED 事件。
三、开启飞行模式流程(关闭射频)
流程图
用户操作
↓
Settings.Global.AIRPLANE_MODE_ON = 1
↓
发送 ACTION_AIRPLANE_MODE_CHANGED 广播
↓
ServiceStateTracker 处理(通过 ContentObserver 或广播)
↓
setRadioPower(false)
↓
mDesiredPowerState = false
mRadioPowerOffReasons.add(RADIO_POWER_REASON_USER)
↓
setPowerStateToDesired()
↓
powerOffRadioSafely()
↓
【关键】先断开数据连接
↓
hangupAndPowerOff()
↓
mCi.setRadioPower(false) → HAL → Modem
↓
射频关闭完成
关键代码分析
1. setRadioPowerForReason 方法
public void setRadioPowerForReason(boolean power, boolean forEmergencyCall,
boolean isSelectedPhoneForEmergencyCall, boolean forceApply, int reason) {
log("setRadioPower power " + power + " forEmergencyCall " + forEmergencyCall
+ " forceApply " + forceApply + " reason " + reason);
if (power) {
if (forEmergencyCall) {
clearAllRadioOffReasons();
} else {
mRadioPowerOffReasons.remove(reason);
}
} else {
mRadioPowerOffReasons.add(reason);
}
if (power == mDesiredPowerState && !forceApply) {
log("setRadioPower mDesiredPowerState is already " + power + " Do nothing.");
return;
}
if (power && !mRadioPowerOffReasons.isEmpty()) {
log("setRadioPowerForReason " + "power: " + power + " forEmergencyCall= "
+ forEmergencyCall + " isSelectedPhoneForEmergencyCall: "
+ isSelectedPhoneForEmergencyCall + " forceApply " + forceApply + " reason: "
+ reason + " will not power on the radio as it is powered off for the "
+ "following reasons: " + mRadioPowerOffReasons + ".");
return;
}
mDesiredPowerState = power;
setPowerStateToDesired(forEmergencyCall, isSelectedPhoneForEmergencyCall, forceApply);
}
重点:
- 使用
mRadioPowerOffReasons集合管理关闭原因(可能有多个,如 USER、CARRIER、THERMAL 等) - 只有当
mRadioPowerOffReasons为空时才允许开机
2. setPowerStateToDesired 方法
protected void setPowerStateToDesired(boolean forEmergencyCall,
boolean isSelectedPhoneForEmergencyCall, boolean forceApply) {
if (DBG) {
String tmpLog = "setPowerStateToDesired: mDeviceShuttingDown=" + mDeviceShuttingDown
+ ", mDesiredPowerState=" + mDesiredPowerState
+ ", getRadioState=" + mCi.getRadioState()
+ ", mRadioPowerOffReasons=" + mRadioPowerOffReasons
+ ", IMS reg state=" + mImsRegistrationOnOff
+ ", pending radio off=" + hasMessages(EVENT_POWER_OFF_RADIO_IMS_DEREG_TIMEOUT);
log(tmpLog);
mRadioPowerLog.log(tmpLog);
}
if (mDesiredPowerState && mDeviceShuttingDown) {
log("setPowerStateToDesired powering on of radio failed because the device is " +
"powering off");
return;
}
// If we want it on and it's off, turn it on
if (mDesiredPowerState && mRadioPowerOffReasons.isEmpty()
&& (forceApply || mCi.getRadioState() == TelephonyManager.RADIO_POWER_OFF)) {
mCi.setRadioPower(true, forEmergencyCall, isSelectedPhoneForEmergencyCall, null);
} else if ((!mDesiredPowerState || !mRadioPowerOffReasons.isEmpty()) && mCi.getRadioState()
== TelephonyManager.RADIO_POWER_ON) {
if (DBG) log("setPowerStateToDesired: powerOffRadioSafely()");
powerOffRadioSafely();
} else if (mDeviceShuttingDown
&& (mCi.getRadioState() != TelephonyManager.RADIO_POWER_UNAVAILABLE)) {
// !mDesiredPowerState condition above will happen first if the radio is on, so we will
// see the following: (delay for IMS dereg) -> RADIO_POWER_OFF ->
// RADIO_POWER_UNAVAILABLE
mCi.requestShutdown(null);
}
// Cancel any pending timeouts because the state has been re-evaluated.
cancelDelayRadioOffWaitingForImsDeregTimeout();
}
判断逻辑:
- 开机条件:
mDesiredPowerState=true&&mRadioPowerOffReasons为空 && 当前射频关闭 - 关机条件: (
mDesiredPowerState=false||mRadioPowerOffReasons非空) && 当前射频开启
3. powerOffRadioSafely 安全关闭
public void powerOffRadioSafely() {
synchronized (this) {
SatelliteController.getInstance().onCellularRadioPowerOffRequested();
if (DomainSelectionResolver.getInstance().isDomainSelectionSupported()) {
EmergencyStateTracker.getInstance().onCellularRadioPowerOffRequested();
}
if (!mPendingRadioPowerOffAfterDataOff) {
// hang up all active voice calls first
if (mPhone.isPhoneTypeGsm() && mPhone.isInCall()) {
mPhone.mCT.mRingingCall.hangupIfAlive();
mPhone.mCT.mBackgroundCall.hangupIfAlive();
mPhone.mCT.mForegroundCall.hangupIfAlive();
}
for (Phone phone : PhoneFactory.getPhones()) {
if (!phone.getDataNetworkController().areAllDataDisconnected()) {
log("powerOffRadioSafely: Data is active on phone " + phone.getSubId()
+ ". Wait for all data disconnect.");
mPendingRadioPowerOffAfterDataOff = true;
phone.getDataNetworkController().registerDataNetworkControllerCallback(
mDataDisconnectedCallback);
}
}
// Tear down outside of the disconnected check to prevent race conditions.
mPhone.getDataNetworkController().tearDownAllDataNetworks(
DataNetwork.TEAR_DOWN_REASON_AIRPLANE_MODE_ON);
if (mPendingRadioPowerOffAfterDataOff) {
sendEmptyMessageDelayed(EVENT_SET_RADIO_POWER_OFF,
POWER_OFF_ALL_DATA_NETWORKS_DISCONNECTED_TIMEOUT);
} else {
log("powerOffRadioSafely: No data is connected, turn off radio now.");
hangupAndPowerOff();
}
}
}
}
安全关闭步骤:
- 通知
SatelliteController和EmergencyStateTracker - 挂断所有进行中的电话 (避免通话中断)
- 断开所有数据连接 (
tearDownAllDataNetworks) - 等待数据完全断开(超时机制)
- 调用
hangupAndPowerOff()→mCi.setRadioPower(false)
四、关闭飞行模式流程(开启射频)
流程图
用户操作
↓
Settings.Global.AIRPLANE_MODE_ON = 0
↓
发送 ACTION_AIRPLANE_MODE_CHANGED 广播
↓
ServiceStateTracker 处理
↓
setRadioPower(true)
↓
mRadioPowerOffReasons.remove(RADIO_POWER_REASON_USER)
mDesiredPowerState = true
↓
setPowerStateToDesired()
↓
mCi.setRadioPower(true) → HAL → Modem
↓
EVENT_RADIO_STATE_CHANGED
↓
setPowerStateToDesired() (确认状态)
↓
pollStateInternal(true) (开始网络搜索)
↓
射频开启完成,开始驻网
关键代码
case EVENT_RADIO_STATE_CHANGED:
RadioPowerStateStats.onRadioStateChanged(mCi.getRadioState());
// fall through, the code above only logs metrics when radio state changes
case EVENT_PHONE_TYPE_SWITCHED:
if(!mPhone.isPhoneTypeGsm() &&
mCi.getRadioState() == TelephonyManager.RADIO_POWER_ON) {
handleCdmaSubscriptionSource(mCdmaSSM.getCdmaSubscriptionSource());
}
// This will do nothing in the 'radio not available' case
setPowerStateToDesired();
// These events are modem triggered, so pollState() needs to be forced
pollStateInternal(true);
break;
关闭飞行模式后:
- Modem 上电后发送
UNSOL_RESPONSE_RADIO_STATE_CHANGED通知 - 触发
EVENT_RADIO_STATE_CHANGED事件 - 调用
setPowerStateToDesired()确认状态 - 调用
pollStateInternal(true)开始网络搜索,进入驻网流程
五、其他无线模块的处理
飞行模式不仅影响蜂窝网络,还会影响:
1. Wi-Fi
- 监听
ACTION_AIRPLANE_MODE_CHANGED广播 - 根据
Settings.Global.AIRPLANE_MODE_RADIOS判断是否需要关闭 - 如果在
AIRPLANE_MODE_TOGGLEABLE_RADIOS中,则允许在飞行模式下独立开关
2. Bluetooth
- 类似 Wi-Fi,通过
AIRPLANE_MODE_RADIOS配置 - 默认在飞行模式下关闭
3. NFC / UWB
- 根据配置决定是否受飞行模式影响
六、特殊场景处理
1. 紧急呼叫中开启飞行模式
if (tm != null && tm.isInEmergencyCall()) {
// Switch airplane mode back to off.
ConnectivityManager cm =
(ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
cm.setAirplaneMode(false);
Toast.makeText(this, R.string.radio_off_during_emergency_call, Toast.LENGTH_LONG)
.show();
Log.i(LOG_TAG, "Ignoring airplane mode: emergency call. Turning airplane off");
特殊处理:
- 如果正在进行紧急呼叫,自动关闭飞行模式
- 显示 Toast 提示用户
2. IMS 注销延迟
- 关闭射频前会等待 IMS 注销完成
- 超时机制:
EVENT_POWER_OFF_RADIO_IMS_DEREG_TIMEOUT
七、底层 HAL 调用
RadioModemProxy.setRadioPower
public void setRadioPower(int serial, boolean powerOn, boolean forEmergencyCall,
boolean preferredForEmergencyCall) throws RemoteException {
if (isEmpty()) return;
if (isAidl()) {
mModemProxy.setRadioPower(serial, powerOn, forEmergencyCall, preferredForEmergencyCall);
} else if (mHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_1_6)) {
((android.hardware.radio.V1_6.IRadio) mRadioProxy).setRadioPower_1_6(serial, powerOn,
forEmergencyCall, preferredForEmergencyCall);
} else if (mHalVersion.greaterOrEqual(RIL.RADIO_HAL_VERSION_1_5)) {
((android.hardware.radio.V1_5.IRadio) mRadioProxy).setRadioPower_1_5(serial, powerOn,
forEmergencyCall, preferredForEmergencyCall);
} else {
mRadioProxy.setRadioPower(serial, powerOn);
}
}
根据 HAL 版本选择接口:
- AIDL HAL:
IRadioModem.setRadioPower - HIDL 1.6+:
setRadioPower_1_6(支持紧急呼叫标志) - HIDL 1.5:
setRadioPower_1_5 - 旧版本:
setRadioPower
八、总结
关键组件
- Settings.Global.AIRPLANE_MODE_ON: 系统数据库存储状态
- Intent.ACTION_AIRPLANE_MODE_CHANGED: 系统广播通知各模块
- ServiceStateTracker: 电话模块核心控制器
- mRadioPowerOffReasons: 关闭原因集合(支持多重原因)
核心流程
开启飞行模式:
修改 Settings → 广播 → 挂断电话 → 断开数据 → setRadioPower(false) → Modem 关闭
关闭飞行模式:
修改 Settings → 广播 → setRadioPower(true) → Modem 开启 → pollState → 驻网
设计亮点
- 安全关闭: 先断开数据和电话再关闭射频,避免数据丢失
- 多重原因管理: 通过
Set<Integer>管理多个关闭原因,任一原因存在都不能开机 - 紧急呼叫保护: 通话中强制阻止飞行模式开启
- IMS 友好: 等待 IMS 注销完成再关闭射频
- 状态机驱动: 通过
mDesiredPowerState和实际状态对比,自动修正
这套机制确保了飞行模式在各种场景下的安全性和可靠性。