【android bluetooth 框架分析 02】【Module详解 4】【Btaa 模块介绍】
1. 背景
我们在上一篇文章中介绍 HciHal 模块时,有如下代码
// system/gd/hal/hci_hal_android_hidl.ccvoid ListDependencies(ModuleList* list) const {list->add<SnoopLogger>();if (common::init_flags::btaa_hci_is_enabled()) {list->add<activity_attribution::ActivityAttribution>();}}
- 在加载 HciHal 模块时,先要去加载 他的依赖模块, 而 HciHal 模块依赖 SnoopLogger 和 activity_attribution::ActivityAttribution 模块。
- 而这里说的 activity_attribution::ActivityAttribution 就是我们今天要介绍的 Btaa
// system/gd/btaa/android/activity_attribution.cc
std::string ActivityAttribution::ToString() const {return "Btaa Module";
}
我们先回忆一下 模块加载的流程:
在 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);
- 我们在 HciHal 模块中 依赖 Btaa, 所以这里将会触发 Btaa 模块的加载, 跳到第1步开始加载 Btaa
-
最后调用自己的 Start() 函数
- instance->Start();
-
将module 实体加入到 started_modules_
- started_modules_[module] = instance;
2. BTAA 介绍
BTAA(Bluetooth Activity Attribution)模块,这个模块的作用是 收集和归因(Attribution)蓝牙相关的活动数据(如 wakelock 使用、设备唤醒、HCI 包传输等),用于 功耗分析、性能优化和调试。
2.1 它是干嘛的?一句话说清楚!
这个模块就是为了监控和记录蓝牙活动,比如:
- 蓝牙让手机唤醒了
- 蓝牙让系统保持唤醒了(wakelock)
- 蓝牙收发了什么数据(HCI 数据包) 然后记录下来:“这是谁干的?”——哪个 App?哪个蓝牙设备?持续多久?
2.2 它的功能拆解(逐条解释)
我们把它的功能拆成几个核心点,每点配上通俗解释 + 示例:
1. 监控蓝牙 wakelock 的使用
什么是 wakelock?
Wakelock 是 Android 用来控制设备是否进入休眠的锁,比如蓝牙模块为了传数据,要让 CPU 保持工作,它就会申请 wakelock。
模块做了什么? 它监听了一个名字叫 hal_bluetooth_lock
的 wakelock,蓝牙模块每次获取或释放这个锁,都会通知这个 BTAA 模块。
在哪里监听的? 通过 AIDL 接口:registerWakelockCallback()
,注册 wakelock 回调。
举例:
某个蓝牙耳机在后台播放音乐,系统蓝牙模块为了保持传输流畅,一直持有 wakelock,这段时间就是功耗关键点。BTAA 就记录下 wakelock 什么时候开始、持续了多久。
2️. 监听系统唤醒(Wakeup)事件
什么是 wakeup?
当手机在待机或打盹时(Doze 模式),某个外设(比如蓝牙设备)唤醒了系统,这个行为就是 wakeup。
模块做了什么? 监听 wakeup 原因里是否包含 hs_uart_wakeup
(蓝牙唤醒),如果有,说明是蓝牙唤醒了手机,BTAA 记录下来。
在哪里监听的? 通过 AIDL 接口:registerCallback()
,注册 wakeup 回调。
举例:
你用蓝牙手环设了早上 7 点叫你起床。凌晨 6:59 手环通过蓝牙发送唤醒指令,系统因此亮屏。这就是一次 wakeup 事件,BTAA 会记录:“蓝牙唤醒了系统”。
3️. 截取蓝牙 HCI 数据包
什么是 HCI 数据包?
HCI(Host Controller Interface)是主机与蓝牙硬件之间传数据的协议,比如发命令、事件通知、ACL 数据传输。
模块做了什么? 每次有蓝牙数据包(CMD、EVT、ACL 等)传过来,模块都会截取部分内容记录。
处理方式:
-
短的命令/事件:全记录
-
大数据包(如 ACL):只截取头部(前 4 字节)
举例:
某个 App 在跟蓝牙温度计通信,系统发了一条命令“获取温度”,HCI 层生成了 CMD 包,BTAA 会截取这条命令,记录是谁发的,发了什么。
4️. 归因(Attribution)
模块做了什么? 根据 UID、包名、MAC 地址等,把每次 wakelock、wakeup、数据传输的来源 “归因” 到:
-
哪个 App?
-
哪个设备?
举例:
App A 通过 MAC 地址
01:23:45:67:89:AB
连了个蓝牙设备,然后 App 一直保持连接,导致 wakelock 时间很长。
BTAA 会记录:UID=10345,包名=com.example.a,Device=01:23:45:67:89:AB,wakelock=3分钟。
2.3 案例分析:真实场景中的作用
1. 场景 1:定位蓝牙耗电异常
问题描述:
用户反馈手机发热,耗电快,但他没主动用蓝牙。
BTAA 能干啥:
-
发现有 App 背景连了蓝牙设备
-
wakelock 持续很久
-
蓝牙传了大量 HCI 数据
-
唤醒手机多次
分析结果:
App “Fitness Tracker” 在后台连接蓝牙心率带,导致 wakelock 没释放,系统一直没休眠。
2. 场景 2:唤醒问题分析
问题描述:
设备每天凌晨都会被唤醒一次,不清楚是谁唤醒的。
BTAA 能干啥:
-
每次唤醒都会记录 wakeup 原因
-
如果是
"hs_uart_wakeup"
,说明是蓝牙干的 -
还可以关联设备 MAC 地址
分析结果:
蓝牙门锁每晚定时传数据,导致唤醒手机。
3. 场景 3:Framework 功耗统计 / dumpsys 支持
模块如何支持系统工具?
-
ActivityAttribution::GetDumpsysData()
实现了 dumpsys 接口 -
系统可以通过
dumpsys bluetooth_manager
或类似命令 dump BTAA 数据
例子输出结构:
`UID: 10345 App: com.example.sensorapp Device: 12:34:56:78:90:AB Wakelock Duration: 2750ms Wakeups: 2`
2.4 总结一句话
🔊 BTAA 模块 = 蓝牙活动记录仪 + 问责系统
它监控蓝牙活动,记录是谁唤醒了系统,谁在用蓝牙传数据,谁在耗电,所有信息都可归因到具体 App 和设备,便于调试和优化。
3. 代码中如何实现
本部分将从 btaa 模块的加载 、如何工作等方面结合代码来介绍,具体实现。
3.1 模块启动流程
-
创建module 实体
- Module* instance = module->ctor_();
-
将 当前 module 实体和 gd_stack_thread 线程绑定
- set_registry_and_handler(instance, thread);
-
启动当前模块所依赖的所有子模块。
- instance->ListDependencies(&instance->dependencies_);
- Start(&instance->dependencies_, thread);
- 我们在 HciHal 模块中 依赖 Btaa, 所以这里将会触发 Btaa 模块的加载, 跳到第1步开始加载 Btaa
-
最后调用自己的 Start() 函数
- instance->Start();
-
将module 实体加入到 started_modules_
- started_modules_[module] = instance;
1. 创建module 实体
Module* instance = module->ctor_();
- 当ModuleRegistry::Start 调用上面代码时,其实是去 new ActivityAttribution()
// system/gd/btaa/android/activity_attribution.cc
const ModuleFactory ActivityAttribution::Factory = ModuleFactory([]() { return new ActivityAttribution(); });
// system/gd/btaa/activity_attribution.h
class ActivityAttribution : public bluetooth::Module {public:ActivityAttribution() = default;ActivityAttribution(const ActivityAttribution&) = delete;ActivityAttribution& operator=(const ActivityAttribution&) = delete;~ActivityAttribution() = default;void Capture(const hal::HciPacket& packet, hal::SnoopLogger::PacketType type);void OnWakelockAcquired();void OnWakelockReleased();void OnWakeup();void RegisterActivityAttributionCallback(ActivityAttributionCallback* callback);void NotifyActivityAttributionInfo(int uid, const std::string& package_name, const std::string& device_address);static const ModuleFactory Factory;protected:std::string ToString() const override;void ListDependencies(ModuleList* list) const override;void Start() override;void Stop() override;DumpsysDataFinisher GetDumpsysData(flatbuffers::FlatBufferBuilder* builder) const override; // Moduleprivate:struct impl; // 这里请关注一下这个, ActivityAttribution 类中还包含 impl 类std::unique_ptr<impl> pimpl_;
};
2. 将 当前 module 实体和 gd_stack_thread 线程绑定
ModuleRegistry::Start(const ModuleFactory* module, Thread* thread){Module* instance = module->ctor_();LOG_INFO("Starting of %s", instance->ToString().c_str());last_instance_ = "starting " + instance->ToString();set_registry_and_handler(instance, thread); // 这里同样也是 将 gd_stack_thread 传递给 btaa 模块}void ModuleRegistry::set_registry_and_handler(Module* instance, Thread* thread) const {instance->registry_ = this;instance->handler_ = new Handler(thread); // 最终将 gd_stack_thread 的 handler 保存在handler_ 中
}
我们在 btaa 模块中,会大量看到 CallOn 例如如下代码
// system/gd/btaa/android/activity_attribution.ccvoid ActivityAttribution::OnWakelockAcquired() {CallOn(pimpl_.get(), &impl::on_wakelock_acquired);
}// system/gd/module.htemplate <typename T, typename Functor, typename... Args>void CallOn(T* obj, Functor&& functor, Args&&... args) {GetHandler()->CallOn(obj, std::forward<Functor>(functor), std::forward<Args>(args)...);}// system/gd/module.cc
Handler* Module::GetHandler() const {ASSERT_LOG(handler_ != nullptr, "Can't get handler when it's not started");return handler_;
}
- 通过 CallOn 就可以将 我们的 任务传递给 通过 set_registry_and_handler 绑定的 线程。 这里就是 gd_stack_thread
3. 启动当前模块所依赖的所有子模块
- instance->ListDependencies(&instance->dependencies_);
- Start(&instance->dependencies_, thread);
void ActivityAttribution::ListDependencies(ModuleList* list) const {}
- 这里看到 btaa 模块不依赖任何 模块
4. 最后调用自己的 Start() 函数
- instance->Start();
// system/gd/btaa/android/activity_attribution.cc
void ActivityAttribution::Start() {pimpl_ = std::make_unique<impl>(this);
}
- 这里创建 了 ActivityAttribution::impl 对象
5. 将module 实体加入到 started_modules_
- started_modules_[module] = instance;
3.2 ActivityAttribution::impl 对象
在 3.1.4 中我们看到 会创建 一个 ActivityAttribution::impl 对象,本节就来看看, ActivityAttribution::impl 是如何工作的。
// system/gd/btaa/android/activity_attribution.cc
struct ActivityAttribution::impl {impl(ActivityAttribution* module) {std::lock_guard<std::mutex> guard(g_module_mutex);g_module = module;if (is_wakeup_callback_registered && is_wakelock_callback_registered) {LOG_ERROR("Wakeup and wakelock callbacks are already registered");return;}Status register_callback_status;bool is_register_successful = false;auto control_service =ISuspendControlService::fromBinder(SpAIBinder(AServiceManager_getService("suspend_control")));if (!control_service) {LOG_ERROR("Fail to obtain suspend_control");return;}if (!is_wakeup_callback_registered) {g_wakeup_callback = SharedRefBase::make<wakeup_callback>();register_callback_status = control_service->registerCallback(g_wakeup_callback, &is_register_successful);if (!is_register_successful || !register_callback_status.isOk()) {LOG_ERROR("Fail to register wakeup callback");return;}is_wakeup_callback_registered = true;}if (!is_wakelock_callback_registered) {g_wakelock_callback = SharedRefBase::make<wakelock_callback>();register_callback_status =control_service->registerWakelockCallback(g_wakelock_callback, kBtWakelockName, &is_register_successful);if (!is_register_successful || !register_callback_status.isOk()) {LOG_ERROR("Fail to register wakelock callback");return;}is_wakelock_callback_registered = true;}}~impl() {std::lock_guard<std::mutex> guard(g_module_mutex);g_module = nullptr;}void on_hci_packet(hal::HciPacket packet, hal::SnoopLogger::PacketType type, uint16_t length) {attribution_processor_.OnBtaaPackets(std::move(hci_processor_.OnHciPacket(std::move(packet), type, length)));}void on_wakelock_acquired() {wakelock_processor_.OnWakelockAcquired();}void on_wakelock_released() {uint32_t wakelock_duration_ms = 0;wakelock_duration_ms = wakelock_processor_.OnWakelockReleased();if (wakelock_duration_ms != 0) {attribution_processor_.OnWakelockReleased(wakelock_duration_ms);}}void on_wakeup() {attribution_processor_.OnWakeup();}void register_callback(ActivityAttributionCallback* callback) {callback_ = callback;}void notify_activity_attribution_info(int uid, const std::string& package_name, const std::string& device_address) {attribution_processor_.NotifyActivityAttributionInfo(uid, package_name, device_address);}void Dump(std::promise<flatbuffers::Offset<ActivityAttributionData>> promise, flatbuffers::FlatBufferBuilder* fb_builder) {attribution_processor_.Dump(std::move(promise), fb_builder);}ActivityAttributionCallback* callback_;AttributionProcessor attribution_processor_;HciProcessor hci_processor_;WakelockProcessor wakelock_processor_;
};
这个 struct ActivityAttribution::impl
是 ActivityAttribution
的实现部分,负责:
- 注册唤醒和 wakelock 回调(注册监听)
- 接收蓝牙 HCI 数据包
- 通知 Attribution 处理器去归因
- 接受系统/蓝牙模块信息更新
- 提供数据输出供 dumpsys 等调用
这部分是整个 BTAA 核心业务的“中控台”。
1. 构造函数
// system/gd/btaa/android/activity_attribution.ccstatic const std::string kBtWakelockName("hal_bluetooth_lock");impl(ActivityAttribution* module) {std::lock_guard<std::mutex> guard(g_module_mutex);g_module = module; // 这里传入的是 ActivityAttribution 对象// 防止重复注册监听。系统中只能有一份回调,避免多次注册导致重复响应。if (is_wakeup_callback_registered && is_wakelock_callback_registered) {LOG_ERROR("Wakeup and wakelock callbacks are already registered");return;}Status register_callback_status;bool is_register_successful = false;// 获取系统服务 suspend_control,这个服务负责睡眠唤醒相关的工作。// 是一个 AIDL 接口,跨进程通信,通常由系统底层的 power HAL 提供。auto control_service =ISuspendControlService::fromBinder(SpAIBinder(AServiceManager_getService("suspend_control")));if (!control_service) {LOG_ERROR("Fail to obtain suspend_control");return;}if (!is_wakeup_callback_registered) {// 创建一个 wakeup 回调对象(wakeup_callback 是内部类)g_wakeup_callback = SharedRefBase::make<wakeup_callback>();// 注册给 suspend_control,告诉它:蓝牙模块想监听你系统是否被蓝牙唤醒register_callback_status = control_service->registerCallback(g_wakeup_callback, &is_register_successful);if (!is_register_successful || !register_callback_status.isOk()) {LOG_ERROR("Fail to register wakeup callback");return;}is_wakeup_callback_registered = true;}if (!is_wakelock_callback_registered) {g_wakelock_callback = SharedRefBase::make<wakelock_callback>();// 注册监听蓝牙 wakelock 行为(kBtWakelockName = hal_bluetooth_lock)register_callback_status =control_service->registerWakelockCallback(g_wakelock_callback, kBtWakelockName, &is_register_successful);if (!is_register_successful || !register_callback_status.isOk()) {LOG_ERROR("Fail to register wakelock callback");return;}is_wakelock_callback_registered = true;}}
- 至此,BTAA 就已经接入了系统 power 管理流程的监听通道
2. 析构函数
~impl() {// 安全清理 g_module 指针std::lock_guard<std::mutex> guard(g_module_mutex);g_module = nullptr;}
3.on_hci_packet
void on_hci_packet(hal::HciPacket packet, hal::SnoopLogger::PacketType type, uint16_t length) {attribution_processor_.OnBtaaPackets(std::move(hci_processor_.OnHciPacket(std::move(packet), type, length)));}
解释:
- 蓝牙模块收到了一个 HCI 数据包(如连接命令、数据传输等)
- 先由
hci_processor_
做初步解析、抽象归一化 - 然后丢给
attribution_processor_
进一步做 App 归因
最终这些结果可以通过 如下命令查看,
adb shell dumpsys bluetooth_manager # 其中包含如下信息:activity_attribution_dumpsys_data: {title_device_wakeup: "----- Device-based Wakeup Attribution Dumpsys -----",num_device_wakeup: 0,device_wakeup_attribution: [],title_device_activity: "----- Device-based Activity Attribution Dumpsys -----",num_device_activity: 0,device_activity_aggregation: [],title_app_wakeup: "----- App-based Wakeup Attribution Dumpsys -----",num_app_wakeup: 0,app_wakeup_attribution: [],title_app_activity: "----- App-based Activity Attribution Dumpsys -----",num_app_activity: 0,app_activity_aggregation: []}
1. 如何调用到 on_hci_packet
当 蓝牙模块收到了一个 HCI 数据包 , 是如何一步步调用到 ActivityAttribution::impl::on_hci_packet 的?
还记得我们的 我们是为何加载 Btaa 模块的? 我们在加载 HciHal 模块的过程中, 发现HciHal模块依赖 SnoopLogger 模块和 Btaa 模块,所以此时 触发了 Btaa 模块。 当我们把 SnoopLogger 模块和 Btaa 模块 都加载完成后。就会触发 HciHal::Start 函数调用。
// system/gd/hal/hci_hal_android_hidl.ccvoid Start() override {if (common::init_flags::btaa_hci_is_enabled()) {// 这里会获得 btaa 的实例btaa_logger_ = GetDependency<activity_attribution::ActivityAttribution>();}btsnoop_logger_ = GetDependency<SnoopLogger>();LOG_INFO("GetService start.");...}
- 从 HciHal::Start 函数中,可以看到我们将 btaa 实例 赋值给了 btaa_logger_
- 也就是说, HciHal 模块中 收到来自于 hal 进程的数据后, 可以通过 btaa_logger_ 传递到 btaa 模块。
当 我们收到 hcievent 时,就会触发 HciHal模块如下代码:
// system/gd/hal/hci_hal_android_hidl.ccReturn<void> hciEventReceived(const hidl_vec<uint8_t>& event) override {common::StopWatch stop_watch(GetTimerText(__func__, event));std::vector<uint8_t> received_hci_packet(event.begin(), event.end());btsnoop_logger_->Capture(received_hci_packet, SnoopLogger::Direction::INCOMING, SnoopLogger::PacketType::EVT);if (common::init_flags::btaa_hci_is_enabled()) {btaa_logger_->Capture(received_hci_packet, SnoopLogger::PacketType::EVT); // 这里会触发到 btaa 模块的 Capture 方法}if (callback_ != nullptr) {callback_->hciEventReceived(std::move(received_hci_packet));}return Void();}
当我们要 发送cmd 到 hal时:
void sendHciCommand(HciPacket command) override {btsnoop_logger_->Capture(command, SnoopLogger::Direction::OUTGOING, SnoopLogger::PacketType::CMD);if (common::init_flags::btaa_hci_is_enabled()) {btaa_logger_->Capture(command, SnoopLogger::PacketType::CMD); // 这里会触发到 btaa 模块的 Capture 方法}bt_hci_->sendHciCommand(command);}
通过这两个实际函数,就能看到, HciHal中通过回调 btaa 的 Capture 函数,而回调到的 btaa 中处理。
HciHal 模块中的如下所有函数都会 回调 btaa 的 Capture 函数:
hciEventReceivedaclDataReceivedscoDataReceivedsendHciCommandsendAclDatasendScoData
我们继续看 btaa 的 Capture 函数:
// system/gd/btaa/android/activity_attribution.cc
void ActivityAttribution::Capture(const hal::HciPacket& packet, hal::SnoopLogger::PacketType type) {uint16_t original_length = packet.size();uint16_t truncate_length;switch (type) {case hal::SnoopLogger::PacketType::CMD:case hal::SnoopLogger::PacketType::EVT:truncate_length = packet.size();break;case hal::SnoopLogger::PacketType::ACL:case hal::SnoopLogger::PacketType::SCO:case hal::SnoopLogger::PacketType::ISO:truncate_length = kHciAclHeaderSize;break;}if (!truncate_length) {return;}hal::HciPacket truncate_packet(packet.begin(), packet.begin() + truncate_length);CallOn(pimpl_.get(), &impl::on_hci_packet, truncate_packet, type, original_length); // 最终调用到 ActivityAttribution::impl::on_hci_packet
}
2.场景示例:
App A 连了个设备
发了一个连接命令 HCI CMD
这里被截获并记录:“App A 发出了连接命令给 12:34:56:78:90:AB”
4.on_wakelock_acquired on_wakelock_released
// system/gd/btaa/android/activity_attribution.cc// impl::on_wakelock_acquired 实现void on_wakelock_acquired() {wakelock_processor_.OnWakelockAcquired();}void on_wakelock_released() {uint32_t wakelock_duration_ms = 0;wakelock_duration_ms = wakelock_processor_.OnWakelockReleased();if (wakelock_duration_ms != 0) {attribution_processor_.OnWakelockReleased(wakelock_duration_ms);}}
1. 如何调用到 on_wakelock_acquired
在ActivityAttribution::impl 构造函数 中, 我们向 suspend_control 服务注册了 wakelock_callback
// system/gd/btaa/android/activity_attribution.ccstatic const std::string kBtWakelockName("hal_bluetooth_lock");impl(ActivityAttribution* module) {
...// 获取系统服务 suspend_control,这个服务负责睡眠唤醒相关的工作。// 是一个 AIDL 接口,跨进程通信,通常由系统底层的 power HAL 提供。auto control_service =ISuspendControlService::fromBinder(SpAIBinder(AServiceManager_getService("suspend_control")));if (!control_service) {LOG_ERROR("Fail to obtain suspend_control");return;}...if (!is_wakelock_callback_registered) {g_wakelock_callback = SharedRefBase::make<wakelock_callback>(); // 创建了 wakelock_callback 对象// 注册监听蓝牙 wakelock 行为(kBtWakelockName = hal_bluetooth_lock)register_callback_status =control_service->registerWakelockCallback(g_wakelock_callback, kBtWakelockName, &is_register_successful);if (!is_register_successful || !register_callback_status.isOk()) {LOG_ERROR("Fail to register wakelock callback");return;}is_wakelock_callback_registered = true;}}
suspend_control
服务会在 系统电源管理层发生 wakelock 获取/释放事件时,通过 AIDL 回调机制 调用你提供的 wakelock_callback
,进而触发 BTAA 的统计逻辑。
suspend_control
是 系统 suspend(挂起)机制的控制服务,其 AIDL 定义位于:
system/hardware/interfaces/suspend/aidl/android/hardware/power/suspend/
服务名称就是:suspend_control
。
它的职责是:
1.追踪各个 subsystem(比如 BT、WiFi、Audio 等)是否申请 wakelock。
2.当 wakelock 获取/释放时,通知订阅者(通过 AIDL 回调)。
3.控制是否允许系统进入 suspend(挂起)状态。
struct wakelock_callback : public BnWakelockCallback {wakelock_callback() {}// wakelock 获取后, 触发 notifyAcquired 调用Status notifyAcquired() override {std::lock_guard<std::mutex> guard(g_module_mutex);if (g_module != nullptr) {g_module->OnWakelockAcquired(); // 调用 ActivityAttribution::OnWakelockAcquired}return Status::ok();}// wakelock 释放后, 触发 notifyReleased 调用Status notifyReleased() override {std::lock_guard<std::mutex> guard(g_module_mutex);if (g_module != nullptr) {g_module->OnWakelockReleased(); // 调用 ActivityAttribution::OnWakelockReleased}return Status::ok();}
};void ActivityAttribution::OnWakelockAcquired() {CallOn(pimpl_.get(), &impl::on_wakelock_acquired); // 调用 impl::on_wakelock_acquired
}
- 当 suspend_control 服务 发现蓝牙 已经获取到 wakelock 后,就会回调到 btaa 中的 notifyAcquired .
- 当 suspend_control 服务 发现蓝牙 已经释放 wakelock 后,就会回调到 btaa 中的 notifyReleased .
那 蓝牙什么时候 获取 wakelock ?
当以下事情发生,系统就会调用你的回调:
-
蓝牙子系统调用 acquire_wake_lock()
acquire_wake_lock(PARTIAL_WAKE_LOCK, kBtWakelockName);
-
内核记录 wakelock 获取/释放事件
- wakelock 内核驱动位于
/sys/power/wake_lock
或使用 binder suspend control 通信 - suspend_control 服务接收到 wakelock 状态变更事件
- wakelock 内核驱动位于
-
suspend_control 通过 AIDL 通知注册的模块
- 此时就调用你注册的
notifyAcquired()
或notifyReleased()
函数
- 此时就调用你注册的
假设某个蓝牙应用准备传输数据(如文件传输、BLE 连接):
-
蓝牙堆栈(比如 hci_layer)申请 wakelock,防止 CPU 睡眠:
acquire_wake_lock(PARTIAL_WAKE_LOCK, "bluetooth");
-
suspend_control 服务被通知 wakelock 被申请:
- 查找是否有注册的
WakelockCallback
- 调用你注册的
notifyAcquired()
- 查找是否有注册的
-
你的 BTAA 模块中
wakelock_processor_
开始计时:
wakelock_processor_.OnWakelockAcquired();
-
任务完成后,蓝牙释放 wakelock:
release_wake_lock("bluetooth");
-
suspend_control 服务通知你回调
notifyReleased()
-
BTAA 模块记录 wakelock 持有时长:
auto duration = wakelock_processor_.OnWakelockReleased(); attribution_processor_.OnWakelockReleased(duration);
acquire_wake_lock("bluetooth") → 内核 wakelock 层↓suspend_control 服务监听 wakelock 变化↓registerWakelockCallback() 中注册的 callback 被触发↓wakelock_callback::notifyAcquired() / notifyReleased()↓ActivityAttribution → WakelockProcessor / AttributionProcessor
组件 | 作用 |
---|---|
suspend_control | 管理 wakelock 生命周期,支持挂起 |
wakelock_callback | 被注册到 suspend_control,用于接收 wakelock 事件 |
notifyAcquired() | 当蓝牙获取 wakelock 时触发 |
notifyReleased() | 当蓝牙释放 wakelock 时触发 |
WakelockProcessor | 记录 wakelock 起止时间 |
AttributionProcessor | 用于生成统计数据,便于 dumpsys 输出或分析 |
5. on_wakeup
void on_wakeup() {attribution_processor_.OnWakeup();}
1. 如何触发调用的
wakeup_callback 同样也是在ActivityAttribution::impl 构造函数 中, 我们向 suspend_control 服务注册了 wakeup_callback
struct wakeup_callback : public BnSuspendCallback {wakeup_callback() {}Status notifyWakeup(bool success, const std::vector<std::string>& wakeup_reasons) override {for (auto& wakeup_reason : wakeup_reasons) {if (wakeup_reason.find(kBtWakeupReason) != std::string::npos) {std::lock_guard<std::mutex> guard(g_module_mutex);if (g_module != nullptr) {g_module->OnWakeup(); // 调用ActivityAttribution::OnWakeup}break;}}return Status::ok();}
};
- wakeup_callback 继承了 BnSuspendCallback 实现了 system/hardware/interfaces/suspend/aidl/android/system/suspend/ISuspendCallback.aidl 接口
interface ISuspendCallback
{/*** An implementation of ISuspendControlService must call notifyWakeup after every system wakeup.** @param success whether previous system suspend attempt was successful.*/void notifyWakeup(boolean success, in @utf8InCpp String[] wakeupReasons);
}
- 作用是监听系统从 suspend(休眠)状态唤醒时的原因,并把唤醒原因传给蓝牙 BTAA 模块。
系统进入休眠↓
有唤醒事件(如 BT、中断、alarm)↓
suspend_control 检测到唤醒,解析 /sys/kernel/wakeup_reasons/last_resume_reason↓
回调 notifyWakeup(success, reasons) 给所有注册者(如蓝牙模块)↓
蓝牙 BTAA 中 wakeup_callback 被调用↓
g_module->OnWakeup() → AttributionProcessor::OnWakeup()
调用发生在 系统刚从休眠状态唤醒 的时候。
例如以下场景都可能触发它:
场景 | 是否触发 notifyWakeup() | 备注 |
---|---|---|
蓝牙来电 / BLE 广播 | ✅ 有蓝牙中断唤醒 | |
AlarmManager 定时器唤醒 | ❌(通常 BT 不感知) | |
用户按电源键唤醒 | ❌(非 BT 原因) | |
蓝牙耳机按键唤醒手机 | ✅ 会触发 BT wakeup | |
蓝牙自动重连事件 | ✅(需要 wakeup 支持) |
void ActivityAttribution::OnWakeup() {CallOn(pimpl_.get(), &impl::on_wakeup);
}
6. register_callback
void register_callback(ActivityAttributionCallback* callback) {callback_ = callback;}
- 用于从外部注册一个 UI 或 log 系统回调,用于把数据送出去(比如 UI 层用来展示统计信息)
7. notify_activity_attribution_info
void notify_activity_attribution_info(int uid, const std::string& package_name, const std::string& device_address) {attribution_processor_.NotifyActivityAttributionInfo(uid, package_name, device_address);}
外部(通常是系统蓝牙模块)主动提供一些上下文信息,如 UID、包名、设备地址,用于给后续数据包做归因。
关键用途:提前“绑定关系”,后面数据包才能归到正确 App。
相关文章:
【android bluetooth 框架分析 02】【Module详解 4】【Btaa 模块介绍】
1. 背景 我们在上一篇文章中介绍 HciHal 模块时,有如下代码 // system/gd/hal/hci_hal_android_hidl.ccvoid ListDependencies(ModuleList* list) const {list->add<SnoopLogger>();if (common::init_flags::btaa_hci_is_enabled()) {list->add<ac…...
gitee新的仓库,Vscode创建新的分支详细步骤
第一步点击创建分支输入新分支的名字 第二步 第三步 第四步...
OpenHarmony - 小型系统内核(LiteOS-A)(五)
OpenHarmony - 小型系统内核(LiteOS-A)(五) 六、文件系统 虚拟文件系统 基本概念 VFS(Virtual File System)是文件系统的虚拟层,它不是一个实际的文件系统,而是一个异构文件系统之…...
Unity动态合批(Dynamic Batching)解析
什么是动态合批? 动态合批是Unity引擎的一项核心优化技术,用于减少绘制调用(Draw Calls)数量,提高游戏性能。它通过将多个使用相同材质的小型可移动物体的渲染操作合并为单个绘制调用,减轻CPU向GPU发送命令…...
【Python】迭代器(Iterator)vs 生成器(Generator)
迭代器(Iterator) vs 生成器(Generator) 1.迭代器(Iterator)1.1 是什么?1.2 示例1.3 适用场景 2.生成器(Generator)2.1 是什么?2.2 示例2.3 适用场景 3.迭代器…...
el-input 限制只能输入负数、正数或2位小数的数值
需求 el-input需要指定输入格式,当键盘事件触发时限制只能输入负数、正数或2位小数的数值。 解决方案 自定义校验数字输入的键盘事件方法函数。 具体实现步骤 1、创建验数字输入的键盘事件方法 /*** 校验数字输入的键盘事件* param {Event} event - 键盘事件对…...
对话框类别组件编写
形如如图所示的对话框的编写 一、基本组件的定义 <template><div><el-dialogclass"cust-dialog":title"title":model-value"show":show-close"showClose":top"toppx":width"widthpx":close-on-…...
ICMAN防水触摸芯片 - 复杂环境下精准交互,提升触控体验
▍核心优势 ◆ 超强抗干扰能力 ◆ 工业级设计,一致性和稳定性好 ▍提供场景化解决方案 【智能厨电矩阵】抽油烟机档位调节 | 电磁炉火力触控 | 洗碗机模式切换 【卫浴设备方案】淋浴房雾化玻璃控制 | 智能马桶触控面板 | 浴缸水位感应 【工业控制应用】仪器仪…...
深度剖析:生成式人工智能备案和登记的关键差异
在人工智能技术日新月异的当下,生成式人工智能以前所未有的态势广泛渗透至各个领域,从内容创作到智能客服,从图像生成到数据分析,其应用场景正呈指数级拓展。2024 年,网信部门协同相关部门,依据《生成式人工…...
kotlin + spirngboot3 + spring security6 配置登录与JWT
1. 导包 implementation("com.auth0:java-jwt:3.14.0") implementation("org.springframework.boot:spring-boot-starter-security")配置用户实体类 Entity Table(name "users") data class User(IdGeneratedValue(strategy GenerationType.I…...
d3.js绘制组合PCA边缘分布图
用d3.js研发了个组合PCA边缘分布图; 组合PCA边缘分布图中包括pca散点图、散点图可根据数据自动分为连续型和离散型、还有散点的各种配置、边缘有箱线边缘、密度边缘、柱状边缘一个各个边缘的配置等等,大部分你能想到的配置都是自行传参调整的࿰…...
开源语音合成模型SparkTTS使用
一、环境配置 git clone https://github.com/SparkAudio/Spark-TTS.git pip install -r requirements.txt 二、模型下载 从modelscope进行下载,pip install modelscope 创建一个download.py import torchfrom modelscope import snapshot_downloadsnapshot_dow…...
课程9. 数据降维
课程9. 数据降维 维度灾难奇异值分解SVD 变换SVD 的几何意义 SVD分解应用示例图像压缩文本分析推荐系统中的应用* 主成分分析PCA演示使用 PCA 降低多元数据的维数PCA 说明单词的语义相似性 t-SNE 维度灾难 机器学习和数据科学中的关键问题之一是数据高维性问题。我们已经遇到过…...
24-25【动手学深度学习】AlexNet + Vgg
1. AlexNet 1.1 原理 1.2 代码 import torch from torch import nn from d2l import torch as d2lnet nn.Sequential(nn.Conv2d(1, 96, kernel_size11,stride4, padding1), nn.ReLU(),nn.MaxPool2d(kernel_size3, stride2),nn.Conv2d(96, 256, kernel_size5, padding2), nn.…...
1.Axum 与 Tokio:异步编程的完美结合
摘要 深入解析 Axum 核心架构与 Tokio 异步运行时的集成,掌握关键原理与实践技巧。 一、引言 在当今的软件开发领域,高并发和高性能是衡量一个系统优劣的重要指标。对于 Web 服务器而言,能够高效地处理大量并发请求是至关重要的。Rust 语言…...
快速认识:数据库、数仓(数据仓库)、数据湖与数据运河
数据技术核心概念对比表 概念核心定义核心功能数据特征典型技术/工具核心应用场景数据库结构化数据的「电子档案柜」,按固定 schema 存储和管理数据,支持高效读写和事务处理。实时事务处理(增删改查),确保数据一致性&…...
【Linux】第十章 配置和保护SSH
1. 简单说下ssh如何实现用户的免密登录? (1)生成公钥和私钥:使用 ssh-keygen -t rsa 命令,在客户端(即你登录的机器)上生成一对密钥——公钥(~/.ssh/id_rsa.pub)和私钥&…...
量子计算:开启未来科技之门的钥匙
在当今科技飞速发展的时代,量子计算正逐渐从实验室走向实际应用,成为全球科技领域的焦点之一。它有望为众多行业带来前所未有的变革,从密码学、药物研发到金融风险评估等,量子计算的潜力不可限量。 一、量子计算的原理 量子计算基…...
基础知识 - 结构体
1、结构体类型与结构体变量 1.1 结构体的定义 结构体是一种自定义的数据类型,它把多个不同类型的变量封装在一起,形成一个新的复合数据类型。可以定义该结构体类型的变量,与使用 int 定义变量的方法相同 结构体是一些值的集合,这…...
uniapp上传图片时(可选微信头像、相册、拍照)
参考文献:微信小程序登录——头像_onchooseavatar-CSDN博客 <button open-type"chooseAvatar" chooseavatar"onChooseAvatar"> </button>onChooseAvatar(e) {uni.showLoading({title: 上传中...,mask: true});uni.uploadFile({url…...
2025年4月16日华为笔试第二题200分
📌 点击直达笔试专栏 👉《大厂笔试突围》 💻 春秋招笔试突围在线OJ 👉 笔试突围OJ 02. 智慧旅游路线规划 问题描述 LYA正在开发一款智慧旅游APP,该APP需要为游客规划城市景点之间的最佳路线。城市有 N N...
面试题之高频面试题
最近开始面试了,410面试了一家公司 针对自己薄弱的面试题库,深入了解下,也应付下面试。在这里先祝愿大家在现有公司好好沉淀,定位好自己的目标,在自己的领域上发光发热,在自己想要的领域上(技术…...
一路磕磕绊绊解决flutter doctor 报错CocoaPods not installed
flutter doctor执行之后,出现以下错误: 错误消息: ✗ CocoaPods not installed.CocoaPods is a package manager for iOS or macOS platform code.Without CocoaPods, plugins will not work on iOS or macOS.For more info, see https://flutter.dev/t…...
探寻Gson解析遇到不存在键值时引发的Kotlin的空指针异常的原因
文章目录 一、问题背景二、问题原因三、问题探析Kotlin空指针校验Gson.fromJson(String json, Class<T> classOfT)TypeTokenGson.fromJson(JsonReader reader, TypeToken<T> typeOfT)TypeAdapter 和 TypeAdapterFactoryReflectiveTypeAdapterFactoryRecordAdapter …...
面试算法高频08-动态规划-01
动态规划 递归知识要点 递归代码模板:提供递归代码的标准形式public void recur(int level, int param) ,包含终止条件(if (level> MAX_LEVEL))、当前层逻辑处理(process(level, param))、向下一层递归…...
【AI】以Llama模型为例学习如何进行LLM模型微调
以Llama模型为例学习如何进行LLM模型微调 推荐超级课程: 本地离线DeepSeek AI方案部署实战教程【完全版】Docker快速入门到精通Kubernetes入门到大师通关课AWS云服务快速入门实战目录 以Llama模型为例学习如何进行LLM模型微调背景预训练微调全部微调参数高效微调低秩适配 (LoR…...
细说STM32单片机FreeRTOS任务管理API函数vTaskList()的使用方法
目录 一、函数vTaskList() 1、 函数说明 2、返回的字符串表格说明 3、函数的使用方法 二、 vTaskList()的应用示例 1、示例功能、项目设置 2、软件设计 (1)main.c (2)freertos.c (3)FreeRTOSConf…...
ffmpeg 添加 nvenc支持
运行以下命令检查当前 FFmpeg 是否支持 hevc_nvenc: ffmpeg -hide_banner -encoders | grep nvenc 若输出包含 hevc_nvenc,说明编码器已集成,问题出在驱动或参数配置若无输出,则需要手动编译 ffmpeg 安装显卡驱动、cuda和cudnn…...
锚定效应的应用-独立站优化价格打折显示-《认知偏差手册》
锚定效应的应用-独立站优化价格打折显示-《认知偏差手册》 先看结果:价格展示 https://atemplate.com/pricing 旧的打折价格展示 新的打折价格展示 锚定效应是什么? 人类在进行决策时,会过度偏重先前取得的资讯(这称为锚点&…...
红宝书第四十九讲:XSS/CSRF攻击防御策略解析
红宝书第四十九讲:XSS/CSRF攻击防御策略解析 资料取自《JavaScript高级程序设计(第5版)》。 查看总目录:红宝书学习大纲 XSS(跨站脚本):黑客把恶意代码塞进网页,当你打开页面时&am…...
Unity基于屏幕空间的鼠标拖动,拖动物体旋转
代码的核心在于,鼠标的屏幕偏移映射到物体的旋转角度,代码中是使用射线去检测的,检测帧间隔鼠标的位置对应物体上的旋转 未解决的问题:旋转都是相对的,怎么去处理,鼠标拖动物体,物体不动&#…...
Unity3D 测试驱动开发(TDD)框架设计
前言 针对Unity3D测试驱动开发(TDD)框架的设计,需要结合Unity引擎特性与TDD核心原则,构建可维护、高效且与开发流程深度集成的测试体系。以下是分层次的框架设计方案: 对惹,这里有一个游戏开发交流小组&a…...
Google Mock(GMock):C++单元测试的高效模拟框架详解
标题: Google Mock(GMock):C单元测试的高效模拟框架详解 摘要: Google Mock(GMock)是C单元测试中的核心工具,能够高效隔离外部依赖并验证复杂交互逻辑。本文详细介绍了GMock的核心…...
智慧城市气象中台架构:多源天气API网关聚合方案
在开发与天气相关的应用时,获取准确的天气信息是一个关键需求。万维易源提供的“天气预报查询”API为开发者提供了一个高效、便捷的工具,可以通过简单的接口调用查询全国范围内的天气信息。本文将详细介绍如何使用该API,以及其核心功能和调用…...
vue3项目启动bug
项目场景: vue3 项目启动运行 问题描述 终端无法正常启动运行 C:/user/adminC:/user/admin> npm run dev > student_status_vue30.0.0 dev > vite原因分析: 暂无 解决方案: 在当前项目目录下运行: npx vite --host…...
逻辑回归 (Logistic Regression)
文章目录 逻辑回归 (Logistic Regression)问题的引出Sigmoid function逻辑回归的解释决策边界 (Decision boundary)逻辑回归的代价函数机器学习中代价函数的设计1. 代价函数的来源(1)从概率模型推导而来(统计学习视角)(…...
SLAM | 激光SLAM中的退化问题
在激光SLAM中,判断退化环境的核心是通过数学建模分析环境特征对位姿估计的约束能力。除了LOAM中提出的退化因子D外,还存在多种基于表达式和阈值设定的方法。以下是几种典型方法及其实现原理: 1. 协方差矩阵特征值分析 原理:通过分析点云协方差矩阵的特征值分布,判断环境中…...
【已更新】2025华中杯B题数学建模网络挑战赛思路代码文章教学:校园共享单车的调度与维护问题
完整内容请看文末最后的推广群 先展示问题一代码和结果、再给出四个问题详细的模型 import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns from matplotlib.font_manager import FontPropertiesfrom matplotlib import rcParams# 设…...
[特殊字符] 基于大模型的地理领域文档中英互译自动化方案
一、📌 项目背景与挑战 在全球化商业环境中,跨国企业经常面临专业文档翻译的痛点: 传统方式效率低下:专业文档翻译需要专人耗时数小时甚至数天 专业术语准确性难保证:地理领域术语的特殊性 格式保持困难:…...
破局遗留系统!AI自动化重构:从静态方法到Spring Bean注入实战
在当今快速发展的软件行业中,许多企业都面临着 Java 遗留系统的维护和升级难题。这些老旧系统往往采用了大量静态方法,随着业务的不断发展,其局限性日益凸显。而飞算 JavaAI 作为一款强大的 AI 工具,为 Java 遗留系统的重构提供了全新的解决方案,能够实现从静态方法到 Spring B…...
高度图(Heightmap)
高度图的数学组成与建模方法 高度图(Heightmap)是一种基于规则网格的地形表示方法,其数学本质是将三维地形简化为二维离散函数,通过高度值的存储和插值实现地形重建。以下从数学建模角度系统阐述其组成原理及关键技术。 一、基础…...
2025第十七届“华中杯”大学生数学建模挑战赛题目B 题 校园共享单车的调度与维护问题完整思路 模型 代码 结果分享
共享单车目前已成为不少大学校园内学生的重要通勤工具,给学生的出行带来了极大便利,但同时也产生了一些问题,如共享单车投放点位设计不合理,高峰期运力不足等。 某高校委托一公司在校园内投放了一批共享单车,经过一段时…...
ESP32-idf学习(一)搭建环境和点灯
一、前言 先说一下查到的数据(不保证准确): 1、连续四年Wi-Fi MCU全球市场份额第一,产品应用于智能家居、工业自动化、医疗健康等泛IoT领域,2024 年营收突破 20 亿元(同比 40%),…...
超详细VMware虚拟机扩容磁盘容量-无坑版
1.环境: 虚拟机:VMware Workstation 17 Pro-17.5.2 Linux系统:Ubuntu 22.04 LTS 2.硬盘容量 虚拟机当前硬盘容量180G -> 扩展至 300G 3.操作步骤 (1)在虚拟机关机的状态下,虚拟机硬盘扩容之前必…...
多线程(进阶续~)(内涵面试题)
目录 一、JUC 的常见类 1. Callable 接口 2. ReentrantLock ReentrantLock 的用法: ReentrantLock 和 synchronized 的区别: 何时使用何锁: 3. 原子类 4. 线程池 ExecutorService 和 Executors ThreadPoolExecutor 5. 信号量 Semaphore 6. C…...
OpenGL shader开发实战学习笔记:第十一章 立方体贴图和天空盒
1. 立方体贴图和天空盒 1.1. 什么是立方体贴图 立方体贴图(Cube Map)是一种纹理,它由六个纹理图像组成,每个纹理图像对应一个方向。这些方向通常是立方体的六个面,分别是“前面”,“后面”,“…...
双指针算法(二)
目录 一、力扣611——有效三角形的个数 二、牛客网3734——和为S的两个数字 三、力扣15——三数之和 四、力扣18——四数之和 一、力扣611——有效三角形的个数 题目如下: 这里我们先认识如何判断是个三角形,ab>c,ac>b,bc>a即为三角形 这里…...
docker Windows 存放位置
docker Windows 存放位置 镜像文件层可能是这 docker的overlay2中存的都是什么and如何清理/var/lib/docker/overlay2_docker overlay 是什么目录-CSDN博客 存的是我们的镜像文件和容器内的文件 \\wsl.localhost\docker-desktop\mnt\docker-desktop-disk\data\docker\overla…...
每日一题(小白)暴力娱乐篇31
首先分析一下题意,需要求出2024的因子,因为我们要求与2024互质的数字,为什么呢?因为我们要求互质说直白点就是我和你两个人没有中间人,我们是自然而然认识的,那我们怎么认识呢,就是直接见面对吧…...
FastAPI与SQLAlchemy数据库集成
title: FastAPI与SQLAlchemy数据库集成 date: 2025/04/17 15:33:34 updated: 2025/04/17 15:33:34 author: cmdragon excerpt: FastAPI与SQLAlchemy的集成通过创建虚拟环境、安装依赖、配置数据库连接、定义数据模型和实现路由来完成。核心模块包括数据库引擎、会话工厂和声…...