第10章 车载电话与通讯:CarPhoneService、通话状态管理、联系人同步、短信与通知

车载电话模块,说实话,是汽车智能化里最容易被低估的一块。很多人觉得不就是个蓝牙电话嘛,但真正做过才知道,这里面的坑多到能让你怀疑人生。我当年接手第一个车载项目时,就天真地以为调通HFP(Hands-Free Profile)就完事了,结果被通话状态管理、联系人同步这些细节折磨得够呛。

今天我们就来聊聊Android Automotive里电话与通讯的那些事。我会把CarPhoneService、通话状态管理、联系人同步、短信与通知这几个核心点掰开揉碎了讲。

10.1 CarPhoneService:车载电话的“大脑”

CarPhoneService是Android Automotive中专门管理电话功能的服务。它不像手机上的TelephonyManager那样直接操作SIM卡,而是通过蓝牙HFP与手机交互。

说白了,车机本身不打电话,它只是手机的“遥控器”加“扬声器”。

核心职责:

  • 管理蓝牙HFP连接
  • 转发通话状态到UI层
  • 处理联系人同步请求
  • 管理通话记录

它的架构其实挺清晰的。我画了张图,你看一眼就明白了:

CarPhoneService 架构图 CarPhoneService 通话状态管理 联系人同步 短信与通知 HFP状态机 通话记录管理 PBAP协议解析 联系人数据库 MAP协议 通知渠道管理 底层依赖:BluetoothAdapter + TelephonyManager 上层暴露:CarPhoneService API + ContentProvider

你看,CarPhoneService处在中间层,向上给UI提供接口,向下管理蓝牙协议。我习惯把它理解成一个“翻译官”——把手机那边的电话事件,翻译成车机能听懂的语言。

10.2 通话状态管理:别让用户懵圈

通话状态管理,说白了就是让车机知道“现在电话在干嘛”。是正在响铃?还是通话中?还是挂断了?

Android Automotive里定义了这么几种状态:

状态 含义 典型场景
CALL_STATE_IDLE 空闲 没有通话
CALL_STATE_RINGING 来电响铃 有来电,等待接听
CALL_STATE_OFFHOOK 摘机 通话中或正在拨号
CALL_STATE_DIALING 拨号中 正在呼出
CALL_STATE_ALERTING 对方响铃 拨出后等待对方接听

嗯,这里要注意。CALL_STATE_OFFHOOK和CALL_STATE_DIALING是有重叠的。我刚开始做的时候,就踩过这个坑——以为OFFHOOK就是通话中,结果发现拨号时也是OFFHOOK。后来我加了个子状态判断才搞定。

我的经验:建议在UI层不要直接依赖TelephonyManager的通话状态,而是通过CarPhoneService封装一层。这样即使底层协议变了,UI也不用改。

来看一段核心代码,这是监听通话状态的标准写法:

// 注册通话状态监听
TelephonyManager telephonyManager = 
    (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);

telephonyManager.listen(new PhoneStateListener() {
    @Override
    public void onCallStateChanged(int state, String phoneNumber) {
        switch (state) {
            case TelephonyManager.CALL_STATE_IDLE:
                // 空闲状态,更新UI
                updateCallUI(CallState.IDLE);
                break;
            case TelephonyManager.CALL_STATE_RINGING:
                // 来电,显示来电界面
                showIncomingCallUI(phoneNumber);
                break;
            case TelephonyManager.CALL_STATE_OFFHOOK:
                // 通话中,显示通话界面
                showActiveCallUI(phoneNumber);
                break;
        }
    }
}, PhoneStateListener.LISTEN_CALL_STATE);

这段代码看着简单,但实际项目中你还要处理多路通话、通话保持、三方通话这些复杂场景。我曾经在一个项目中,用户反馈说“挂断电话后界面还显示通话中”,查了半天发现是状态回调延迟导致的。解决方案是加了个超时机制——如果IDLE状态超过500ms还没收到,就强制重置UI。

10.3 联系人同步:PBAP协议的那些事

联系人同步,用的是PBAP(Phone Book Access Profile)。车机通过蓝牙从手机拉取联系人数据。

这里有个关键点:PBAP有两种同步方式。

  • 全量同步:每次连接时,把手机通讯录全部拉下来。简单粗暴,但耗时长。
  • 增量同步:只同步变化的部分。效率高,但实现复杂。

我个人建议,第一次连接时做全量同步,之后用增量同步。但要注意,有些手机对PBAP的支持不太好,增量同步可能会漏掉联系人。我遇到过一台某品牌手机,每次增量同步都会漏掉姓“张”的联系人,最后只能改成每次全量同步。

避坑指南:我曾经因为联系人同步太慢被客户投诉。后来发现是数据库写入没做批量处理,一条一条insert当然慢。改成ContentProvider的bulkInsert后,速度提升了10倍。

联系人数据在车机端的存储,我建议用ContentProvider封装。这样其他应用(比如语音助手、导航)也能方便地读取联系人。

// 批量插入联系人
ContentValues[] valuesArray = new ContentValues[contactList.size()];
for (int i = 0; i < contactList.size(); i++) {
    Contact contact = contactList.get(i);
    ContentValues values = new ContentValues();
    values.put(ContactsContract.Contacts.DISPLAY_NAME, contact.getName());
    values.put(ContactsContract.Contacts.PHOTO_URI, contact.getPhotoUri());
    // ... 其他字段
    valuesArray[i] = values;
}
getContentResolver().bulkInsert(ContactsContract.Contacts.CONTENT_URI, valuesArray);

你想想看,如果不用bulkInsert,1000个联系人就要执行1000次数据库操作,那体验能好吗?

10.4 短信与通知:MAP协议的应用

短信和通知,用的是MAP(Message Access Profile)。车机通过这个协议读取手机上的短信和通知。

MAP协议分两种角色:

  • MSE(Message Server Equipment):手机端,提供消息数据
  • MCE(Message Client Equipment):车机端,消费消息数据

车机作为MCE,向手机MSE请求消息列表,然后解析显示。这里有个细节——MAP协议传输的是纯文本,所以表情符号、富文本这些需要车机自己处理。

核心流程:

  1. 车机通过蓝牙与手机建立MAP连接
  2. 车机发送GetMessageListing请求,获取消息列表
  3. 车机根据消息ID,发送GetMessage请求获取具体内容
  4. 车机解析消息内容,显示到UI上
  5. 收到新消息通知时,车机主动拉取新消息

说实话,短信这块现在用得越来越少了。大家更习惯用微信、WhatsApp这些。但车载系统还是要支持,毕竟有些场景下(比如开车时),语音播报短信比看手机安全得多。

通知这块,Android Automotive有自己的一套通知管理机制。车机上的通知和手机上的通知是独立的,但可以通过蓝牙同步。我建议把通知分成三类:

通知类型 处理方式 示例
紧急通知 立即播报+显示 安全警报、紧急联系人来电
普通通知 显示但不播报 短信、App推送
静默通知 仅记录,不打扰 系统更新、后台任务

嗯,这里要注意。开车时不要一股脑把所有通知都播报出来,那会分散驾驶员注意力。我见过一个项目,把微信群里每条消息都播报,结果驾驶员差点撞车。后来我们加了个“驾驶模式”开关,只有紧急通知才会播报。

我的习惯:在实现通知功能时,我会先定义一个通知优先级枚举。然后根据当前驾驶状态(行驶中/停车中)动态调整通知的展示方式。这样既安全又灵活。

最后说一句,车载电话与通讯这块,技术实现其实不难,难的是在各种手机、各种蓝牙芯片上都能稳定工作。我建议你在开发阶段就准备至少10台不同品牌的手机做兼容性测试。别问我怎么知道的——都是泪的教训。


公众号:蓝海资料掘金营,微信deep3321