12、电话与通讯集成:TelecomManager API、蓝牙HFP通话、通讯录同步、来电显示与接听
车机上的电话功能,说白了就是让车变成一个大号蓝牙耳机加触控屏。用户上车,手机自动连上,然后就能在车机上打电话、看通讯录、甚至直接点屏幕接听来电。这个体验做得好不好,直接决定了用户对车机系统的第一印象。
我参与过好几个车机互联项目,电话模块永远是用户吐槽最多的地方。不是接听延迟,就是通讯录同步不全,再就是来电显示乱码。嗯,今天我们就来把这些坑一个一个填上。
12.1 TelecomManager API:车机通话的底层管家
TelecomManager 是 Android 系统里管理通话的核心 API。它负责处理电话的接听、挂断、静音、保持等操作。在车机场景下,我们主要用它来做两件事:
- 监听通话状态:知道当前是空闲、响铃还是通话中
- 控制通话行为:接听、挂断、切换音频通道
先看怎么获取这个服务:
TelecomManager telecomManager = (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
获取之后,我们可以注册一个通话监听器:
telecomManager.registerPhoneAccountListener(new PhoneAccountListener() {
@Override
public void onCallStateChanged(int state, String phoneNumber) {
switch (state) {
case TelecomManager.CALL_STATE_IDLE:
// 空闲状态
break;
case TelecomManager.CALL_STATE_RINGING:
// 来电响铃
break;
case TelecomManager.CALL_STATE_OFFHOOK:
// 通话中
break;
}
}
});
这里有个细节要注意。我在项目中遇到过,有些车机厂商会定制 TelecomManager 的实现,导致回调不及时。我的建议是:不要完全依赖这个回调来做 UI 更新,最好同时监听蓝牙 HFP 的状态作为补充。
android.permission.READ_PHONE_STATE 和 android.permission.MANAGE_OWN_CALLS。Android 10 以上还需要动态申请。
12.2 蓝牙HFP通话:音频通道的建立与切换
HFP(Hands-Free Profile)是蓝牙协议里专门用于车载免提通话的规范。车机通过 HFP 与手机建立音频通道,实现语音的上行和下行。
在 Android Automotive 中,蓝牙 HFP 的连接通常由系统服务自动管理。但作为开发者,我们需要知道如何获取 HFP 的状态,以及如何切换音频通道。
获取蓝牙适配器并检查 HFP 连接:
BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
BluetoothProfile.ServiceListener profileListener = new BluetoothProfile.ServiceListener() {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (profile == BluetoothProfile.HEADSET) {
BluetoothHeadset headset = (BluetoothHeadset) proxy;
List<BluetoothDevice> devices = headset.getConnectedDevices();
// 检查是否有已连接的 HFP 设备
}
}
@Override
public void onServiceDisconnected(int profile) {
// 处理断开
}
};
bluetoothAdapter.getProfileProxy(context, profileListener, BluetoothProfile.HEADSET);
音频通道切换是个容易出问题的地方。我记得有一次,用户接听电话后,声音还是从手机扬声器出来,车机喇叭没响。排查了半天,发现是 HFP 的 SCO(同步面向连接)链路没有正确建立。
BluetoothHeadset.startVoiceRecognition() 来强制建立 SCO 链路。虽然这个方法名字叫"语音识别",但它实际上就是用来建立 HFP 音频通道的。
12.3 通讯录同步:从手机到车机的数据流转
通讯录同步,说白了就是把手机里的联系人信息搬到车机上。这样用户就能在车机屏幕上直接搜索联系人、拨打电话。
Android Automotive 提供了两种同步方式:
| 方式 | 协议 | 特点 |
|---|---|---|
| PBAP(Phone Book Access Profile) | 蓝牙 | 自动同步,无需用户操作,但数据量有限 |
| ContentProvider 同步 | 网络/数据线 | 数据完整,支持头像、分组,但需要应用配合 |
PBAP 是车机最常用的方式。车机作为 PBAP Client,手机作为 PBAP Server。连接建立后,车机可以拉取手机上的联系人列表。
在代码层面,我们可以通过 BluetoothMapClient 来获取联系人:
BluetoothMapClient mapClient = new BluetoothMapClient(context, profileListener);
mapClient.getContacts(); // 异步获取联系人列表
但这里有个坑。PBAP 同步回来的联系人数据,通常只包含姓名和电话号码。头像、公司、备注这些信息,很多手机厂商不会同步。我建议在车机端做一个本地缓存,每次同步时做增量更新,而不是全量替换。否则用户可能在开车途中看到通讯录"闪一下"。
12.4 来电显示与接听:用户体验的最后一步
来电显示和接听,是用户直接感知的功能。做得好,用户觉得"这车机真智能";做得不好,用户觉得"这车机真垃圾"。
来电显示的核心逻辑:
- 监听到来电事件(TelecomManager 或 HFP 回调)
- 获取来电号码
- 在本地通讯录中匹配联系人
- 显示联系人姓名、头像(如果有)
- 提供接听/挂断按钮
接听操作的代码很简单:
// 接听来电
telecomManager.acceptRingingCall();
// 挂断来电
telecomManager.endCall();
但实际开发中,问题往往出在来电显示的延迟上。我曾经遇到一个情况:电话已经响了 3 秒,车机屏幕才显示出联系人姓名。用户早就拿起手机接了,车机上的接听按钮成了摆设。
为什么会这样?因为通讯录匹配是在车机本地做的,如果通讯录数据量大(比如几千个联系人),匹配过程就会卡顿。
我的优化方案是:
- 预索引:在通讯录同步完成后,立即建立电话号码的哈希索引
- 异步匹配:来电时先显示号码,后台匹配到姓名后再更新 UI
- 缓存热门联系人:最近通话过的联系人,优先匹配
12.5 知识体系总览
下面这张图,是我梳理的电话与通讯集成的整体架构。你可以把它当作一个 roadmap,开发时对照着看,不容易漏掉关键环节。
从这张图你可以看到,整个电话与通讯集成分为三层:手机端、蓝牙协议层、车机端。车机端的四个模块各司其职,又相互协作。TelecomManager 负责通话控制,HFP 负责音频通道,通讯录同步提供数据支撑,来电显示与接听则是最终的用户交互出口。
开发时,我建议你从下往上做:先搞定蓝牙连接和 HFP 音频通道,再接入 TelecomManager 监听通话状态,然后做通讯录同步,最后才是来电显示的 UI 交互。每一步都验证通过后,再进入下一步。这样排查问题会轻松很多。