Android telephony面经

6 阅读12分钟

你想要 Android Telephony 面试题 吧?基于我们之前讨论的架构和源码,让我为你总结一份全面的面经题库。

完美!现在我有了足够的信息。让我为你整理一份完整的 Android Telephony 面试题库

📚 Android Telephony 面试题库

第一部分:架构和基础

1. 解释 Android Telephony 的整体架构

答案框架:

应用层 (TelephonyManager)
    ↓ Binder IPC
Framework 层 (PhoneStateListener, TelephonyCallback)
    ↓
Telephony Framework (frameworks_opt_telephony)
    ├─ Phone (GsmCdmaPhone)
    ├─ RIL (Radio Interface Layer)
    ├─ ServiceStateTracker
    ├─ DataNetworkController
    └─ PhoneSwitcher
    ↓ HIDL/Stable AIDL
vendor 厂商进程
    ├─ RadioNetworkProxy
    ├─ RadioDataProxy
    └─ DataService
    ↓
Hardware/Modem
    ├─ RIL Daemon (rild)
    └─ baseband

关键点:

  • IPC 机制:从 Binder → HIDL → Stable AIDL 的演进
  • 多进程模型system_server 中的 Telephony / vendor 进程 / netd
  • 架构分层:Framework → HIDL/AIDL → HAL → Modem

2. Phone 类为什么继承 Handler?

答案:

public abstract class Phone extends Handler implements PhoneInternalInterface {
    protected final Looper mLooper; // 保存 Looper 引用
    
    private void checkCorrectThread(Handler h) {
        if (h.getLooper() != mLooper) {
            throw new RuntimeException(
                    "Phone must be used from within one thread");
        }
    }
}

原因:

  1. 线程安全:Phone 对象只能从特定线程(Phone Looper)访问
  2. 事件处理:通过 Handler.handleMessage() 处理异步事件(RIL 回调、状态变化)
  3. 避免竞态条件:所有操作序列化到单一线程
┌──────────────────────────────────┐
│     Phone Looper Thread          │
│  (Telephony process main thread) │
│                                  │
│  ┌────────────────────────────┐  │
│  │ Message Queue              │  │
│  │ ├─ EVENT_RADIO_ON          │  │
│  │ ├─ EVENT_SERVICE_STATE_CHG │  │
│  │ └─ EVENT_DATA_CONNECTION   │  │
│  └────────────────────────────┘  │
│           ↓ Looper               │
│     handleMessage()               │
└──────────────────────────────────┘

3. GsmCdmaPhone.getNetworkSelectionMode() 的完整调用链

答案:

应用层:
  TelephonyManager.getNetworkSelectionMode(Message)
  
Framework:
  ↓
  Phone.getNetworkSelectionMode(Message)
  ↓
  GsmCdmaPhone.getNetworkSelectionMode(Message)
  │
  ├─ mCi.getNetworkSelectionMode(result)
  │  (CommandsInterface)
  │
  └─ RIL.getNetworkSelectionMode(Message result)
      ├─ radioServiceInvokeHelper(...)
      ├─ NetworkProxy.getNetworkSelectionMode(serial)
      │
      ├─ if (isAidl()) {
      │   mNetworkProxy.getNetworkSelectionMode(serial)
      │ } else {
      │   mRadioProxy.getNetworkSelectionMode(serial)
      │ }
      
Vendor/HAL:
  ↓
  IRadioNetwork.getNetworkSelectionMode(int serial)
  
Modem:
  ↓
  AT+COPS? (查询网络选择模式)
  
回程:
  ↓
  onGetNetworkSelectionMode(int serial, RadioResponseInfo info, 
                            GetNetworkSelectionModeResult result)
  ↓
  RIL.processNetworkSelectionModeResponse()
  ↓
  Message.sendToTarget()
  ↓
  应用层回调

代码引用:

public int getNumberOfModemsWithSimultaneousDataConnections() {
    return getStaticPhoneCapability().getMaxActiveDataSubscriptions();
}

第二部分:数据连接(DataNetwork)

4. 解释 DataNetwork 的状态机

答案:

                           ┌─────────┐
                           │Handover │ (IWLAN ↔ Cellular)
                           └─▲────┬──┘
                             │    │
        ┌───────────┐       ┌─┴────▼──┐       ┌──────────────┐
        │Connecting ├──────►Connected ├──────►Disconnecting │
        └─────┬─────┘       └────┬────┘       └───────┬──────┘
              │                  │                    │
              │            ┌─────▼──────┐            │
              └────────────►Disconnected◄────────────┘
                          └────────────┘

5 个状态:

1. Connecting
   - 发送 setupDataCall() 到 DataService
   - 等待 DataService 回复
   
2. Connected
   - 数据连接已建立
   - 可以收发数据
   - 监听信号/服务状态变化
   
3. Handover
   - IWLAN ↔ Cellular 切换中
   - 网络保持在 ConnectedState 数据可用
   - 完成后返回 ConnectedState
   
4. Disconnecting
   - IMS 优雅断开期间的中间状态
   - 网络仍可用,但客户端应准备断开
   
5. Disconnected
   - 最终状态,无法恢复
   - 数据网络已释放

触发条件:

setupDataNetwork()
  → Connecting
    → setupDataCall() 成功
      → Connected
        ├─ 需要 Handover
        │   → Handover
        │     → Connected
        │
        └─ 需要断开
            → Disconnecting
              → deactivateDataCall()
                → Disconnected

setupDataCall() 失败
  → Disconnected (直接)

5. DataNetworkController 的核心职责

答案:

DataNetworkController (per-SIM)
│
├─ 管理多个 DataNetwork 实例
│  └─ mDataNetworkList: List<DataNetwork>
│     ├─ Internet (default APN)
│     ├─ IMS (IMS APN)
│     ├─ MMS (MMS APN)
│     └─ Enterprise (企业数据)
│
├─ 网络请求处理
│  ├─ addNetworkRequest(TelephonyNetworkRequest)
│  ├─ removeNetworkRequest(int requestId)
│  └─ 将请求附加到兼容的 DataNetwork
│
├─ 数据评估
│  ├─ evaluateNetworkRequest()
│  ├─ 检查数据是否允许
│  ├─ 检查漫游政策
│  └─ 返回 DataEvaluation (允许/禁止原因)
│
├─ 切换(Handover)管理
│  ├─ tryHandoverDataNetwork()
│  ├─ IWLAN ↔ Cellular 判断
│  └─ 执行切换过程
│
└─ 状态聚合
   ├─ Internet 数据连接状态
   ├─ IMS 数据连接状态
   └─ 向上层通知状态变化

6. 双卡设备上数据连接如何管理?

答案:

设备有 2 个 SIM 卡:SIM1 (主卡)、SIM2 (副卡)

PhoneSwitcher
  ├─ mPreferredDataPhoneId = 0 (默认主卡)
  └─ 决定 DDS (Default Data Subscription)

每个 SIM 有独立的 DataNetworkController:

┌──────────────────────────────┐
│   Phone 0 (SIM1)             │
│   DataNetworkController      │
│   ├─ mDataNetworkList        │
│   │  ├─ Internet: Connecting │
│   │  └─ IMS: Connected       │
│   └─ mInternetDataNetworkState
└──────────────────────────────┘

┌──────────────────────────────┐
│   Phone 1 (SIM2)             │
│   DataNetworkController      │
│   ├─ mDataNetworkList        │
│   │  └─ (空,非 DDS)         │
│   └─ mInternetDataNetworkState: IDLE
└──────────────────────────────┘

DDS 切换 (自动双卡切换):
  PhoneSwitcher.onRequireImmediatelySwitchToPhone(1)
  ↓
  mPreferredDataPhoneId = 1
  ↓
  Phone 0: 断开 Internet 连接
  Phone 1: 建立 Internet 连接

第三部分:智能双卡切换(Auto Data Switch)

7. 智能双卡切换的 8 种触发原因

答案:

public class AutoDataSwitchController extends Handler {
    // 1. 注册状态变化 (OOS → 有信号)
    EVALUATION_REASON_REGISTRATION_STATE_CHANGED
    
    // 2. 5G 显示信息变化 (5G NSA ↔ 5G SA)
    EVALUATION_REASON_DISPLAY_INFO_CHANGED
    
    // 3. 信号强度变化
    EVALUATION_REASON_SIGNAL_STRENGTH_CHANGED
    
    // 4. 默认网络变化 (WiFi 连接/断开)
    EVALUATION_REASON_DEFAULT_NETWORK_CHANGED
    
    // 5. 数据设置变化 (用户关闭移动数据)
    EVALUATION_REASON_DATA_SETTINGS_CHANGED
    
    // 6. 验证失败重试
    EVALUATION_REASON_RETRY_VALIDATION
    
    // 7. SIM 卡加载完成
    EVALUATION_REASON_SIM_LOADED
    
    // 8. 语音通话结束
    EVALUATION_REASON_VOICE_CALL_END
}

8. 信号强度打分系统如何工作?

答案:

// 根据网络类型 (RAT) 和信号强度计算分数
getAutoDataSwitchScore(TelephonyDisplayInfo, SignalStrength) 
  → mAutoDataSwitchNetworkTypeSignalMap.get(networkType)
  → scores[signalStrength.getLevel()]

打分表(例如):
┌──────────────────────────────────────────┐
│ 信号强度   NONE  POOR  MODERATE  GOOD  GREAT │
├──────────────────────────────────────────┤
│ 2G (GSM)   0    10     20        30    40    │
│ 3G (UMTS)  0    20     40        60    80    │
│ 4G (LTE)   0    30     60        90    120   │
│ 5G-SA      0    40     80        120   200   │
│ 5G-NSA     0    50     100       150   200   │
└──────────────────────────────────────────┘

切换决策:
if (scoreCandidate - scoreDDS) > mScoreTolerance {
    切换到 Candidate SIM
}

例子:
  DDS (LTE, 信号弱) = 30 分
  Candidate (5G, 信号强) = 120 分
  mScoreTolerance = 50
  
  120 - 30 = 90 > 50 → 切换到 Candidate
public int getAutoDataSwitchScore(@NonNull TelephonyDisplayInfo displayInfo,
        @NonNull SignalStrength signalStrength) {
    int[] scores = mAutoDataSwitchNetworkTypeSignalMap.get(
            getDataConfigNetworkType(displayInfo));
    return scores != null ? scores[signalStrength.getLevel()]
            : OUT_OF_SERVICE_AUTO_DATA_SWITCH_SCORE;
}

9. 自动切换的稳定性检查机制

答案:

mAutoDataSwitchAvailabilityStabilityTimeThreshold
  └─ 服务状态稳定时间(防止频繁切换)
  └─ 例如:30 秒
  
应用场景:
  ├─ OOS → 有信号:需等待 30s 确保信号稳定
  ├─ 从 LTE 切到 5G:需等待 30s 确保 5G 覆盖稳定
  
重试延迟(指数退避):
  第 1 次失败:延迟 = 30 秒
  第 2 次失败:延迟 = 60 秒
  第 3 次失败:延迟 = 120 秒
  ...
  
  计算:delayMs = threshold << failureCount
  
mAutoDataSwitchPerformanceStabilityTimeThreshold
  └─ 信号/RAT 变化稳定时间(基于分数的切换)
  └─ 例如:60 秒

验证机制:
  切换前 → 执行 Ping 测试 (CellularNetworkValidator)
  └─ 如果失败,触发重试逻辑

10. DDS (Default Data Subscription) 切换的优先级

答案:

优先级从高到低:

1. 紧急电话 (最高优先级)
   └─ overrideDefaultDataForEmergency()
   └─ 临时切换数据卡到紧急卡

2. 语音通话中的卡
   ├─ isAnyVoiceCallActiveOnDevice() && isUserDataEnabled()
   ├─ 如果启用"通话中使用数据"
   └─ shouldSwitchDataDueToInCall() → 切换到语音卡

3. 机制性切换 (用户/运营商配置)
   ├─ 用户设置的默认数据卡
   ├─ Opportunistic 卡 (CBRS)
   └─ 自动数据切换推荐

4. 自动双卡切换 (最低优先级)
   ├─ AutoDataSwitchController 推荐
   ├─ 网络质量更优的卡
   └─ 但不能违反上层优先级

第四部分:Handover(切换)

11. IWLAN ↔ Cellular Handover 的完整过程

答案:

初始状态:DataNetwork 在 Cellular (LTE)
  ├─ mTransport = TRANSPORT_TYPE_WWAN
  └─ mCid (Cellular) = 有效值

触发 Handover:
  WiFi 连接 → AccessNetworksManager 通知
  ↓
  DataNetworkController.tryHandoverDataNetwork()
  ├─ targetTransport = TRANSPORT_TYPE_WLAN
  ├─ evaluateDataNetworkHandover() → 评估是否允许
  └─ dataNetwork.startHandover(TRANSPORT_TYPE_WLAN)

DataNetwork 状态转换:
  Connected (WWAN)
    ↓
    (发送 startHandover 到 target transport)
    ↓
    Handover 状态 (中间态)
    ├─ setupDataCall(WLAN, mDataProfile)
    ├─ 等待 DataService 回复
    │
    ├─ ✓ 成功:
    │   ├─ 切换 mTransport = WLAN
    │   ├─ deactivateDataCall(WWAN, cid)
    │   ├─ mCid 从 WWAN 切到 WLAN
    │   ├─ 更新 mLogTag
    │   └─ 返回 Connected (WLAN)
    │
    └─ ✗ 失败:
        ├─ cancelHandover(WWAN)
        ├─ 保持 Connected (WWAN)
        ├─ 触发重试逻辑
        └─ 返回 Connected (WWAN)

关键点:
  ├─ PDU Session ID:在 source 和 target 之间保持
  ├─ LinkProperties:可能需要更新
  ├─ 网络能力:可能需要更新 (QoS 不同)
  └─ 超时处理:30 秒无响应则失败
public boolean startHandover(@TransportType int targetTransport,
        @Nullable DataHandoverRetryEntry retryEntry) {
    if (getCurrentState() == null || isDisconnected() || isDisconnecting()) {
        // Fail the request if not in the appropriate state.
        if (retryEntry != null) retryEntry.setState(DataRetryEntry.RETRY_STATE_CANCELLED);
        return false;
    }

    // Before we really initiate the handover process on target transport, we need to notify
    // source transport that handover is about to start. Handover will be eventually initiated
    // in onStartHandover().
    sendMessage(obtainMessage(EVENT_NOTIFY_HANDOVER_STARTED, 0, targetTransport, retryEntry));
    return true;
}

12. 何时执行 Handover?何时不执行?

答案:

执行 Handover 的情况:

WiFi 连接时:WWAN → WLAN (离线模式)
✓ WiFi 断开时:WLAN → WWAN (恢复蜂窝)
✓ 用户设置允许 IWLAN 切换
✓ 运营商配置允许(KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY)
✓ 目标传输有服务(NRI.isInService() == true

不执行 Handover 的情况:

✗ 数据已断开 (isDisconnected() || isDisconnecting())
✗ 数据设置禁用
✗ 通话中且需要 VoPS(Voice over PS)
✗ SIM 不可用
✗ Dual SIM 冲突(某些运营商)
✗ 紧急电话进行中
✗ 运营商禁用该方向切换
✗ 目标传输 OOS
✗ 数据漫游但目标传输漫游被禁

第五部分:权限和 IPC

13. Android Telephony 中的主要权限及其作用

答案:

┌────────────────────────────────────────────────┐
│ READ 权限 (只读)                               │
├────────────────────────────────────────────────┤
│ READ_PHONE_STATE                               │
│  └─ 读取电话状态、运营商信息、SIM IMSI       │
│  └─ 应用层:TelephonyManager.getSimOperator()  │
│                                                │
│ READ_PRIVILEGED_PHONE_STATE                    │
│  └─ 只有系统应用可用                          │
│  └─ 读取 IMEI、IMSI、运营商 ID、通话记录     │
│                                                │
│ ACCESS_NETWORK_STATE                           │
│  └─ 读取网络连接状态                          │
│  └─ 由 ConnectivityManager 使用               │
└────────────────────────────────────────────────┘

┌────────────────────────────────────────────────┐
│ WRITE 权限 (修改)                              │
├────────────────────────────────────────────────┤
│ MODIFY_PHONE_STATE                             │
│  └─ 启用/禁用飞行模式                         │
│  └─ 修改 SIM 卡密码                           │
│  └─ 非系统应用无法获得                        │
│                                                │
│ SEND_SMS / RECEIVE_SMS                         │
│  └─ 发送/接收短信                             │
│                                                │
│ CALL_PHONE                                     │
│  └─ 发起电话                                  │
└────────────────────────────────────────────────┘

┌────────────────────────────────────────────────┐
│ Carrier Privileges (运营商特权)                │
├────────────────────────────────────────────────┤
│ 运营商可使用特殊权限:                         │
│  ├─ 查询/设置 APN                              │
│  ├─ 修改网络模式                              │
│  ├─ 使用 SMS 接收 OTA 更新                    │
│  └─ 通过 SIM 卡证书验证                       │
└────────────────────────────────────────────────┘

14. 解释 /dev/binder 和 /dev/hwbinder 的区别

答案:

/dev/binder (传统 Binder)
├─ System Server ↔ Framework 进程
├─ Framework ↔ 应用
├─ 例如:
│  └─ TelephonyManager 查询 Phone 信息
│     → Binder IPC
│     → system_server (Telephony Manager Service)
│     → RIL
│
├─ Stable AIDL:
│  └─ framework_opt_telephony
│  └─ aidl/com/android/internal/telephony/
│  └─ 跨进程通信

/dev/hwbinder (HIDL Binder)
├─ Framework ↔ Vendor HAL 进程
├─ 例如:
│  └─ RIL.getNetworkSelectionMode()
│     → /dev/hwbinder (serialization)
│     → vendor 进程中的 IRadioNetwork
│     → Radio HAL
│
├─ HIDL (Hardware Interface Definition Language)
│  └─ 厂商进程和 Framework 通信
│  ├─ android.hardware.radio@1.0
│  ├─ android.hardware.radio@1.5
│  └─ android.hardware.radio@1.6

关键区别:
┌─────────────────────┬──────────────┬────────────────┐
│ 方面                 │ /dev/binder  │ /dev/hwbinder  │
├─────────────────────┼──────────────┼────────────────┤
│ 通信对象              │ Framework 内 │ Framework-厂商  │
│ 序列化方式           │ Parcel       │ HIDL Parcel    │
│ API 版本             │ 同一个       │ 版本化的 HAL   │
│ 兼容性               │ 松耦合       │ 紧耦合         │
└─────────────────────┴──────────────┴────────────────┘

第六部分:高级问题

15. 如何实现 SMS 延迟返回的解决方案?

答案:

问题场景:
  应用调用 SmsManager.sendTextMessage()
    ↓
  Telephony Framework 立即返回 AT+CMGS 命令
    ↓
  Modem 正在查询运营商信息(可能需要几秒)
    ↓
  应用无法知道真正的发送延迟

解决方案:

1. 缓存运营商信息 (ServiceStateTracker)
   ├─ 定期查询并缓存运营商 MCC/MNC
   ├─ 避免每次发送 SMS 都查询
   └─ 用 SharedPreferences 持久化缓存

2. 异步处理 (Telephony Framework 层)
   ├─ SmsTracker:追踪发送状态
   ├─ sendMessage() 延迟处理
   └─ 在 Phone Looper 中序列化

3. AT 命令优化 (Modem 层)
   ├─ 预加载 UDH (User Data Header)
   ├─ 减少 Modem 查询次数
   └─ 合并多个 AT 命令

4. 应用层优化
   ├─ 使用 PendingIntent 接收发送结果
   ├─ 异步回调而非阻塞等待
   └─ 批量发送多条 SMS

实现参考:
├─ InboundSmsHandler / OutboundSmsHandler
├─ SmsTracker 管理发送队列
├─ ServiceStateTracker 缓存信息
└─ Message 机制异步处理

16. 双卡搜网逻辑(NetworkScan)如何工作?

答案:

NetworkScanRequestTracker (per-SIM)

触发搜网:
  应用 → TelephonyManager.requestNetworkScan()
    ↓ Binder IPC
  NetworkScanRequestTracker.startNetworkScan()
    ↓
  RIL.startNetworkScan()
    ↓ HIDL/Stable AIDL
  RadioNetworkProxy.startNetworkScan()
    ↓
  Modem 开始搜网

双卡场景:
┌──────────────────────────────────────┐
│ SIM1 搜网进行中                       │
│ NetworkScanRequestTracker-0.start()   │
│  ├─ 发送 AT+COPS=? 到 Modem 1        │
│  ├─ 超时:30 秒                      │
│  └─ 状态:SCAN_ACTIVE                │
└──────────────────────────────────────┘

┌──────────────────────────────────────┐
│ 同时 SIM2 搜网                       │
│ NetworkScanRequestTracker-1.start()   │
│  ├─ 发送 AT+COPS=? 到 Modem 2        │
│  ├─ 超时:30 秒                      │
│  └─ 状态:SCAN_ACTIVE                │
└──────────────────────────────────────┘

搜网结果处理:
  onNetworkScanResult()
  ├─ 解析 Modem 返回的网络列表
  ├─ 排序并过滤
  └─ 通知应用或 UI

DSDS 仲裁(争夺 Modem 资源):
  ├─ 如果 Modem 资源有限
  ├─ PhoneSwitcher 决定优先级
  └─ 优先搜网 DDS (首选数据卡)

17. WakeLock 在 Telephony 中的作用

答案:

WakeLock 类型:
├─ PARTIAL_WAKE_LOCK (最常用)
│  └─ 保持 CPU 运行,允许屏幕关闭
│  └─ 用途:处理 RIL 回调、发送 SMS
│
├─ FULL_WAKE_LOCK (废弃)
│  └─ 保持屏幕亮起
│
└─ SCREEN_DIM_WAKE_LOCK (废弃)
   └─ 屏幕变暗但亮起

Telephony 使用场景:

1. RIL 回调处理
   ├─ RILRequest 获取 WakeLock
   ├─ 执行回调
   └─ 回调完成后释放
   
   ├─ 原因:避免 RIL 回调时 CPU 进入睡眠

2. SMS 发送
   ├─ OutboundSmsHandler 获取 WakeLock
   ├─ 处理 SMSC 地址查询
   ├─ 发送 AT 命令到 Modem
   └─ 等待确认
   
   ├─ 原因:确保 SMS 可靠发送

3. 来电/来短信
   ├─ InboundSmsHandler 获取 WakeLock
   ├─ 处理短信存储
   ├─ 触发通知
   └─ 唤醒屏幕
   
   ├─ 原因:及时处理来电/短信

代码示例:
// 获取 WakeLock
PowerManager pm = mContext.getSystemService(PowerManager.class);
WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "tag");
wl.acquire(timeout);

try {
    // 关键操作
    processRilResponse();
} finally {
    wl.release(); // 必须在 finally 中释放
}

18. 如何在 Framework 层延迟返回 AT 命令结果?

答案:

需求:
  App 调用 getSimOperator()
  → Framework 需要查询 Modem
  → AT+COPS? (可能需要几秒)
  → 返回结果给 App

实现方案:

1. 使用 Message 机制延迟 (推荐)
   
   getNetworkSelectionMode(Message msg) {
       RILRequest rr = obtainRequest(..., msg);
       mRadioProxy.getNetworkSelectionMode(rr.mSerial);
       
       // msg 会在 Modem 回复后通过 Handler 投递
       // Message 会进入 Phone Looper 的 MessageQueue
       // 在适当时机由 handleMessage() 处理
   }

2. 使用 Callback + Executor 延迟

   getNetworkSelectionMode(Executor executor, 
                          Consumer<Integer> callback) {
       executor.execute(() -> {
           // 在指定 Executor (线程) 上执行
           // 可以有延迟
           int result = queryModem();
           callback.accept(result);
       });
   }

3. 使用定时器延迟

   sendMessageDelayed(msg, delayMillis) {
       // 延迟指定时间后投递消息
       // 在 Phone Looper 中处理
   }

关键点:
├─ 所有操作必须在 Phone Looper 中序列化
├─ 不能阻塞 Binder 线程
├─ 使用 Message / Handler 机制
└─ Callback 通过 Executor 异步返回

第七部分:故障排查和性能

19. 如何调试 Telephony 中的竞态条件?

答案:

常见竞态条件:

1. Phone Looper 线程问题
   问题:从错误的线程访问 Phone 对象
   
   调试方法:
   ├─ checkCorrectThread() 抛出异常
   └─ 堆栈追踪定位调用者
   
   解决:使用 Message 投递到 Phone Looper
   
2. 订阅/状态同步问题
   问题:DDS 切换时订阅 ID 不一致
   
   调试方法:
   ├─ 查看 logcat 中 [ADSC] / [PhoneSwitcher] 日志
   ├─ 检查 mPreferredDataPhoneId
   ├─ 对比 mPhoneSubscriptions[]
   └─ 验证 mPreferredDataSubId 一致性
   
   tools/emulator:
   └─ adb shell "dumpsys telephony.registry"

3. Handover 失败
   问题:IWLAN ↔ Cellular 切换时卡住
   
   调试方法:
   ├─ 检查 DataNetwork 状态机状态
   ├─ 查看超时日志
   ├─ 验证 startHandover() 是否调用
   └─ 确认 DataService 是否响应
   
   工具:
   └─ adb shell "dumpsys connectivity"

4. SMS/通话延迟
   问题:处理缓慢导致用户感知延迟
   
   调试方法:
   ├─ 启用 VDBG 日志
   ├─ 检查 InboundSmsHandler 队列
   ├─ 验证 Phone Looper 是否被阻塞
   └─ 监控 Binder 线程池
   
   命令:
   └─ adb shell "dumpsys telephony" | grep EventLog

20. Telephony 性能优化的最佳实践

答案:

1. 避免主线程 IPC
   ✗ 错误:
   TelephonyManager tm = context.getSystemService(...);
   String operatorName = tm.getNetworkOperatorName(); // 阻塞
   
   ✓ 正确:
   TelephonyCallback callback = new TelephonyCallback() {
       void onDisplayInfoChanged(TelephonyDisplayInfo info) {
           // 异步回调
       }
   };

2. 缓存 Telephony 查询结果
   ✗ 错误:
   for (int i = 0; i < 1000; i++) {
       String imei = tm.getDeviceId(); // 重复查询
   }
   
   ✓ 正确:
   String cachedImei = tm.getDeviceId();
   for (int i = 0; i < 1000; i++) {
       // 使用缓存
   }

3. 监听变化而非轮询
   ✗ 错误:
   new Thread(() -> {
       while (true) {
           String operator = tm.getNetworkOperator();
           Thread.sleep(1000); // 浪费电量
       }
   }).start();
   
   ✓ 正确:
   tm.registerTelephonyCallback(executor, callback);

4. 合理使用 WakeLock
   ✗ 错误:
   wl.acquire(); // 永不释放
   
   ✓ 正确:
   wl.acquire(timeout); // 或在 finally 中释放

5. 优化搜网过程
   ├─ 限制搜网频率
   ├─ 缓存搜网结果
   └─ 只在需要时启动搜网

6. 数据连接优化
   ├─ 使用 Handover 而非重连
   ├─ 批量处理网络请求
   └─ 避免频繁切换 DDS

📖 总结:面试准备清单

理解部分 (必须掌握):
✓ Telephony 整体架构(3 层模型)
✓ Phone Looper 和线程模型
✓ IPC 机制(Binder → HIDL → Stable AIDL)
✓ DataNetwork 状态机(5 个状态)
✓ Handover 流程(IWLAN ↔ Cellular)

深入部分 (加分项):
✓ 自动双卡切换算法(打分系统)
✓ 稳定性检查和重试机制
✓ Telephony 权限体系
✓ WakeLock 管理
✓ 竞态条件调试

代码理解部分:
✓ 阅读核心类源码(GsmCdmaPhone、RIL、DataNetworkController)
✓ 理解回调链(onXxx 方法)
✓ 追踪 Message 流向

实战部分:
✓ 能描述完整的 API 调用链
✓ 能识别潜在的性能问题
✓ 能分析日志定位 bug

这份面经涵盖了 Android Telephony 的核心概念、架构设计、关键算法和实战经验。 💪 希望能帮助你在面试中有所收获!