m24h作者
1年5个月前IP:上海
944482
1楼

这个软件现在遇到最大的问题 就是慢 几乎每次连上相机都需要接近10秒的时间 让人不耐

经查 主要消耗时间在ble的discoverServices()上 因为我确定 其实并不需要每次都discoverServices() 而看下面底层调用Gatt service的源码 证实保存的BluetoothGattCharacteristic是可用再用的 因为只需要用的是其getInstanceId()得到的Gatt Handle号 这个其实对本应用是不变的

 public int writeCharacteristic(
            @NonNull BluetoothGattCharacteristic characteristic,
            @NonNull byte[] value,
            @WriteType int writeType) {
.....        
         try {
            for (int i = 0; i < WRITE_CHARACTERISTIC_MAX_RETRIES; i++) {
                requestStatus =
                        mService.writeCharacteristic(
                                mClientIf,
                                device.getAddress(),
                                characteristic.getInstanceId(),
                                writeType,
                                AUTHENTICATION_NONE,
                                value,
                                mAttributionSource);

于是 我决定再次连上 如果需要的Characteristic还在 就不进行discoverServices()了

override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) { 
        ......               
                if (faithMode && characteristicGpsData!=null) {
                    gattConnected=true
                    cameraInitialized=false
                    lastSyncTime=null
                    loopActor.trySend(Unit)
                } else {
                    gatt.discoverServices()
                }

然而并没有尽全用 有时快有时慢

抓蓝牙包发现 即使我没有明式地调用discoverServices() 它仍然可能发生 有趣的是 即使第一次连接 需要明式地调用discoverServices() 它反而有可能不发生 显然后台有一个不可控的cache之类的东西

只能看AOSP源代码了 看看连接后发生了什么 底层是怎么搞cache的

void bta_gattc_conn(tBTA_GATTC_CLCB* p_clcb, const tBTA_GATTC_DATA* p_data) {
   ......

  /* start database cache if needed */
  if (p_clcb->p_srcb->gatt_database.IsEmpty() || p_clcb->p_srcb->state != BTA_GATTC_SERV_IDLE) {
    if (p_clcb->p_srcb->state == BTA_GATTC_SERV_IDLE) {
      p_clcb->p_srcb->state = BTA_GATTC_SERV_LOAD;
      // Consider the case that if GATT Server is changed, but no service
      // changed indication is received, the database might be out of date. So
      // if robust caching is known to be supported, always check the db hash
      // first, before loading the stored database.

      // Only load the database if we are bonded, since the device cache is
      // meaningless otherwise (as we need to do rediscovery regardless)
      gatt::Database db = btm_sec_is_a_bonded_dev(p_clcb->bda)
                                  ? bta_gattc_cache_load(p_clcb->p_srcb->server_bda)
                                  : gatt::Database();
      auto robust_caching_support = GetRobustCachingSupport(p_clcb, db);
      log::info("Connected to {}, robust caching support is {}",
                p_clcb->bda.ToRedactedStringForLogging(), robust_caching_support);

      if (!db.IsEmpty()) {
        p_clcb->p_srcb->gatt_database = db;
      }

      if (db.IsEmpty() || robust_caching_support != RobustCachingSupport::UNSUPPORTED) {
        // If the peer device is expected to support robust caching, or if we
        // don't know its services yet, then we should do discovery (which may
        // short-circuit through a hash match, but might also do the full
        // discovery).
        p_clcb->p_srcb->state = BTA_GATTC_SERV_DISC;

        /* set true to read database hash before service discovery */
        p_clcb->p_srcb->srvc_hdl_db_hash = true;

        /* cache load failure, start discovery */
        bta_gattc_start_discover(p_clcb, NULL);
      } else {
        p_clcb->p_srcb->state = BTA_GATTC_SERV_IDLE;
        bta_gattc_reset_discover_st(p_clcb->p_srcb, GATT_SUCCESS);
      }
    } else { /* cache is building */
      p_clcb->state = BTA_GATTC_DISCOVER_ST;
    }
  } else {
    /* a pending service handle change indication */
    if (p_clcb->p_srcb->srvc_hdl_chg) {
      p_clcb->p_srcb->srvc_hdl_chg = false;

      /* set true to read database hash before service discovery */
      p_clcb->p_srcb->srvc_hdl_db_hash = true;

      /* start discovery */
      bta_gattc_sm_execute(p_clcb, BTA_GATTC_INT_DISCOVER_EVT, NULL);
    }
  }

这里我要blame一下这个cache的思想了

如果高层程序员需要cache 自然会自己写 如果底层有cache 则应该提供一些控制接口 诸如允许或者不允许 现在就是政令不通 底层有自己的想法 队伍不好带了的感觉 需要discoverServices()的时候 它可能过于自信 未必真去刷新 不需要的似乎它反而去刷新 (甚至没有等待connection update过程结束 导致了Different Transaction Collision)


设备如果是刚开始连接 反而会有可能使用缓存的数据

 if (p_clcb->p_srcb->gatt_database.IsEmpty() ...) {
    .....
      gatt::Database db = btm_sec_is_a_bonded_dev(p_clcb->bda)
                                  ? bta_gattc_cache_load(p_clcb->p_srcb->server_bda)
                                  : gatt::Database();

这也是为什么我第一次连接 需要discoverServices()的时候 经常没有实际发生 速度很快

但是之后HAL的db不空(因为我为了及时发现相机连接 使用了自动连接 状态是保留且持续的) 再次连接就需要看有没有发生service changed 如果发生了 会再次start discovery

else {
    /* a pending service handle change indication */
    if (p_clcb->p_srcb->srvc_hdl_chg) {
      p_clcb->p_srcb->srvc_hdl_chg = false;

      /* set true to read database hash before service discovery */
      p_clcb->p_srcb->srvc_hdl_db_hash = true;

      /* start discovery */
      bta_gattc_sm_execute(p_clcb, BTA_GATTC_INT_DISCOVER_EVT, NULL);
    }

令人遗憾的是 Sony相机是个保守主义者 它有事没事都经常会发service changed 大抵是认出这个连接者来过 并执意让它不使用缓存

上面的流程 除非改写并编译自己的手机固件 没有什么改变的余地 从上往下撕个口子一下真的很难 我没有找到什么方法透过service/JNI直达底层 但我并没有放弃 还是看看这个蛋有没有条缝可用让我叮一下的

/** Start a discovery on server */
void bta_gattc_start_discover(tBTA_GATTC_CLCB* p_clcb, const tBTA_GATTC_DATA* /* p_data */) {
   ....
 /* set all srcb related clcb into discovery ST */
    bta_gattc_set_discover_st(p_clcb->p_srcb);
  .....
    /* clear the service change mask */
    p_clcb->p_srcb->srvc_hdl_chg = false;
    p_clcb->p_srcb->update_count = 0;
  .....

可以看到 discovery并不是一个立即执行的过程 只是往状态机里设置了将要进入的状态 不过话又说回来 即使立即发出了查询包 也不影响我叮这颗蛋的姿势

我的做法很简单 设置一个2秒的超时 一般写蓝牙Characteristic不会花那么多时间 如果堵塞了 说明正在等待discovery结束 那么 我就disconnect 再马上connect 因为实际上 虽然discovery没成功完成 但是srvc_hdl_chg已经先被设为false了!!!! 这就是关键!!!!

private suspend fun gattWrite(characteristic: BluetoothGattCharacteristic, bytes: ByteArray) : Boolean? {
        return withTimeoutOrNull (if (faithMode) 2000L else 20000L) {
            while (gattStatus!=null) delay(100)
            suspendCancellableCoroutine<Boolean> { cont ->
                cont.invokeOnCancellation {
                    gattStatus=null
                    Log.e("MainService.gattWrite", "timeout, try to reconnect")
                    // this will break discovery progress in backend for faith mode, and speed up the response
                    gatt?.disconnect()
                    gatt?.connect()
                }
                gattStatus=cont
.....

实测有效 discovery进程被打断 虽然重连了一下 但是时间由原来的9秒变成了4秒 对于即时拍摄就容易接受了

如果在onConnectionStateChange()的时候 有手段知道底层会不会discovery 立即重连 这个时间有望减到2秒

如果新的android底层发现这个事务型漏洞并弥补上 我的程序可能会陷入不断re-connect 但是这也是我为什么专门设置一个可以开关的faith mode的原因 加速源于hack 算是真正的"黑科技了" 但是我想 至少到Android15这个服方法都奏效 而且Android开发人员不会来看这个贴子(那我得喷他一脸 一个不可控得cache是为了啥) 编程中也不可能什么都能以安全事务的形态进行 那程序会上升到不可思议的难度

评论
2
文号 / 944482

千古风流
名片专栏发私信
学术分 1
总主题 68 帖总回复 1007 楼拥有证书:进士 学者 机友
注册于 2020/01/22 18:44:43最后登录 2026/10/31 00:16:32
主体类型:个人
所属领域:无
认证方式:手机号
IP归属地:上海

个人简介

个人开源项目: m24h.github.io

笔记
{{note.content}}
{{n.user.username}}
{{fromNow(n.toc)}} {{n.status === noteStatus.disabled ? "已屏蔽" : ""}} {{n.status === noteStatus.unknown ? "正在审核" : ""}} {{n.status === noteStatus.deleted ? '已删除' : ''}}
  • 编辑
  • 删除
  • {{n.status === 'disabled' ? "解除屏蔽" : "屏蔽" }}
我也是有底线的