当前位置: 首页 > news >正文

【android bluetooth 框架分析 02】【Module详解 6】【StorageModule 模块介绍】

1. 背景

我们在 gd_shim_module 介绍章节中,看到 我们将 StorageModule 模块加入到了 modules 中。

// system/main/shim/stack.cc
modules.add<storage::StorageModule>();

在 ModuleRegistry::Start 函数中我们对 加入的所有 module 挨个初始化。
而在该函数中启动一个 module 都要执行那下面几步:

  1. 创建module 实体

    • Module* instance = module->ctor_();
  2. 将 当前 module 实体和 gd_stack_thread 线程绑定

    • set_registry_and_handler(instance, thread);
  3. 启动当前模块所依赖的所有子模块。

    • instance->ListDependencies(&instance->dependencies_);
    • Start(&instance->dependencies_, thread);
  4. 最后调用自己的 Start() 函数

    • instance->Start();
  5. 将module 实体加入到 started_modules_

    • started_modules_[module] = instance;

本篇文章就拿 storage::StorageModule 模块来具体分析一下 他的启动。

2. modules.add

我们先来看一下 在调用 modules.add 时, 到底做了那些事情。

modules.add<storage::StorageModule>();
class ModuleList {friend Module;friend ModuleRegistry;public:template <class T>void add() {list_.push_back(&T::Factory); // add 时 添加的是 StorageModule::Factory}private:std::vector<const ModuleFactory*> list_;
};
  • 从代码中不难发现, 我们是将 StorageModule::Factory 加入到 list_ 中的。
// system/gd/storage/storage_module.cc
const ModuleFactory StorageModule::Factory = ModuleFactory([]() {return new StorageModule(os::ParameterProvider::ConfigFilePath(), kDefaultConfigSaveDelay, kDefaultTempDeviceCapacity, false, false);
});// 这里在创建 ModuleFactory 对象时, 传入了一个 函数, 这个函数 去 new StorageModule 对象
  • 这里在创建 ModuleFactory 对象时, 传入了一个 函数,但是并没有去调用这个函数。
    • 这个函数的目的是 去 new StorageModule 对象
class ModuleFactory {friend ModuleRegistry;friend FuzzTestModuleRegistry;public:ModuleFactory(std::function<Module*()> ctor);private:std::function<Module*()> ctor_;
};//  system/gd/module.cc
ModuleFactory::ModuleFactory(std::function<Module*()> ctor) : ctor_(ctor) {
}
  • 在创建 ModuleFactory 对象时, 也仅仅是将 如下的函数赋值给了 ModuleFactory::ctor_ 函数指针。
[]() {return new StorageModule(os::ParameterProvider::ConfigFilePath(), kDefaultConfigSaveDelay, kDefaultTempDeviceCapacity, false, false);
}

3. 模块具体启动流程

1. 创建module 实体

  1. 创建module 实体
    • Module* instance = module->ctor_();
[]() {return new StorageModule(os::ParameterProvider::ConfigFilePath(), kDefaultConfigSaveDelay, kDefaultTempDeviceCapacity, false, false);
}// 参数讲解// 传入 配置文件加载的路径
std::string ParameterProvider::ConfigFilePath() {{std::lock_guard<std::mutex> lock(parameter_mutex);if (!config_file_path.empty()) {return config_file_path;}}return is_default_bluetooth() ?"/data/misc/bluedroid/bt_config.conf" :"/data/misc/bluedroid/new/bt_config.conf";
}// Save config whenever there is a change, but delay it by this value so that burst config change won't overwhelm disk
/*1. 是配置保存的延迟时间,默认是 3000 毫秒(即 3 秒)2. 配置每次变化都要保存,但通过设置一个延迟时间,避免连续多次修改频繁写盘,造成磁盘负担。3. 例如用户在 1 秒内修改了 10 个配置,系统只会在最后一次修改后延迟 3 秒进行一次集中保存
*/
static const std::chrono::milliseconds kDefaultConfigSaveDelay = std::chrono::milliseconds(3000);/*代表:临时设备或临时数据的最大容纳数量,比如 10000 个设备或条目目的: 控制内存中“临时设备列表”或“缓存队列”的最大容量,防止内存溢出
*/
static const size_t kDefaultTempDeviceCapacity = 10000;
  • 这里就会去实际触发 该函数,去创建 StorageModule 对象。
  • 也就是说,modules.addstorage::StorageModule();模块对应的 实体其实是 StorageModule 对象。
class StorageModule : public bluetooth::Module {}
  • StorageModule 又继承 Module

如下是 StorageModule 的构造函数

// system/gd/storage/storage_module.cc
StorageModule::StorageModule(std::string config_file_path,std::chrono::milliseconds config_save_delay,size_t temp_devices_capacity,bool is_restricted_mode,bool is_single_user_mode): config_file_path_(std::move(config_file_path)),config_save_delay_(config_save_delay),temp_devices_capacity_(temp_devices_capacity),is_restricted_mode_(is_restricted_mode),is_single_user_mode_(is_single_user_mode) {// e.g. "/data/misc/bluedroid/bt_config.conf" to "/data/misc/bluedroid/bt_config.bak"config_backup_path_ = config_file_path_.substr(0, config_file_path_.find_last_of('.')) + ".bak";ASSERT_LOG(config_save_delay > kMinConfigSaveDelay/*这个事 20ms*/,"Config save delay of %lld ms is not enough, must be at least %lld ms to avoid overwhelming the disk",config_save_delay_.count(),kMinConfigSaveDelay.count());
};// The config saving delay must be bigger than this value to avoid overwhelming the disk
// 表示最小允许的配置保存延迟时间:20 毫秒。
// 写入配置文件到磁盘至少需要 10~20ms(取决于是否创建备份),因此不应在更短间隔内频繁触发写盘操作
// 目的:给开发者设置一个“下限”参考,避免误把保存延迟设置得太小
static const std::chrono::milliseconds kMinConfigSaveDelay = std::chrono::milliseconds(20);

这三行常量控制了 配置文件的保存机制,防止因配置频繁变更导致磁盘 I/O 过载:

常量名含义默认值
kDefaultTempDeviceCapacity临时设备的最大缓存数量10000
kDefaultConfigSaveDelay配置变更后的写盘延迟,避免频繁写盘3000 ms
kMinConfigSaveDelay配置保存的最小延迟阈值,低于此值会造成磁盘压力20 ms

2. 将 当前 module 实体和 gd_stack_thread 线程绑定

  1. 将 当前 module 实体和 gd_stack_thread 线程绑定
    • set_registry_and_handler(instance, thread);
void ModuleRegistry::set_registry_and_handler(Module* instance, Thread* thread) const {instance->registry_ = this;instance->handler_ = new Handler(thread);
}
  • 将我们的 gd_stack_thread 对应的 handle 直接保存在 Module->handler_ 中。
Handler* Module::GetHandler() const {ASSERT_LOG(handler_ != nullptr, "Can't get handler when it's not started");return handler_;
}
  • 通过 Module::GetHandler() 来获取当前 handler_

3.启动当前模块所依赖的所有子模块

  1. 启动当前模块所依赖的所有子模块。
    • instance->ListDependencies(&instance->dependencies_);
    • Start(&instance->dependencies_, thread);
// system/gd/storage/storage_module.ccvoid StorageModule::ListDependencies(ModuleList* list) const {list->add<metrics::CounterMetrics>();
}
  • 这里可以看到 StorageModule 模块依赖 CounterMetrics 模块。

  • 此时就会去加载 CounterMetrics 模块。

4. 最后调用自己的 Start() 函数

  1. 最后调用自己的 Start() 函数
    • instance->Start();

1. StorageModule::Start

// system/gd/storage/storage_module.ccstatic const std::string kFactoryResetProperty = "persist.bluetooth.factoryreset";void StorageModule::Start() {// 确保整个 Start() 执行期间持有互斥锁,避免多线程同时修改内部状态std::lock_guard<std::recursive_mutex> lock(mutex_);std::string file_source;/*如果系统属性中标记了“恢复出厂设置”1. 删除主配置文件和备份配置文件2. 然后重置该系统属性为 "false"3. 这是在执行软重置或恢复设置时常见的模式*/if (os::GetSystemProperty(kFactoryResetProperty) == "true") {LOG_INFO("%s is true, delete config files", kFactoryResetProperty.c_str());LegacyConfigFile::FromPath(config_file_path_).Delete();LegacyConfigFile::FromPath(config_backup_path_).Delete();os::SetSystemProperty(kFactoryResetProperty, "false");}/*校验配置文件有效性(主 + 备份)1. 调用 is_config_checksum_pass() 检查主文件和备份文件的校验和是否正确2. 若无效,则删除对应配置文件,防止加载损坏数据*/if (!is_config_checksum_pass(kConfigFileComparePass)) {LegacyConfigFile::FromPath(config_file_path_).Delete();}if (!is_config_checksum_pass(kConfigBackupComparePass)) {LegacyConfigFile::FromPath(config_backup_path_).Delete();}/*加载配置文件(主优先,备份次之)1. 尝试从主路径读取配置。2. 如果读取失败,或缺少关键的 "Adapter" 配置段,则尝试读取备份文件3. 并记录文件来源 "Backup"*/auto config = LegacyConfigFile::FromPath(config_file_path_).Read(temp_devices_capacity_);if (!config || !config->HasSection(kAdapterSection)) {LOG_WARN("cannot load config at %s, using backup at %s.", config_file_path_.c_str(), config_backup_path_.c_str());config = LegacyConfigFile::FromPath(config_backup_path_).Read(temp_devices_capacity_);file_source = "Backup";}/*加载失败:创建空配置1. 如果备份也不可用,同样缺少核心段落,则说明配置彻底损坏2. 创建一个新的空配置对象,参数用于初始化默认设备列表*/if (!config || !config->HasSection(kAdapterSection)) {LOG_WARN("cannot load backup config at %s; creating new empty ones", config_backup_path_.c_str());config.emplace(temp_devices_capacity_, Device::kLinkKeyProperties);file_source = "Empty";}/*记录配置来源(Info 段)1. 在配置中标记是从哪个源(主/备份/空)加载的2. 有利于后续调试或问题分析。*/if (!file_source.empty()) {config->SetProperty(kInfoSection, kFileSourceProperty, std::move(file_source)); // 这里不是设置到android的系统属性里,而是保存在 配置文件中。}/*清除“访客模式”下遗留数据1. 如果当前不是“受限模式”(如访客模式),则删除 Restricted 属性相关的 section。2. 防止访客数据泄露*/// Cleanup temporary pairings if we have left guest modeif (!is_restricted_mode_) {config->RemoveSectionWithProperty("Restricted");}/*初始化配置创建时间(如不存在)1. 读取配置信息段中的 TimeCreated2. 如果不存在,就使用当前系统时间格式化一个时间字符串写入3. 用于记录配置的首次生成时间*/// Read or set config file creation timestampauto time_str = config->GetProperty(kInfoSection, kTimeCreatedProperty);if (!time_str) {std::stringstream ss;auto now = std::chrono::system_clock::now();auto now_time_t = std::chrono::system_clock::to_time_t(now);ss << std::put_time(std::localtime(&now_time_t), kTimeCreatedFormat.c_str());config->SetProperty(kInfoSection, kTimeCreatedProperty, ss.str());}/*修正设备类型配置中的不一致项1. 自动扫描并修复配置中设备类型的异常数据2. 可能针对历史版本残留配置进行兼容性修复*/config->FixDeviceTypeInconsistencies();/*注册配置变更回调1. 当配置发生变更时,自动调用 SaveDelayed()2. SaveDelayed() 是延迟保存机制(之前提到默认 3 秒后),避免频繁写盘*/config->SetPersistentConfigChangedCallback([this] { this->CallOn(this, &StorageModule::SaveDelayed); });/*初始化核心实现(Impl)1. 创建 impl 类实例(Pimpl Idiom:接口/实现分离)2. 把读取好的配置传入其中,以及临时设备容量限制3. GetHandler() 提供事件处理线程句柄。*/// TODO (b/158035889) Migrate metrics module to GDpimpl_ = std::make_unique<impl>(GetHandler(), std::move(config.value()), temp_devices_capacity_);SaveDelayed();// 再次调用延迟保存,确保初始化之后的状态持久化/*尝试加解密配置密钥1. 如果系统启用了 Bluetooth Keystore 接口(密钥加解密),则尝试转换加密格式2. 用于配置文件中的 LinkKey 或密钥材料的迁移*/if (bluetooth::os::ParameterProvider::GetBtKeystoreInterface() != nullptr) {bluetooth::os::ParameterProvider::GetBtKeystoreInterface()->ConvertEncryptOrDecryptKeyIfNeeded();}
}

2. bt_config.conf

记录分享一个真实的 bt_config.conf

// adb pull /data/misc/bluedroid/bt_config.conf[Info]
FileSource = Empty
TimeCreated = 2025-04-18 10:33:13[Adapter]
Address = 22:22:96:de:b1:39
LE_LOCAL_KEY_IRK = 7c39750923b27df313f988bbfee74bb8
LE_LOCAL_KEY_IR = ad109bf638e98981aa439c9f0570bfe9
LE_LOCAL_KEY_DHK = d48e846c3fb280a572fbec8e096a137d
LE_LOCAL_KEY_ER = 74a3b80a8145351e9b0943600e8a68e3
ScanMode = 0
DiscoveryTimeout = 120
Name = cheji_8295[Metrics]
Salt256Bit = 94bb8f142ccfe6265a4b3ab38d447fcaexxxxxxxf7c6aa4043d7f[00:1b:dc:f4:b1:83]
Timestamp = 1745558511
DevClass = 1049092
DevType = 3
AddrType = 0
Name = PTS-PBAP-B183
LinkKeyType = 8
PinLength = 0
LinkKey = a88655950fc0ad9xxxxxxx31e96bde
MetricsId = 21
Service = 0000110a-0000-1000-8000-00805f9b34fb
AvdtpVersion = 0001[70:8f:47:91:b0:62]
Name = cbx
DevClass = 5898764
DevType = 3
AddrType = 0
SdpDiManufacturer = 29
SdpDiModel = 4608
SdpDiHardwareVersion = 5174
SdpDiVendorIdSource = 1
Service = 00001105-0000-1000-8000-00805f9b34fb 0000110a-0000-1000-8000-00805f9b34fb 0000110c-0000-1000-8000-00805f9b34fb 0000110e-0000-1000-8000-00805f9b34fb 00001112-0000-1000-8000-00805f9b34fb 00001115-0000-1000-8000-00805f9b34fb 00001116-0000-1000-8000-00805f9b34fb 0000111f-0000-1000-8000-00805f9b34fb 0000112d-0000-1000-8000-00805f9b34fb 0000112f-0000-1000-8000-00805f9b34fb 00001132-0000-1000-8000-00805f9b34fb 00001200-0000-1000-8000-00805f9b34fb 2c042b0a-7f57-4c0a-afcf-1762af70257c 8fa9c715-bd1f-596c-a1b0-13162b15c892 9fed64fd-e91a-499e-88dd-73dfe023feed
Timestamp = 1745510776
LinkKeyType = 8
PinLength = 0
LinkKey = 6453dc8978aexxxxxxx0b4e7b90
MetricsId = 25
AvdtpVersion = 0301

3.StorageModule::impl

  /*初始化核心实现(Impl)1. 创建 impl 类实例(Pimpl Idiom:接口/实现分离)2. 把读取好的配置传入其中,以及临时设备容量限制3. GetHandler() 提供事件处理线程句柄。*/pimpl_ = std::make_unique<impl>(GetHandler(), std::move(config.value()), temp_devices_capacity_);

这里我们看一下 StorageModule::impl 构造做了那些事情。

struct StorageModule::impl {explicit impl(Handler* handler, ConfigCache cache, size_t in_memory_cache_size_limit): config_save_alarm_(handler), cache_(std::move(cache)), memory_only_cache_(in_memory_cache_size_limit, {}) {}Alarm config_save_alarm_;ConfigCache cache_;ConfigCache memory_only_cache_;bool has_pending_config_save_ = false;
};
  • 整个 StorageModule::impl 类,也没有任何 操作方法。 只是将 传入的参数,保存了一下。

5.将module 实体加入到 started_modules_

  1. 将module 实体加入到 started_modules_
    • started_modules_[module] = instance;

4. StorageModule 对外功能

当我们执行完 上述 5 个步骤 后,此时其他模块就可以 正常访问 StorageModule 模块的功能了。 我们来看一下, StorageModule 模块对外都提供了那些接口,供其他模块使用。

1. GetDeviceByLegacyKey

  • system/gd/storage/storage_module.h
// Methods to access the storage layer via Device abstraction// - Devices will be lazily created when methods below are called. Hence, no std::optional<> nor nullptr is used in//   the return type. User of the API can use the Device object's API to find out if the device has existed before// - Devices with no config values will not be saved to config cache// - Devices that are not paired will also be discarded when stack shutdown// Concept://// BR/EDR Address://  -> Public static address only, begin with 3 byte IEEE assigned OUI number//// BLE Addresses//  -> Public Address: begin with IEEE assigned OUI number//     -> Static: static public address do not change//     -> Private/Variable: We haven't seen private/variable public address yet//  -> Random Address: randomly generated, does not begin with IEEE assigned OUI number//     -> Static: static random address do not change//     -> Private/Variable: private random address changes once so often//        -> Resolvable: this address can be resolved into a static address using identity resolving key (IRK)//        -> Non-resolvable: this address is for temporary use only, do not save this address//// MAC addresses are six bytes only and hence are only regionally unique// Get a device object using the |legacy_key_address|. In legacy config, each device's config is stored in a config// section keyed by a single MAC address. For BR/EDR device, this is straightforward as a BR/EDR device has only a// single public static MAC address. However, for LE devices using private addresses, we only learn its real static// address after pairing. Since we still need to store that device's information prior to pairing, we use the// first-seen address of that device, no matter random private or static public, as a "key" to store that device's// config. This method gives you a device object using this legacy key. If the key does not exist, the device will// be lazily created in the configDevice GetDeviceByLegacyKey(hci::Address legacy_key_address);Device StorageModule::GetDeviceByLegacyKey(hci::Address legacy_key_address) {std::lock_guard<std::recursive_mutex> lock(mutex_);return Device(&pimpl_->cache_,&pimpl_->memory_only_cache_,std::move(legacy_key_address),Device::ConfigKeyAddressType::LEGACY_KEY_ADDRESS);
}

单纯的看 GetDeviceByLegacyKey 函数的实现很简单。 就是创建并返回了一个 蓝牙设备对象。但仔细对 注释就可以发现很多隐含的信息。

1. 注释解释

GetDeviceByLegacyKey 功能介绍:

  1. 通过 Device 抽象层访问存储层的方法

    • 设备对象会在 GetDeviceByLegacyKey 方法被调用时“延迟创建”。因此,返回类型中不使用 std::optional<> 或 nullptr。 使用该 API 的用户可以通过 Device 对象本身的 API 判断该设备是否已经存在(即是否真实持久化)
    • 没有配置值的设备不会被保存到配置缓存中(即不会持久化)
    • 未配对的设备在蓝牙协议栈关闭时也会被丢弃(不会写入配置文件)
  2. 概念部分:

    1. BR/EDR 地址:
      • 仅包含“公共静态地址”,以 IEEE 分配的 3 字节 OUI(组织唯一标识符)开头
    2. BLE 地址:
      • 公共地址:同样以 IEEE 分配的 OUI 开头
        • 静态型:静态公共地址不会改变
        • 私有/可变型:目前尚未见到此类公共地址
      • 随机地址:由设备随机生成,不以 IEEE OUI 开头
        • 静态型:静态随机地址不会改变
        • 私有/可变型:私有随机地址会周期性变化
          • 可解析类型:该地址可通过 IRK(身份解析密钥)还原为固定地址
          • 不可解析类型:该地址仅临时使用,不应保存(即不持久化)
  3. MAC 地址仅有 6 字节,因此它们只在区域内具有唯一性(不能全球唯一)

  4. 使用 legacy_key_address 获取设备对象:

  5. 使用 legacy_key_address 获取设备对象。在传统配置中,每个设备的配置信息存储在以 MAC 地址为键的配置段中。

  6. 对于 BR/EDR 设备来说,这很简单,因为它只有一个公共的、静态的 MAC 地址。

  7. 但对于使用私有地址的 LE 设备,我们只有在配对后才能知道它真正的静态地址

  8. 由于在配对之前我们仍然需要保存设备的信息,因此无论是随机私有地址还是静态公共地址,都会使用首次发现的地址作为“键”来存储该设备的配置信息。

  9. 此方法会根据该 legacy key 返回一个设备对象。如果该 key 不存在,则会延迟创建该设备(不会立即写入配置)。

这段注释主要说明了:

  1. 设备的创建是“延迟”的:只有在真正需要操作设备时才会生成 Device 对象。
  2. 返回值不是 optional 或指针:直接返回对象,由调用者通过 API 判断其是否存在于配置中。
  3. 未配对或无配置的设备不会被持久化:配置文件只保存有效信息。
  4. 设备地址有多种类型,尤其是 BLE 设备使用的随机地址在配对前无法确认其真实身份,因此使用“首次发现地址”作为配置键。
  5. 函数提供的是 legacy key(旧地址)所对应的设备对象,符合传统配置系统的逻辑。

2. 地址分类

类型说明是否可保存
BR/EDR Public Static固定 MAC,3 字节 OUI 开头✅ 可以保存
BLE Public Static固定 MAC,3 字节 OUI 开头✅ 可以保存
BLE Random Static伪随机但固定✅ 可以保存
BLE Random Private Resolvable会变,但可通过 IRK 解析⚠️ 后续可替换
BLE Random Private Non-resolvable仅临时使用❌ 不保存

在“未配对”情况下,我们只知道设备的某个临时地址,所以只能用这个地址作为“临时配置 key”,即 legacy key。


3. 为什么用 “legacy key address”?

  • 在 Bluetooth BR/EDR(传统蓝牙)中,每个设备有唯一的 静态公共 MAC 地址,所以可以直接作为 key。
  • 在 BLE(低功耗蓝牙)中,设备可能使用 随机地址(尤其是未配对前),这些地址不是长期固定的。
  • 所以:我们在初次发现设备时,用“当时第一次见到的地址”作为 legacy_key_address 存储其配置。

4. 简单理解 GetDeviceByLegacyKey

功能:根据传入的 legacy_key_address(通常是首次发现时的地址)获取对应的 Device 对象,
如果不存在则延迟构造一个 Device(暂不落盘),用于配置读取或写入。
用途:支持对“尚未配对”但已被发现的设备进行设置或读取操作。
线程安全:内部加锁,防止多线程状态竞争。
设计理念:

  • 延迟初始化设备对象;
  • 设备对象接口可判断自己是否已经存在于配置中;
  • 非配对设备或无配置数据的设备在堆栈关闭时将被丢弃。

2. GetDeviceByClassicMacAddress

  • system/gd/storage/storage_module.h
// A classic (BR/EDR) or dual mode device can be uniquely located by its classic (BR/EDR) MAC addressDevice GetDeviceByClassicMacAddress(hci::Address classic_address);Device StorageModule::GetDeviceByClassicMacAddress(hci::Address classic_address) {std::lock_guard<std::recursive_mutex> lock(mutex_);return Device(&pimpl_->cache_,&pimpl_->memory_only_cache_,std::move(classic_address),Device::ConfigKeyAddressType::CLASSIC_ADDRESS);
}

1. 作用:

该函数的作用是:

根据一个经典蓝牙(BR/EDR)设备的 MAC 地址,获取对应的 Device 对象。

这适用于以下情况:

  • 设备是经典蓝牙设备(BR/EDR only),或
  • 设备是双模设备(同时支持 BR/EDR 和 BLE)
  • 这些设备拥有一个 唯一且固定的公共 MAC 地址

2. 内部逻辑分析:

  • 函数持有一个递归互斥锁 mutex_,用于线程安全。
  • 调用构造函数创建 Device 对象时,传入的地址类型是:
    Device::ConfigKeyAddressType::CLASSIC_ADDRESS

这表示我们确定该地址是经典蓝牙设备的公共地址,可直接作为配置键。


3. 与 GetDeviceByLegacyKey 的区别

比较项GetDeviceByClassicMacAddressGetDeviceByLegacyKey
适用设备类型仅适用于经典蓝牙(BR/EDR)或双模设备主要用于 BLE 设备,尤其是尚未配对、地址可能不固定的设备
地址含义地址是设备唯一的、固定的公共 BR/EDR MAC 地址地址可能是首次看到的 BLE 随机地址,用作“临时键”
稳定性地址稳定且可以长期作为唯一标识地址可能会变化(如 BLE 私有地址),真实身份需配对后才能确认
地址类型标识Device::ConfigKeyAddressType::CLASSIC_ADDRESSDevice::ConfigKeyAddressType::LEGACY_KEY_ADDRESS
配置键作用直接用于配置文件中的设备段标识用作“临时配置键”,直到设备配对后可替换为真实身份地址
典型使用场景经典蓝牙耳机、扬声器、手机等设备BLE 手环、传感器、Beacon 等尚未配对的设备

4. 案例

// bt_config.conf[70:8f:47:91:b0:62]      ← 蓝牙远端设备的 MAC 地址(作为唯一键)
Name = cbx              ← 设备名称(可来自蓝牙广播或 SDP 查询)
DevClass = 5898764      ← 设备类别(Device Class)
DevType = 3             ← 设备类型(BR/EDR/LE/Dual)
AddrType = 0            ← 地址类型(Public/Random)
SdpDiManufacturer = 29  ← 设备 SDP 中的制造商 ID
SdpDiModel = 4608       ← SDP 中的设备型号
SdpDiHardwareVersion=...← SDP 中的硬件版本号
SdpDiVendorIdSource = 1 ← Vendor ID 来源类型
Service = ...           ← SDP 中支持的服务 UUID 列表
Timestamp = 1745510776  ← 上次交互时间戳(Unix 时间)
LinkKeyType = 8         ← Link Key 类型(身份验证方式)
PinLength = 0           ← PIN 长度(传统配对时使用)
LinkKey = ...           ← 用于身份验证的配对密钥
MetricsId = 25          ← 用于度量标识的 ID
AvdtpVersion = 0301     ← 远端设备支持的 AVDTP 协议版本
// 当我们调用如下函数, 就可以从 我们的配置文件中,生成 70:8f:47:91:b0:62 设备对应的 Device.
GetDeviceByClassicMacAddress("70:8f:47:91:b0:62")
字段名说明
[70:8f:47:91:b0:62]蓝牙设备的 MAC 地址,用于唯一标识配对设备。
Name = cbx蓝牙设备名称,可能来自广播包或 SDP。
DevClass = 5898764蓝牙设备类别(Device Class),十进制形式,拆成 bit 字段可表示主要/次要设备类别和服务类别。
DevType = 3设备类型:1 = BR/EDR 2 = LE 3 = Dual(支持 BR/EDR + LE)
AddrType = 0地址类型:0 = Public Address 1 = Random Address
SdpDiManufacturer = 29SDP 的 Device Identification Profile 中的 manufacturer ID(制造商编号,29 代表 Samsung)
SdpDiModel = 4608SDP 中的 Model ID,厂商自定义字段,表明型号
SdpDiHardwareVersion = 5174SDP 中的硬件版本号(例如可能表示 HW rev 5.1.74)
SdpDiVendorIdSource = 1Vendor ID 来源类型:1 = Bluetooth SIG 分配的 Vendor ID2 = USB-IF 分配的 Vendor ID
Service = UUID 列表SDP 查询发现的支持服务 UUID(以空格分隔):例如 0000110a... 是 A2DP Sink具体服务见下表
Timestamp = 1745510776最后一次交互时间戳(Unix 时间,单位:秒)
LinkKeyType = 8Link Key 类型,表示加密方式:4 = Unauthenticated 5 = Authenticated 6 = Changed combination 8 = Secure connections
PinLength = 0用于旧配对方式的 PIN 长度,常为 0 表示未使用。
LinkKey = …BR/EDR 模式下配对生成的密钥,用于身份验证。是一个 16 字节的十六进制字符串。
MetricsId = 25Android Bluetooth Metrics 用于标识配对设备来源的 ID,方便统计用途
AvdtpVersion = 0301表示远端支持的 AVDTP 版本号,例如:0301 = 版本 1.3.1(用于 A2DP)
UUID服务功能
00001105-…OBEX Object Push发送名片、图片等文件
0000110a-…A2DP Sink接收音频(如车机、音箱)
0000110c-…A/V Remote Control Target接收遥控命令
0000110e-…Hands-Free免提通话协议
00001112-…Headset Audio Gateway耳机音频通道
00001115-…PANU个人区域网用户
0000111f-…Handover用于 NFC 触发蓝牙连接
0000112d/2fPBAP电话簿访问协议
00001132-…Message Access Profile短信访问协议
00001200-…Generic Access Profile (GAP)基本服务发现
自定义 UUID比如 2c042b0a-...8fa9c715-...厂商自定义服务 UUID,用于特定应用协议

5. 总结

  • GetDeviceByClassicMacAddress 是用于经典蓝牙设备的标准接口,因为这类设备的地址具有唯一性与稳定性,可以直接作为配置的主键使用。

  • GetDeviceByLegacyKey 是为了解决 BLE 设备使用随机地址的配对前场景,在无法获取真实身份地址前,临时使用首次看到的地址作为键来保存配置。

前者是正式识别用地址,后者是临时过渡用地址。

3. GetDeviceByLeIdentityAddress

  • system/gd/storage/storage_module.h
// A LE or dual mode device can be uniquely located by its identity address that is either://   -> Public static address//   -> Random static address// If remote device uses LE random private resolvable address, user of this API must resolve its identity address// before calling this method to get the device object//// Note: A dual mode device's identity address is normally the same as its BR/EDR address, but they can also be// different. Hence, please don't make such assumption and don't use GetDeviceByBrEdrMacAddress() interchangeablyDevice GetDeviceByLeIdentityAddress(hci::Address le_identity_address);Device StorageModule::GetDeviceByLeIdentityAddress(hci::Address le_identity_address) {std::lock_guard<std::recursive_mutex> lock(mutex_);return Device(&pimpl_->cache_,&pimpl_->memory_only_cache_,std::move(le_identity_address),Device::ConfigKeyAddressType::LE_IDENTITY_ADDRESS);
}

1. 注释介绍

// A LE or dual mode device can be uniquely located by its identity address that is either: //   -> Public static address
//   -> Random static address

BLE 或双模设备都拥有一个唯一的 身份地址(Identity Address),这个地址有以下两种合法类型:

  • Public static address(公开静态地址):以 IEEE 分配的 OUI 开头,是制造商固定分配的地址;

  • Random static address(随机静态地址):使用随机算法生成,但一段时间内保持不变。

这两种地址都能唯一标识 BLE 设备,是建立配对关系、保存配置、建立连接的“身份证”。


  // If remote device uses LE random private resolvable address, user of this API must resolve its identity address// before calling this method to get the device object

如果远程设备使用的是:

  • Resolvable Private Address(RPA,可解析随机地址)

那么调用方必须先通过本地保存的 IRK(Identity Resolving Key) 解析出对方的真实身份地址(即 Public Static 或 Random Static),再调用本方法

否则你可能会用一个临时地址来查找设备,查找会失败或返回新对象。


  // Note: A dual mode device's identity address is normally the same as its BR/EDR address, but they can also be// different. Hence, please don't make such assumption and don't use GetDeviceByBrEdrMacAddress() interchangeably

重要警告:不要混用 BLE 身份地址 和 BR/EDR 地址!

  • 对于双模设备(支持 BLE 和 BR/EDR),其 BLE 身份地址 通常和 BR/EDR MAC 地址相同,但这并不是强制的
  • 因此:
    • 不能假设它们相同!
    • 不要用 GetDeviceByBrEdrMacAddress() 来获取 BLE 设备对象,应使用 GetDeviceByLeIdentityAddress()

2. 作用

该函数用于通过 LE 设备的身份地址(Identity Address) 获取对应的 Device 对象,即代表某个 BLE 或双模设备(BR/EDR + BLE)的对象。

3. 使用时机和条件

使用场景是否适用
设备是 BLE-only 或 Dual-mode✅ 适用
已知对方的身份地址(public static 或 random static)✅ 适用
对方使用的是 RPA(可解析地址)❌ 必须先解析出身份地址才能使用此方法
只知道 BR/EDR 地址❌ 应使用 GetDeviceByClassicMacAddress()
不清楚地址类型(例如首次见到的随机地址)❌ 应使用 GetDeviceByLegacyKey() 保存首次信息

4. 与其它获取设备方法对比

方法适用地址类型场景是否需已知身份
GetDeviceByLegacyKey()任意首次见到的地址初次遇到设备,尚未配对或解析身份时
GetDeviceByClassicMacAddress()BR/EDR MAC经典蓝牙设备(或双模的 BR/EDR 部分)
GetDeviceByLeIdentityAddress()Public static / Random staticBLE 设备(或双模 BLE 部分),身份已解析

5.例子

假设你车机上连接了一个 BLE 心率带(Heart Rate Monitor):

  • 心率带使用 RPA 地址广播;
  • 你使用 IRK 成功解析出其 identity address = A4:C1:38:11:22:33
  • 然后你可以:

Device heart_rate_device = storage_module.GetDeviceByLeIdentityAddress(Address::FromString("A4:C1:38:11:22:33"));

此时 Device 对象会对应到配置文件中 [A4:C1:38:11:22:33] 段,所有配置信息(配对信息、特征值、服务等)都与这个地址关联。

4. GetAdapterConfig

  • system/gd/storage/storage_module.h
// A think copyable, movable, comparable object that is used to access adapter level informationAdapterConfig GetAdapterConfig();AdapterConfig StorageModule::GetAdapterConfig() {std::lock_guard<std::recursive_mutex> lock(mutex_);return AdapterConfig(&pimpl_->cache_, &pimpl_->memory_only_cache_, kAdapterSection);
}

1. AdapterConfig 介绍

AdapterConfig 是一个:

  • 轻量级(thin) 对象:开销小
  • 可复制(copyable)
  • 可移动(movable)
  • 可比较(comparable)

它的设计目的是:用于访问“适配器级别(adapter-level)”的信息。
这里的“适配器”一般是指 蓝牙本地控制器(Bluetooth Adapter),也就是主机设备自身的蓝牙模块,而不是连接的外部设备。

2. 作用:

获取一个表示当前本地蓝牙适配器配置的 AdapterConfig 对象。

这个对象可以被用来:

  • 读取本地蓝牙模块的各种状态配置(如名称、地址、功能支持情况)
  • 写入/修改这些配置值(例如设置本地设备名)
  • 比较不同配置是否相等
  • 被用于持久化或在不同模块间传递

3. 参数说明:

AdapterConfig(&pimpl_->cache_, &pimpl_->memory_only_cache_, kAdapterSection)

AdapterConfig 被初始化时传入了三个参数:

参数含义
&pimpl_->cache_持久化配置缓存,读取自 config 文件,用于保存“有用的设备信息”
&pimpl_->memory_only_cache_仅保存在内存中的配置项,不会写入磁盘,适合存放临时信息
kAdapterSection表示“配置文件中哪一段”是适配器相关配置,一般是 [Adapter]

这个构造函数的逻辑是:

在两个缓存中查找和操作 kAdapterSection(也就是适配器配置段)的属性。


4. 使用场景举例

我们可以用 AdapterConfig 做如下操作:

读取属性
auto config = storage_module.GetAdapterConfig();
auto name = config.GetProperty("Name");  // 读取适配器名称
写入属性

config.SetProperty("Name", "MyBluetoothCar");

删除属性

config.RemoveProperty("DiscoverableTimeout");

这些修改通常会在适当的时候触发保存(例如通过 SaveDelayed())。


5. 与设备配置的区别

类别接口描述
适配器配置GetAdapterConfig()访问和操作 本地蓝牙适配器的属性(如本地设备名称、可发现性等)
设备配置GetDeviceByClassicMacAddress()针对每一个连接的远程设备,分别维护其配置信息(如配对密钥、设备类型等)

6. 案例

[Adapter]
Address = 22:22:96:de:b1:39
LE_LOCAL_KEY_IRK = 7c39750923b27df313f988bbfee74bb8
LE_LOCAL_KEY_IR = ad109bf638e98981aa439c9f0570bfe9
LE_LOCAL_KEY_DHK = d48e846c3fb280a572fbec8e096a137d
LE_LOCAL_KEY_ER = 74a3b80a8145351e9b0943600e8a68e3
ScanMode = 0
DiscoveryTimeout = 120
Name = cheji_8295
配置项示例值含义与功能说明
Address22:22:96:de:b1:39本地蓝牙模块的 MAC 地址,是蓝牙适配器在 BR/EDR 和 BLE 中都使用的物理地址。它由硬件指定,唯一标识当前设备。
LE_LOCAL_KEY_IRK7c39750923b27df313f988bbfee74bb8Identity Resolving Key (IRK):用于在 BLE 中解析对方设备的 可解析随机地址(Resolvable Private Address)。是隐私保护机制的一部分。
LE_LOCAL_KEY_IRad109bf638e98981aa439c9f0570bfe9Identity Root (IR):用作生成本地 BLE 安全密钥(如 IRK)的根密钥,AOSP BLE 栈通过它生成 IRK。是 BLE 安全子系统的基础密钥之一。
LE_LOCAL_KEY_DHKd48e846c3fb280a572fbec8e096a137dDiversifier Hash Key (DHK):用于计算加密用的 Diversifier(DIV) 的散列,在密钥分发和存储时使用。
LE_LOCAL_KEY_ER74a3b80a8145351e9b0943600e8a68e3Encryption Root (ER):用于派生加密相关的 BLE 密钥,如 LTK(长期密钥)。是 BLE 的核心加密密钥根。
ScanMode0蓝牙适配器的 可发现性模式
- 0:不可发现,不可连接
- 1:仅限连接(不可被搜索到)
- 2:可发现且可连接
DiscoveryTimeout120蓝牙在“可发现模式”下的持续时间(单位:秒),超过这个时间后将自动退出发现状态。
Namecheji_8295本地蓝牙设备名称(如在手机、耳机列表中看到的名称)。默认显示给外部设备的名称,车机中常被设置为“设备名”。

关于 BLE 密钥项的小总结
密钥项用途分类作用
IRK隐私解析远程设备的 Resolvable Private Address
IR身份根密钥用于派生 IRK
ER加密根密钥用于派生 LTK、STK 等
DHK散列辅助密钥用于生成身份散列与验证

这些密钥是在 BLE 的配对和加密过程中 本地设备生成和使用的私有密钥,通常保存在本地配置中,确保在设备重启后仍能保持配对关系。


示例应用场景
  • 车机初次启动时,蓝牙模块读取 bt_config.conf 中的 [Adapter] 段,恢复 BLE 密钥、名称、扫描模式等信息。
  • BLE 连接中,系统通过 IRK 解析陌生的随机地址,判断是否为熟悉设备。
  • ScanMode 与 DiscoveryTimeout 结合使用,控制设备是否暴露在搜索中,避免长时间保持可发现节省功耗。

7. 总结

  • GetAdapterConfig() 返回的是一个轻量级对象,用于访问 本地蓝牙适配器(即车机或手机自身)相关的配置。
  • 它支持读写、比较,适合被广泛传递使用。
  • GetDeviceXXX() 系列函数返回的是远程设备的配置不同,这里关注的是本地适配器级别的设置(Adapter Section)。

5. GetBondedDevices

  • system/gd/storage/storage_module.h
// Get a list of bonded devices from configstd::vector<Device> GetBondedDevices();std::vector<Device> StorageModule::GetBondedDevices() {std::lock_guard<std::recursive_mutex> lock(mutex_);/*1. GetConfigCache() 返回的是配置缓存对象(通常是对 bt_config.conf 的抽象表示)。2. GetPersistentSections() 会获取所有持久化的 section 名称,也就是 config 文件中类似于 [XX:XX:XX:XX:XX:XX] 的条目3. 这些 section 通常是代表 已绑定设备 的 MAC 地址段。*/auto persistent_sections = GetConfigCache()->GetPersistentSections();std::vector<Device> result; // 创建结果数组 result,类型为 std::vector<Device>result.reserve(persistent_sections.size()); // 预先分配空间以提升性能,避免多次内存分配/*1. 遍历每个配置段名(通常是 MAC 地址);2. 为每个 section 构造一个 Device 对象,并将其加入结果列表中;3. 这些 Device 对象通过配置 section 名(通常是地址)定位,并绑定了:1. pimpl_->cache_: 永久性缓存(从 config 文件中读取);2. pimpl_->memory_only_cache_: 内存中的缓存(运行时变化,但不持久化);3. section: 该设备的唯一标识(如 MAC 地址);*/for (const auto& section : persistent_sections) {result.emplace_back(&pimpl_->cache_, &pimpl_->memory_only_cache_, section);}return result;
}

1. 作用

从配置中获取所有已绑定(Bonded)的设备列表,并以 std::vector<Device> 的形式返回。


2. 什么是 Bonded Device?

在蓝牙中,“Bonded” 表示 两台设备已经完成配对,并在配置中保存了配对信息(如密钥),以便后续自动连接。

在 Android 和 AOSP 中,bonded 设备的身份信息会被写入 bt_config.conf,如下所示:


[00:1b:dc:f4:b1:83]
Timestamp = 1745558511
DevClass = 1049092
DevType = 3
AddrType = 0
Name = PTS-PBAP-B183
LinkKeyType = 8
PinLength = 0
LinkKey = a88655950fc0ad9xxxxxxx31e96bde
MetricsId = 21
Service = 0000110a-0000-1000-8000-00805f9b34fb
AvdtpVersion = 0001[70:8f:47:91:b0:62]
Name = cbx
DevClass = 5898764
DevType = 3
AddrType = 0
SdpDiManufacturer = 29
SdpDiModel = 4608
SdpDiHardwareVersion = 5174
SdpDiVendorIdSource = 1
Service = 00001105-0000-1000-8000-00805f9b34fb 0000110a-0000-1000-8000-00805f9b34fb 0000110c-0000-1000-8000-00805f9b34fb 0000110e-0000-1000-8000-00805f9b34fb 00001112-0000-1000-8000-00805f9b34fb 00001115-0000-1000-8000-00805f9b34fb 00001116-0000-1000-8000-00805f9b34fb 0000111f-0000-1000-8000-00805f9b34fb 0000112d-0000-1000-8000-00805f9b34fb 0000112f-0000-1000-8000-00805f9b34fb 00001132-0000-1000-8000-00805f9b34fb 00001200-0000-1000-8000-00805f9b34fb 2c042b0a-7f57-4c0a-afcf-1762af70257c 8fa9c715-bd1f-596c-a1b0-13162b15c892 9fed64fd-e91a-499e-88dd-73dfe023feed
Timestamp = 1745510776
LinkKeyType = 8
PinLength = 0
LinkKey = 6453dc8978aexxxxxxx0b4e7b90
MetricsId = 25
AvdtpVersion = 0301

这类信息只有配对完成后才会存在。


3. 小结

内容说明
功能返回所有已绑定设备(从配置文件读取)
数据来源bt_config.conf 中所有持久化的 [section](通常是设备地址)
条件已绑定设备才会出现在 config 文件中
返回类型std::vector<Device>,每个元素表示一个绑定设备
线程安全是(加了 mutex 锁)
常见用途蓝牙启动后恢复已配对设备、构建连接设备列表、展示配对历史等

6. Modify

  • system/gd/storage/storage_module.h
  // Modify the underlying config by starting a mutation. All entries in the mutation will be applied atomically when// Commit() is called. User should never touch ConfigCache() directly.Mutation Modify();Mutation StorageModule::Modify() {std::lock_guard<std::recursive_mutex> lock(mutex_);return Mutation(&pimpl_->cache_, &pimpl_->memory_only_cache_);
}

1. 功能:

  • 提供一种 安全且原子的方式 修改底层配置(如 bt_config.conf)。
  • 返回一个 Mutation 对象,允许你设置多个键值对。
  • 所有改动在 Commit()不会生效,这确保了改动的 原子性(要么全生效,要么全不生效)。

2. Mutation 是什么?

Mutation mutation = storage_module.Modify();
mutation.SetProperty(address, "Name", "my_device");
mutation.SetProperty(address, "LinkKey", "AABBCC...");
mutation.Commit();
1. Mutation 的工作流程:
阶段操作说明
开始修改Mutation mutation = Modify()获取一个修改器对象
设置内容mutation.SetProperty(...)设置一个或多个配置项
提交mutation.Commit()所有配置原子性地写入 config 文件

2. 意义和优势
特性说明
原子性所有配置更改必须通过 Commit() 提交,保证一致性
安全性多线程访问加锁,防止并发读写冲突
避免误用不允许直接访问 ConfigCache(),必须通过 Mutation 间接操作
灵活性一次修改多个配置字段,不需要单独写入磁盘多次
统一接口所有对配置的修改都封装在 Mutation 内,便于维护和扩展

3. 使用场景示例

以下是几个典型的使用情境:

1. 蓝牙配对完成后写入配对信息
auto mutation = storage->Modify();
mutation.SetProperty(mac_address, "LinkKey", link_key);
mutation.SetProperty(mac_address, "KeyType", key_type);
mutation.Commit();
2. 用户更改蓝牙本地设备名称
auto mutation = storage->Modify();
mutation.SetAdapterProperty("Name", "My_Car_Device");
mutation.Commit();
3. 清除某个设备的配置信息
auto mutation = storage->Modify();
mutation.RemoveDevice(device_address);
mutation.Commit();

4. 小结

项目内容
函数名StorageModule::Modify()
返回值Mutation 对象
作用开启一段“配置修改事务”
线程安全是(加锁)
提交方式通过 Mutation::Commit() 原子性写入
好处

相关文章:

  • 关于 js:8. 反调试与混淆识别
  • 基于javaweb的SpringBoot酒店管理系统设计与实现(源码+文档+部署讲解)
  • [51单片机]---DS18B20 温度检测
  • MYSQL 的缓存策略(四)
  • python视频拆帧并根据所选区域保存指定区域
  • Memcached 的特性和使用场景介绍,以及集群搭建
  • 基于机器学习的卫星钟差预测方法研究HPSO-BP
  • 腾讯发布数字人框架MuseTalk 1.5,开放训练逻辑,生成效果进一步优化~
  • MCP Server多节点滚动升级一致性治理
  • 爆肝整理!软件测试面试题整理(项目+接口问题)
  • 2025年真实面试问题汇总(二)
  • 如何检测和解决服务器端口被占用的问题
  • 分布式异步强化学习框架训练32B大模型:INTELLECT-2
  • vue2将文字转为拼音
  • 【Python生活】如何构建一个跌倒检测的算法?
  • 8天Python从入门到精通【itheima】-6~10
  • 蓝桥杯 10. 全球变暖
  • H5S视频平台-Ascend昇腾 GPU转码
  • 双种群进化算法:动态约束处理与资源分配解决约束多目标优化问题
  • 鹅厂面试数学题
  • 广东省中医院脾胃病科大科主任张北平病逝,年仅52岁
  • 广州一饮品店取名“警茶”?市监局:取名没问题,但图像会产生误解
  • 体坛联播|安切洛蒂执掌巴西男足,字母哥尝试离开雄鹿
  • 牛市早报|中美日内瓦经贸会谈联合声明公布
  • 食用城市|食饭识人
  • 火车站员工迟到,致出站门未及时开启乘客被困?铁路部门致歉