首页 > 编程语言 >Android BLE scan流程及源码分析

Android BLE scan流程及源码分析

时间:2024-09-07 16:22:57浏览次数:14  
标签:scanner scan app 扫描 callback 源码 BLE registerScanner uuid

在 Android 系统中,startScan 方法用于启动蓝牙扫描,本文针对较新的 Android 版本14进行BLE扫描源码分析。

一、Android Ble scan的一般流程概述

1. 权限和蓝牙适配器检查

  • 应用需要确保具有适当的蓝牙和位置权限(从 Android 6.0 开始,蓝牙扫描通常需要位置权限)。
  • 应用还需要检查蓝牙适配器是否可用并启用。

2. 获取 BluetoothLeScanner

  • 通过 BluetoothAdapter 获取 BluetoothLeScanner 实例。这是启动 BLE 扫描的主要接口。

3. 配置扫描设置和过滤器(可选)

  • 使用 ScanSettings 配置扫描模式(如低延迟、平衡、低功耗)、回调类型等。
  • 使用 ScanFilter(如果有的话)来过滤扫描结果,只接收感兴趣的蓝牙设备信息。

4. 启动扫描

  • 调用 BluetoothLeScanner 的 startScan 方法,传入之前配置的 ScanSettings 和 ScanFilter(如果有的话),以及一个 ScanCallback 来接收扫描结果。

5. 扫描回调处理

  • 在 ScanCallback 的 onScanResult 方法中处理每个扫描到的设备信息。
  • 也可以处理批量扫描结果(如果启用了批量扫描模式)和扫描失败的情况。

6. 停止扫描

  • 当不再需要扫描时,调用 BluetoothLeScanner 的 stopScan 方法停止扫描。这是释放资源并避免不必要的电量消耗的重要步骤。

二、源码流程

在Android中,对于BLE(Bluetooth Low Energy)扫描,通常会使用BluetoothAdapter来获取BluetoothLeScanner的实例,然后调用BluetoothLeScannerstartScan方法来启动扫描过程。这个过程允许应用发现附近的BLE设备。

1. frameworks层:startScan

使用BluetoothLeScanner,指定ScanCallback进行扫描。

/packages/modules/Bluetooth/framework/java/android/bluetooth/le/BluetoothLeScanner.java
public void startScan(
        List<ScanFilter> filters, ScanSettings settings, final ScanCallback callback) {
    startScan(filters, settings, null, callback, /* callbackIntent= */ null);
}

三参数版本startScan转到5参数版本。

/packages/modules/Bluetooth/framework/java/android/bluetooth/le/BluetoothLeScanner.java
private int startScan(
        List<ScanFilter> filters,
        ScanSettings settings,
        final WorkSource workSource,
        final ScanCallback callback,
        final PendingIntent callbackIntent) {
    //从3参数版本可看到,此处的workSource、callbackIntent。callback、callbackIntent不能同时是null。
    BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter); //确保蓝牙适配器已启用
    if (callback == null && callbackIntent == null) {
        throw new IllegalArgumentException("callback is null");
    }
    if (settings == null) {
        throw new IllegalArgumentException("settings is null");
    }
    synchronized (mLeScanClients) { //在mLeScanClients的同步块中执行,以确保线程安全。
        if (callback != null && mLeScanClients.containsKey(callback)) {//检查扫描客户端是否已存在
            return postCallbackErrorOrReturn(
                    callback, ScanCallback.SCAN_FAILED_ALREADY_STARTED);
        }
        IBluetoothGatt gatt = mBluetoothAdapter.getBluetoothGatt();
        if (gatt == null) {
            return postCallbackErrorOrReturn(callback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR);
        }
        if (!isSettingsConfigAllowedForScan(settings)) {
            return postCallbackErrorOrReturn(
                    callback, ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED);
        }
        if (!isHardwareResourcesAvailableForScan(settings)) {
            return postCallbackErrorOrReturn(
                    callback, ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES);
        }
        if (!isSettingsAndFilterComboAllowed(settings, filters)) {
            return postCallbackErrorOrReturn(
                    callback, ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED);
        }
        if (callback != null) {
         // 进这个入口
            BleScanCallbackWrapper wrapper =
                    new BleScanCallbackWrapper(gatt, filters, settings, workSource, callback);//封装回调逻辑
            wrapper.startRegistration(); //启动注册过程
        } else {
            try {
                final SynchronousResultReceiver recv = SynchronousResultReceiver.get();
                gatt.startScanForIntent(
                        callbackIntent, settings, filters, mAttributionSource, recv);
                recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
            } catch (TimeoutException | RemoteException e) {
                return ScanCallback.SCAN_FAILED_INTERNAL_ERROR;
            }
        }
    }
    return ScanCallback.NO_ERROR;
}

startScan方法的一个重载版本,它接受额外的WorkSource和PendingIntent参数。主要处理如下:

  1. 检查蓝牙适配器状态;

  2. 参数验证;

  3. 检查扫描客户端是否已存在;

  4. 获取IBluetoothGatt接口;

  5. 设置和过滤器组合验证:进行一系列检查以确保提供的settings和filters是有效的,并且符合当前硬件和系统的要求。

  6. 启动扫描:

  • 如果提供了callback,则创建一个BleScanCallbackWrapper来。

  • 如果提供了callbackIntent,则使用gatt.startScanForIntent方法启动扫描,并通过SynchronousResultReceiver同步等待扫描结果。

GATT是bt-jni中binder本地对象BluetoothGattBinder的代理对象。当“callback != null”时,创建BleScanCallbackWrapper,并调用startRegistration方法。

startRegistration

/packages/modules/Bluetooth/framework/java/android/bluetooth/le/BluetoothLeScanner.java
@SuppressWarnings("WaitNotInLoop") // TODO(b/314811467)
public void startRegistration() {
    synchronized (this) { //确保在同一时间只有一个线程可以执行此方法的代码块,为了防止并发问题,特别是当多个线程尝试同时注册扫描器时
        // Scan stopped.
        if (mScannerId == -1 || mScannerId == -2) return;
        try {
            final SynchronousResultReceiver recv = SynchronousResultReceiver.get(); //获取一个用于接收注册结果的对象。这个对象将用于等待注册操作的结果
            // 调用BluetoothGattBinder.registerScanner。
            mBluetoothGatt.registerScanner(this, mWorkSource, mAttributionSource, recv); //尝试注册扫描器
            recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
            wait(REGISTRATION_CALLBACK_TIMEOUT_MILLIS);
        } catch (TimeoutException | InterruptedException | RemoteException e) {
            Log.e(TAG, "application registration exception", e);
            postCallbackError(mScanCallback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR);
        }
        if (mScannerId > 0) {
            mLeScanClients.put(mScanCallback, this);
        } else {
            // Registration timed out or got exception, reset RscannerId to -1 so no
            // subsequent operations can proceed.
            if (mScannerId == 0) mScannerId = -1;

            // If scanning too frequently, don't report anything to the app.
            if (mScannerId == -2) return;

            postCallbackError(
                    mScanCallback,
                    ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED);
        }
    }
}

startRegistration方法用于尝试注册扫描器,以便可以开始BLE扫描操作。

  1. 检查扫描器状态;

  2. 获取结果接收器;

  3. 注册扫描器:调用mBluetoothGatt.registerScanner方法尝试注册扫描器。这个方法需要当前对象(this)、工作源(mWorkSource)、归因源(mAttributionSource)和结果接收器(recv)作为参数。

  4. 等待结果:使用recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null)等待注册结果。这里设置了一个超时时间(通过getSyncTimeout()获取),如果超时或结果被中断,则会抛出异常。

  5. 等待回调:调用wait(REGISTRATION_CALLBACK_TIMEOUT_MILLIS)进一步等待可能的回调。这个等待是为了确保在继续之前,所有相关的回调都被处理完毕。

  6. 注册成功:如果mScannerId大于0,表示注册成功,将当前扫描回调添加到mLeScanClients映射中。

  7. 注册失败:如果mScannerId为0(理论上不应发生,因为通常会有超时或异常),则将其重置为-1。如果mScannerId为-2(表示扫描太频繁),则不向应用报告任何内容并直接返回。对于其他失败情况,通过postCallbackError方法通知扫描回调接口注册失败。

总之,startRegistration的主要任务是调用mBluetoothGatt.registerScanner,即BluetoothGattBinder.registerScanner。至此进入bluetooth apk。

2. Bluetooth Servicec层:registerScanner

/packages/modules/Bluetooth/android/app/src/com/android/bluetooth/gatt/GattService.java
 static class BluetoothGattBinder extends IBluetoothGatt.Stub
    implements IProfileServiceBinder {
     private void registerScanner(IScannerCallback callback, WorkSource workSource,
        AttributionSource attributionSource) throws RemoteException {
        GattService service = getService();
        if (service == null) {
            return;
        }
        service.registerScanner(callback, workSource, attributionSource);
    }
    

    void registerScanner(IScannerCallback callback, WorkSource workSource,
        AttributionSource attributionSource) throws RemoteException {
    if (!Utils.checkScanPermissionForDataDelivery(
            this, attributionSource, "GattService registerScanner")) {
        return;
    }

    UUID uuid = UUID.randomUUID(); //为每个扫描器注册生成一个唯一的UUID,有助于跟踪和管理不同的扫描请求
    if (DBG) {
        Log.d(TAG, "registerScanner() - UUID=" + uuid);
    }

    enforceImpersonatationPermissionIfNeeded(workSource);

    AppScanStats app = mTransitionalScanHelper.getScannerMap()
                .getAppScanStatsByUid(Binder.getCallingUid());
    //检查调用应用是否过于频繁地发起扫描。如果扫描频率过高且应用没有特权权限,则拒绝注册并通知调用者失败
    if (app != null && app.isScanningTooFrequently() 
            && !Utils.checkCallerHasPrivilegedPermission(this)) {
        Log.e(TAG, "App '" + app.appName + "' is scanning too frequently");
        callback.onScannerRegistered(ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY, -1);
        return;
    }

    mTransitionalScanHelper
        .getScannerMap().add(uuid, workSource, callback, null, this);
    mScanManager.registerScanner(uuid);
}

BluetoothGattBinder.registerScanner调用service.registerScanner,sevice是GattService。GattService.registerScanner主要两个操作。

  1. 随机生成一个uuid,类似connectGatt,将作为唯一标识,用于在扫描client集合中作为key。

  2. mScanManager.registerScanner(uuid)。调用ScanManager.registerScanner,继续注册Scanner。

packages/modules/Bluetooth/android/app/src/com/android/bluetooth/le_scan/ScanManager.java
public void registerScanner(UUID uuid) {
    mScanNative.registerScanner(uuid.getLeastSignificantBits(),
            uuid.getMostSignificantBits());
}

ScanManager.registerScanner调用mScanNative.registerScannerNative,此至要转入c/c++代码。

3. JNI层:registerScannerNative

packages/modules/Bluetooth/android/app/jni/com_android_bluetooth_gatt.cpp
static void registerScannerNative(JNIEnv* /* env */, jobject /* object */,
                                  jlong app_uuid_lsb, jlong app_uuid_msb) {
  if (!sGattIf) return;

  Uuid uuid = from_java_uuid(app_uuid_msb, app_uuid_lsb);
  sGattIf->scanner->RegisterScanner(
      uuid, base::Bind(&btgattc_register_scanner_cb, uuid));
}

调用sGattIf->scanner->RegisterScanner方法,将转换后的UUID和一个回调函数(通过base::Bind绑定到btgattc_register_scanner_cb函数)传递给协议栈的扫描器组件进行注册。这里的回调函数用于处理扫描器注册成功或失败的情况。

sGattIf指向bt协议栈的btgattInterface。sGattIf->scanner指向bt协议栈的get_ble_scanner_instance()。

4. Bluedroid协议栈:btLeScannerInstance->RegisterScanner

a. btif_gatt_get_interface
/packages/modules/Bluetooth/system/btif/src/btif_gatt.cc
const btgatt_interface_t* btif_gatt_get_interface() {
  // TODO(jpawlowski) right now initializing advertiser field in static
  // structure cause explosion of dependencies. It must be initialized here
  // until those dependencies are properly abstracted for tests.
  btgattInterface.scanner = get_ble_scanner_instance();
  btgattInterface.advertiser = bluetooth::shim::get_ble_advertiser_instance();
  btgattInterface.distance_measurement_manager =
      bluetooth::shim::get_distance_measurement_instance();
  return &btgattInterface;
}
b. get_ble_scanner_instance
packages/modules/Bluetooth/system/btif/src/btif_ble_scanner.cc
BleScannerInterface* get_ble_scanner_instance() {
  return bluetooth::shim::get_ble_scanner_instance();
}
packages/modules/Bluetooth/system/main/shim/le_scanning_manager.cc
BleScannerInterfaceImpl* bt_le_scanner_instance = nullptr;

BleScannerInterface* bluetooth::shim::get_ble_scanner_instance() {
  if (bt_le_scanner_instance == nullptr) {
    bt_le_scanner_instance = new BleScannerInterfaceImpl();
  }
  return bt_le_scanner_instance;
}

bt_le_scanner_instance是个指向BleScannerInterface指针,BleScannerInterfaceImpl是一个class,不是c struct,scanner->RegisterScanner(...)是调用BleScannerInterfaceImpl成员函数RegisterScanner。

4.1. BleScannerInterfaceImpl::RegisterScanner

packages/modules/Bluetooth/system/main/shim/le_scanning_manager.cc
/** Registers a scanner with the stack */
void BleScannerInterfaceImpl::RegisterScanner(const bluetooth::Uuid& uuid,
                                              RegisterCallback) {
  LOG(INFO) << __func__ << " in shim layer";
  auto app_uuid = bluetooth::hci::Uuid::From128BitBE(uuid.To128BitBE());
  bluetooth::shim::GetScanning()->RegisterScanner(app_uuid);
}

RegisterScanner将任务委托给了另一个通过bluetooth::shim::GetScanning() 获取的对象,将转换后的UUID传递给BLE的扫描管理器进行注册。GetScanning返回一个指向管理BLE扫描的对象的指针。

/packages/modules/Bluetooth/system/gd/hci/le_scanning_manager.cc
void LeScanningManager::RegisterScanner(Uuid app_uuid) {
  CallOn(pimpl_.get(), &impl::register_scanner, app_uuid);
}

使用CallOn来调用impl成员(一个指向实现的智能指针)上的register_scanner 方法。

4.2. register_scanner

/packages/modules/Bluetooth/system/gd/hci/le_scanning_manager.cc
 void register_scanner(const Uuid app_uuid) {
    for (uint8_t i = 1; i <= kMaxAppNum; i++) { //检查重复注册
      if (scanners_[i].in_use && scanners_[i].app_uuid == app_uuid) {
        LOG_ERROR("Application already registered %s", app_uuid.ToString().c_str());
        scanning_callbacks_->OnScannerRegistered(app_uuid, 0x00, ScanningCallback::ScanningStatus::INTERNAL_ERROR);
        return;
      }
    }

    // valid value of scanner id : 1 ~ kMaxAppNum
    for (uint8_t i = 1; i <= kMaxAppNum; i++) { //寻找空闲扫描器ID
      if (!scanners_[i].in_use) {
        scanners_[i].app_uuid = app_uuid;
        scanners_[i].in_use = true;
        scanning_callbacks_->OnScannerRegistered(app_uuid, i, ScanningCallback::ScanningStatus::SUCCESS);
        return;
      }
    }

    LOG_ERROR("Unable to register scanner, max client reached:%d", kMaxAppNum);
    scanning_callbacks_->OnScannerRegistered(app_uuid, 0x00, ScanningCallback::ScanningStatus::NO_RESOURCES);
  }

注册一个新的BLE扫描器实例给指定的应用程序。通过检查scanners_数组来管理扫描器的注册状态。

5. 开始回调OnScannerRegistered

先看注册扫描回调的地方

/packages/modules/Bluetooth/system/main/shim/le_scanning_manager.cc
void BleScannerInterfaceImpl::RegisterCallbacks(ScanningCallbacks* callbacks) {
  LOG(INFO) << __func__ << " in shim layer";
  scanning_callbacks_ = callbacks;
}

5.1. OnScannerRegistered

/packages/modules/Bluetooth/system/gd/rust/topshim/gatt/gatt_ble_scanner_shim.cc
// ScanningCallbacks implementations

void BleScannerIntf::OnScannerRegistered(const bluetooth::Uuid app_uuid, uint8_t scannerId, uint8_t status) {
  rusty::gdscan_on_scanner_registered(reinterpret_cast<const signed char*>(&app_uuid), scannerId, status);
}

BleScannerIntf::OnScannerRegistered是一个回调函数实现,它是 BleScannerIntf接口的一部分,用于处理BLE扫描器注册的结果。这个函数接收三个参数:应用程序的UUID、扫描器ID和注册状态。然后,它将这些信息传递给Rust代码,通过调用rusty::gdscan_on_scanner_registered函数实现。

具体地,这个函数首先将 app_uuid的地址转换为 const signed char* 类型,因为Rust与C++之间的接口需要明确的数据类型转换,尤其是当涉及到指针和复杂数据结构时。在这里,reinterpret_cast被用来重新解释 app_uuid的地址,将其视为指向 signed char的常量指针。因为Rust代码期望以这种方式接收UUID数据,这是为了满足Rust与C++之间特定ABI(应用程序二进制接口)的要求。

然而,将 Uuid类型的地址直接转换为 signed char*可能不是处理UUID数据的最佳方式,因为这取决于 Uuid 类型的内部表示和Rust代码期望的格式。

在实际应用中,更常见的做法是将UUID转换为字节数组(例如,一个16字节的数组),然后将该数组传递给Rust代码。不过,这里直接使用 reinterpret_cast 可能是因为某些内部约定或性能考虑。

总的来说,这段代码展示了如何在C++和Rust之间传递复杂数据(如UUID)和简单数据类型(如整数),并通过回调函数机制实现跨语言的交互。

/packages/modules/Bluetooth/system/gd/rust/topshim/src/profiles/gatt.rs
cb_variant!(
    GDScannerCb,
    gdscan_on_scanner_registered -> GattScannerCallbacks::OnScannerRegistered,
    *const i8, u8, u8 -> GattStatus, {
        let _0 = unsafe { *(_0 as *const Uuid).clone() };
    }
);

5.2. BleScannerInterfaceImpl::OnScannerRegistered

/packages/modules/Bluetooth/system/main/shim/le_scanning_manager.cc
void BleScannerInterfaceImpl::OnScannerRegistered(
    const bluetooth::hci::Uuid app_uuid, bluetooth::hci::ScannerId scanner_id,
    ScanningStatus status) {
  auto uuid = bluetooth::Uuid::From128BitBE(app_uuid.To128BitBE());
  do_in_jni_thread(FROM_HERE,
                   base::BindOnce(&ScanningCallbacks::OnScannerRegistered,
                                  base::Unretained(scanning_cal

标签:scanner,scan,app,扫描,callback,源码,BLE,registerScanner,uuid
From: https://blog.csdn.net/weixin_37800531/article/details/141996997

相关文章

  • J.U.C Review - ThreadLocal原理源码分析
    文章目录一致性问题一致性问题简介解决一致性问题的常见方法ThreadLocal什么是ThreadLocalThreadLocal的线程模型ThreadLocal的工作原理使用场景ThreadLocal的基本API1.构造函数`ThreadLocal()`2.初始化方法`initialValue()`3.访问器`get()`和`set()`4.......
  • J.U.C Review - 计划任务ScheduledThreadPoolExecutor源码分析
    文章目录TimeVSScheduledThreadPoolExecutor小DemoScheduledThreadPoolExecutor类结构ScheduledThreadPoolExecutor主要方法介绍scheduleDelayed接口ScheduledFuture接口RunnableScheduledFuture接口ScheduledFutureTask类scheduleAtFixedRatescheduleWithFixedDelayd......
  • java+vue计算机毕设城市应急救援辅助系统【源码+开题+论文+程序】
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着城市化进程的加速,城市人口密集度不断提高,各类突发灾害与紧急事件的风险也随之增加。自然灾害如地震、洪水,以及人为事故如火灾、交通事故等,均对城......
  • java+vue计算机毕设宠物购物系统【源码+开题+论文+程序】
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景在当今社会,随着人们生活水平的提高和情感需求的多样化,宠物已成为许多家庭不可或缺的重要成员。宠物的养护与陪伴不仅丰富了人们的情感世界,也带动了宠......
  • java+vue计算机毕设宠物交易平台【源码+开题+论文+程序】
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着社会经济的快速发展和人们生活水平的提高,宠物已成为许多家庭不可或缺的重要成员。宠物市场的繁荣不仅体现在宠物数量的激增上,更在于宠物相关服务......
  • java+vue计算机毕设宠物领养系统【源码+开题+论文+程序】
    本系统(程序+源码)带文档lw万字以上文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着城市化进程的加速,人类生活与宠物之间的联系日益紧密,但同时也带来了流浪宠物数量激增的社会问题。流浪宠物不仅面临着生存的挑战,还可能成为疾病传......
  • 基于JAVA的数计学院学生综合素质评价系统设计与实现,LW、源码+部署讲解
    摘   要传统信息的管理大部分依赖于管理人员的手工登记与管理,然而,随着近些年信息技术的迅猛发展,让许多比较老套的信息管理模式进行了更新迭代,个人综合素质信息因为其管理内容繁杂,管理数量繁多导致手工进行处理不能满足广大用户的需求,因此就应运而生出相应的数计学院学生......
  • 基于Spring Boot的付费问答管理系统设计与实现,源码、LW+部署讲解
    摘  要在如今社会上,关于信息上面的处理,没有任何一个企业或者个人会忽视,如何让信息急速传递,并且归档储存查询,采用之前的纸张记录模式已经不符合当前使用要求了。所以,对问答信息管理的提升,也为了对问答信息进行更好的维护,付费问答系统的出现就变得水到渠成不可缺少。通过对付......
  • 基于Spring Boot的在线骑行网站设计与实现,LW+源码+部署讲解
    摘 要传统办法管理信息首先需要花费的时间比较多,其次数据出错率比较高,而且对错误的数据进行更改也比较困难,最后,检索数据费事费力。因此,在计算机上安装在线骑行网站软件来发挥其高效地信息处理的作用,可以规范信息管理流程,让管理工作可以系统化和程序化,同时,在线骑行网站的有效......
  • To enable inbound and outbound TCP traffic specifically for port 18917 using `uf
    ToenableinboundandoutboundTCPtrafficspecificallyforport18917usingufwonDebian12,followthesesteps:AllowinboundTCPtrafficonport18917:sudoufwallow18917/tcpAllowoutboundTCPtrafficonport18917(ifyouhaverestrictedoutgoingtra......