在 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
的实例,然后调用BluetoothLeScanner
的startScan
方法来启动扫描过程。这个过程允许应用发现附近的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参数。主要处理如下:
-
检查蓝牙适配器状态;
-
参数验证;
-
检查扫描客户端是否已存在;
-
获取IBluetoothGatt接口;
-
设置和过滤器组合验证:进行一系列检查以确保提供的settings和filters是有效的,并且符合当前硬件和系统的要求。
-
启动扫描:
-
如果提供了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扫描操作。
-
检查扫描器状态;
-
获取结果接收器;
-
注册扫描器:调用mBluetoothGatt.registerScanner方法尝试注册扫描器。这个方法需要当前对象(this)、工作源(mWorkSource)、归因源(mAttributionSource)和结果接收器(recv)作为参数。
-
等待结果:使用recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null)等待注册结果。这里设置了一个超时时间(通过getSyncTimeout()获取),如果超时或结果被中断,则会抛出异常。
-
等待回调:调用wait(REGISTRATION_CALLBACK_TIMEOUT_MILLIS)进一步等待可能的回调。这个等待是为了确保在继续之前,所有相关的回调都被处理完毕。
-
注册成功:如果mScannerId大于0,表示注册成功,将当前扫描回调添加到mLeScanClients映射中。
-
注册失败:如果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主要两个操作。
-
随机生成一个uuid,类似connectGatt,将作为唯一标识,用于在扫描client集合中作为key。
-
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