有段时间没更新文章了,向各位朋友们抱拳了(主要这段时间项目比较忙,加班狗…)后续希望挤出更多时间来持续更新,那咱就赶紧开始本期的安卓避坑分享吧。
测试环境:android-9车机系统(高通源码)
测试步骤:
测试现象:配对连接失败
乍一看,操作步骤很正常啊,和大伙平常连接的操作是不是一模一样,怎么会失败呢?查看logcat如下:
07-04 02:43:36.243 6346 6601 I bt_btm : BTM_SecBond: Remote sm4: 0x0 HCI Handle: 0xffff
07-04 02:43:36.243 6346 6601 D bt_btm : sec mode: 4 sm4:x0
07-04 02:43:36.243 6346 6411 D BluetoothDevice: mAddress: C8:14:51:27:23:9A
07-04 02:43:36.243 6346 6601 I bt_btm : btm_sec_change_pairing_state() Old: IDLE
07-04 02:43:36.243 6346 6601 I bt_btm : btm_sec_change_pairing_state() New: GET_REM_NAME pairing_flags:0x1
07-04 02:43:36.243 6346 6411 D BluetoothDevice: mAddress: C8:14:51:27:23:9A
07-04 02:43:36.243 6346 6601 D bt_btm : State:GET_REM_NAME sm4: 0x0 sec_state:0
07-04 02:43:36.243 6346 6601 E bt_btm : btm_sec_bond_by_transport BTM_ReadRemoteDeviceName or btm_sec_dd_create_conn error: 0x2
07-04 02:43:36.243 6346 6601 I bt_btm : btm_sec_change_pairing_state() Old: GET_REM_NAME
07-04 02:43:36.243 6346 6601 I bt_btm : btm_sec_change_pairing_state() New: IDLE pairing_flags:0x1
07-04 02:43:36.243 6346 6411 D BluetoothDevice: mAddress: C8:14:51:27:23:9A
07-04 02:43:36.243 6346 6601 D bt_btm : btm_sec_clear_ble_keys() Clearing BLE Keys
07-04 02:43:36.244 6346 6601 I bt_btm : btm_ble_resolving_list_remove_dev
07-04 02:43:36.244 6346 6601 D bt_btm : Device not in resolving list
07-04 02:43:36.244 6346 6601 I bt_btm : BTM: BTM_DeleteStoredLinkKey: delete_all_flag: false
02:43:36.243 这条logcat明显看出配对请求远端设备的蓝牙名字失败,进而导致整个配对连接的过程失败。
根据 error:0x2 这条log,查看源码得出当前系统有一条读取远端名的命令正在执行中,所以配对过程中需要获取新设备的蓝牙名字时由于 p_inq->remname_active 为 true,从而返回 BTM_BUSY 的status值。
这样的分析更让人头懵了,蓝牙服务层 AdapterService.createBond() 方法中,在配对之前已经下发了取消搜索的命令。
按照常理来说,协议栈中配对流程开始时,相关变量应该已重置到初始值才对,那现在的问题就是:已经执行了 cancel discovery 操作,为啥 p_inq->remname_active 值还没重置为 false ?
要解决上述问题,那难免不追问一句 p_inq->remname_active 是何时置为 true 的,还是在源码中追踪,蓝牙协议栈源码中只有一处将上述变量置为 true,也就是 btm_initiate_rem_name() 中下发读远端名HCI命令后,标志着正在读取一个设备的蓝牙名字。
蓝牙相关流程中涉及到读远端名操作的一个是配对流程,而另一个就是搜索中的 Discovery 流程。
应用层发起蓝牙搜索指令到达协议栈会分为两个阶段执行:inquiry + discovery
至此我们大致清楚该问题发生的时间点:搜索蓝牙流程中正在对某一个扫描到的蓝牙设备请求远端名时,p_inq->remname_active = true,应用开启配对流程。
而 p_inq->remname_active 置为 false 也只有一处地方btm_process_remote_name(),有以下三种情况会调用到该函数:
正常都是第三种情况调用上述函数将 p_inq->remname_active 置为false的。
再次回到本文章的场景,应用层在底层有请求远端名的discovery流程时开启了配对流程。
蓝牙服务会优先将搜索流程取消掉,协议栈对应的动作就是下发取消读远端名的HCI命令;蓝牙服务层在下发完cancelDiscoveryNative(),就立即下发配对指令。而协议栈在下发完 HCI_RMT_NAME_REQUEST_CANCEL 后并不会立即将 p_inq->remname_active 置为false,需要收到远端名请求的完成事件才设置成false
配对流程发起的读远端名的操作时间点正好处于上面截图中完成事件之前,所以 p_inq->remname_active 依然为 true,配对失败。
分析到这里想必大家都知道问题的根因了,这就是个时序导致的问题。清楚问题根因那解决方案也就水到渠成,暂时能想到的方案有如下几种。
解决方案:
上述A、B、C这三种方案按照逻辑分析,还是存在该问题中的时序问题,但是概率能够极大降低。
方案D能够保证配对发起的读远端名操作时,p_inq->remname_active 的值为 false,不影响配对流程的进行。
欢迎大家留言一起讨论其他方案,本期是安卓源码避坑指南系列的第四篇文章,想了解更多安卓源码bug的同学可以翻看以前的系列文章,共同学习,一起进步。