【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 都要执行那下面几步:
-
创建module 实体
- Module* instance = module->ctor_();
-
将 当前 module 实体和 gd_stack_thread 线程绑定
- set_registry_and_handler(instance, thread);
-
启动当前模块所依赖的所有子模块。
- instance->ListDependencies(&instance->dependencies_);
- Start(&instance->dependencies_, thread);
-
最后调用自己的 Start() 函数
- instance->Start();
-
将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 实体
- 创建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 线程绑定
- 将 当前 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.启动当前模块所依赖的所有子模块
- 启动当前模块所依赖的所有子模块。
- 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() 函数
- 最后调用自己的 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_
- 将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 功能介绍:
-
通过 Device 抽象层访问存储层的方法
- 设备对象会在 GetDeviceByLegacyKey 方法被调用时“延迟创建”。因此,返回类型中不使用 std::optional<> 或 nullptr。 使用该 API 的用户可以通过 Device 对象本身的 API 判断该设备是否已经存在(即是否真实持久化)
- 没有配置值的设备不会被保存到配置缓存中(即不会持久化)
- 未配对的设备在蓝牙协议栈关闭时也会被丢弃(不会写入配置文件)
-
概念部分:
- BR/EDR 地址:
- 仅包含“公共静态地址”,以 IEEE 分配的 3 字节 OUI(组织唯一标识符)开头
- BLE 地址:
- 公共地址:同样以 IEEE 分配的 OUI 开头
- 静态型:静态公共地址不会改变
- 私有/可变型:目前尚未见到此类公共地址
- 随机地址:由设备随机生成,不以 IEEE OUI 开头
- 静态型:静态随机地址不会改变
- 私有/可变型:私有随机地址会周期性变化
- 可解析类型:该地址可通过 IRK(身份解析密钥)还原为固定地址
- 不可解析类型:该地址仅临时使用,不应保存(即不持久化)
- 公共地址:同样以 IEEE 分配的 OUI 开头
- BR/EDR 地址:
-
MAC 地址仅有 6 字节,因此它们只在区域内具有唯一性(不能全球唯一)
-
使用 legacy_key_address 获取设备对象:
-
使用
legacy_key_address
获取设备对象。在传统配置中,每个设备的配置信息存储在以 MAC 地址为键的配置段中。 -
对于 BR/EDR 设备来说,这很简单,因为它只有一个公共的、静态的 MAC 地址。
-
但对于使用私有地址的 LE 设备,我们只有在配对后才能知道它真正的静态地址
-
由于在配对之前我们仍然需要保存设备的信息,因此无论是随机私有地址还是静态公共地址,都会使用首次发现的地址作为“键”来存储该设备的配置信息。
-
此方法会根据该 legacy key 返回一个设备对象。如果该 key 不存在,则会延迟创建该设备(不会立即写入配置)。
这段注释主要说明了:
- 设备的创建是“延迟”的:只有在真正需要操作设备时才会生成
Device
对象。 - 返回值不是 optional 或指针:直接返回对象,由调用者通过 API 判断其是否存在于配置中。
- 未配对或无配置的设备不会被持久化:配置文件只保存有效信息。
- 设备地址有多种类型,尤其是 BLE 设备使用的随机地址在配对前无法确认其真实身份,因此使用“首次发现地址”作为配置键。
- 函数提供的是 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
的区别
比较项 | GetDeviceByClassicMacAddress | GetDeviceByLegacyKey |
---|---|---|
适用设备类型 | 仅适用于经典蓝牙(BR/EDR)或双模设备 | 主要用于 BLE 设备,尤其是尚未配对、地址可能不固定的设备 |
地址含义 | 地址是设备唯一的、固定的公共 BR/EDR MAC 地址 | 地址可能是首次看到的 BLE 随机地址,用作“临时键” |
稳定性 | 地址稳定且可以长期作为唯一标识 | 地址可能会变化(如 BLE 私有地址),真实身份需配对后才能确认 |
地址类型标识 | Device::ConfigKeyAddressType::CLASSIC_ADDRESS | Device::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 = 29 | SDP 的 Device Identification Profile 中的 manufacturer ID(制造商编号,29 代表 Samsung) |
SdpDiModel = 4608 | SDP 中的 Model ID,厂商自定义字段,表明型号 |
SdpDiHardwareVersion = 5174 | SDP 中的硬件版本号(例如可能表示 HW rev 5.1.74) |
SdpDiVendorIdSource = 1 | Vendor ID 来源类型:1 = Bluetooth SIG 分配的 Vendor ID2 = USB-IF 分配的 Vendor ID |
Service = UUID 列表 | SDP 查询发现的支持服务 UUID(以空格分隔):例如 0000110a... 是 A2DP Sink具体服务见下表 |
Timestamp = 1745510776 | 最后一次交互时间戳(Unix 时间,单位:秒) |
LinkKeyType = 8 | Link Key 类型,表示加密方式:4 = Unauthenticated 5 = Authenticated 6 = Changed combination 8 = Secure connections |
PinLength = 0 | 用于旧配对方式的 PIN 长度,常为 0 表示未使用。 |
LinkKey = … | BR/EDR 模式下配对生成的密钥,用于身份验证。是一个 16 字节的十六进制字符串。 |
MetricsId = 25 | Android 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/2f | PBAP | 电话簿访问协议 |
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 static | BLE 设备(或双模 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
配置项 | 示例值 | 含义与功能说明 |
---|---|---|
Address | 22:22:96:de:b1:39 | 本地蓝牙模块的 MAC 地址,是蓝牙适配器在 BR/EDR 和 BLE 中都使用的物理地址。它由硬件指定,唯一标识当前设备。 |
LE_LOCAL_KEY_IRK | 7c39750923b27df313f988bbfee74bb8 | Identity Resolving Key (IRK):用于在 BLE 中解析对方设备的 可解析随机地址(Resolvable Private Address)。是隐私保护机制的一部分。 |
LE_LOCAL_KEY_IR | ad109bf638e98981aa439c9f0570bfe9 | Identity Root (IR):用作生成本地 BLE 安全密钥(如 IRK)的根密钥,AOSP BLE 栈通过它生成 IRK。是 BLE 安全子系统的基础密钥之一。 |
LE_LOCAL_KEY_DHK | d48e846c3fb280a572fbec8e096a137d | Diversifier Hash Key (DHK):用于计算加密用的 Diversifier(DIV) 的散列,在密钥分发和存储时使用。 |
LE_LOCAL_KEY_ER | 74a3b80a8145351e9b0943600e8a68e3 | Encryption Root (ER):用于派生加密相关的 BLE 密钥,如 LTK(长期密钥)。是 BLE 的核心加密密钥根。 |
ScanMode | 0 | 蓝牙适配器的 可发现性模式: - 0 :不可发现,不可连接 - 1 :仅限连接(不可被搜索到) - 2 :可发现且可连接 |
DiscoveryTimeout | 120 | 蓝牙在“可发现模式”下的持续时间(单位:秒),超过这个时间后将自动退出发现状态。 |
Name | cheji_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() 原子性写入 |
好处 |