【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…...
“星睿O6” AI PC开发套件评测 - Windows on Arm 安装指南和性能测评
引言 Radxa联合此芯科技和安谋科技推出全新的"星睿O6"迷你 ITX 主板。该系统搭载了 CIX P1(CD8180)12 核 Armv9 处理器,拥有高达30T算力的NPU和高性能的GPU,最高配备64GB LPDDR内存,并提供了如 5GbE、HDMI …...
Python 调用 YOLOv11 ONNX
Python 调用 YOLO ONNX 1 下载ONNX文件2 Python代码 1 下载ONNX文件 ONNX下载地址 2 Python代码 import cv2 from ultralytics import YOLOdef check(yolo:str, path:str):# 加载 YOLOv11model YOLO(yolo)# 读取图片img cv2.imread(path)# 推理(可以传文件路径…...
数据通信学习笔记之OSPF路由汇总
区域间路由汇总 路由汇总又被称为路由聚合,即是将一组前缀相同的路由汇聚成一条路由,从而达到减小路由表规模以及优化设备资源利用率的目的,我们把汇聚之前的这组路由称为精细路由或明细路由,把汇聚之后的这条路由称为汇总路由或…...
ASP.NET Core Web API 配置系统集成
文章目录 前言一、配置源与默认设置二、使用步骤1)创建项目并添加配置2)配置文件3)强类型配置类4)配置Program.cs5)控制器中使用配置6)配置优先级测试7)动态重载配置测试8)运行结果示…...
如何判断单片机性能极限?
目录 1、CPU 负载 2、内存使用情况 3、实时性能 4、外设带宽 5、功耗与温度 在嵌入式系统设计中,当系统变得复杂、功能增加时,单片机可能会逐渐逼近其性能极限。及时识别这些极限点对于保证产品质量、稳定性和用户体验至关重要。 当你的嵌入式系统…...
AI在多Agent协同领域的核心概念、技术方法、应用场景及挑战 的详细解析
以下是 AI在多Agent协同领域的核心概念、技术方法、应用场景及挑战 的详细解析: 1. 多Agent协同的定义与核心目标 多Agent系统(MAS, Multi-Agent System): 由多个独立或协作的智能体(Agent)组成ÿ…...
1.凸包、极点、极边基础概念
目录 1.凸包 2.调色问题 3.极性(Extrem) 4.凸组合(Convex Combination) 5.问题转化(Strategy)编辑 6.In-Triangle test 7.To-Left-test 8.极边(Extream Edges) 1.凸包 凸包就是上面蓝色皮筋围出来的范围 这些钉子可以转换到坐标轴中࿰…...
OSCP - Proving Grounds - DriftingBlues6
主要知识点 路径爆破dirtycow内核漏洞提权 具体步骤 总体来讲,这台靶机还是比较直接的,没有那么多的陷阱,非常适合用来学习 依旧是nmap开始,只开放了80端口 Nmap scan report for 192.168.192.219 Host is up (0.42s latency). Not shown: 65534 cl…...
深度理解指针之例题
文章目录 前言题目分析与讲解涉及知识点 前言 对指针有一定了解后,讲一下一道初学者的易错题 题目分析与讲解 先定义一个数组跟一个指针变量 然后把数组名赋值给指针变量————也就是把首地址传到pulPtr中 重点是分析这一句: *(pulPtr…...
java 设计模式之策略模式
简介 策略模式:策略模式可以定制目标对象的行为,它尅通过传入不同的策略实现,来配置目标对象的行为。使用策略模式,就是为了定制目标对象在某个关键点的行为。 策略模式中的角色: 上下文类:持有一个策略…...
LeetCode算法题(Go语言实现)_51
题目 给你两个下标从 0 开始的整数数组 nums1 和 nums2 ,两者长度都是 n ,再给你一个正整数 k 。你必须从 nums1 中选一个长度为 k 的 子序列 对应的下标。 对于选择的下标 i0 ,i1 ,…, ik - 1 ,你的 分数 …...
条件变量condition_variable
1.条件变量用来控制线程同步和协调。 2.调用wait方法,如果条件满足就不挂起,如果条件不满足就挂起线程并释放锁。 3.等到通知后,挂起线程先 竞争锁,然后 判断条件,如果满足条件就往下走,如果不满足就再次挂起并释放锁…...
Solon AI MCP Server 入门:Helloworld (支持 java8 到 java24。国产解决方案)
目前网上能看到的 MCP Server 基本上都是基于 Python 或者 nodejs ,虽然也有 Java 版本的 MCP SDK,但是鲜有基于 Java 开发的。 作为Java 开发中的国产顶级框架 Solon 已经基于 MCP SDK 在进行 Solon AI MCP 框架开发了,本文将使用 Solon AI …...
Maven工具学习使用(十一)——部署项目到仓库
1、使用Maven默认方式 Maven 部署项目时默认使用的上传文件方式是通过 HTTP/HTTPS 协议。要在 Maven 项目中配置部署,您需要在项目的 pom.xml 文件中添加 部分。这个部分定义了如何部署项目的构件(如 JAR 文件)到仓库。。这个部分定义了如何…...
公司内部自建知识共享的方式分类、详细步骤及表格总结,分为开源(对外公开)和闭源(仅限内部),以及公共(全员可访问)和内部(特定团队/项目组)四个维度
以下是公司内部自建知识共享的方式分类、详细步骤及表格总结,分为开源(对外公开)和闭源(仅限内部),以及公共(全员可访问)和内部(特定团队/项目组)四个维度&am…...
Oracle 19c部署之初始化实例(三)
上一篇文章中,我们已经完成了数据库软件安装,接下来我们需要进行实例初始化工作。 一、初始化实例的两种方式 1.1 图形化初始化实例 描述:图形化初始化实例是通过Oracle的Database Configuration Assistant (DBCA)工具完成的。用户通过一系…...
医疗设备预测性维护合规架构:从法规遵循到技术实现的深度解析
在医疗行业数字化转型加速推进的当下,医疗设备预测性维护已成为提升设备可用性、保障医疗安全的核心技术。然而,该技术的有效落地必须建立在严格的合规框架之上。医疗设备直接关乎患者生命健康,其维护过程涉及医疗法规、数据安全、质量管控等…...
Openfeign的最佳实践
文章目录 问题引入一、继承的方式1. 建立独立的Moudle服务2. 服务调用方继承jar包中的接口3. 直接注入继承后的接口进行使用 二、抽取的方式1. 建立独立的Moudle服务2.服务调用方依赖注入 问题引入 openfeign接口的实现和服务提供方的controller非常相似,例如&…...
Python中如何加密/解密敏感信息(如用户密码、token)
敏感信息,如用户密码、API密钥、访问令牌(token)、信用卡号以及其他个人身份信息(PII),构成了现代应用程序和系统中最为关键的部分。这些信息一旦被未经授权的第三方获取,可能引发灾难性的后果,从个人隐私泄露到企业经济损失,甚至是大规模的社会安全问题。保护这些敏感…...
【Java面试系列】Spring Cloud微服务架构中的分布式事务解决方案与Seata框架实现原理详解 - 3-5年Java开发必备知识
【Java面试系列】Spring Cloud微服务架构中的分布式事务解决方案与Seata框架实现原理详解 - 3-5年Java开发必备知识 引言 在微服务架构中,分布式事务是一个不可避免的挑战。随着业务复杂度的提升,如何保证跨服务的数据一致性成为了面试中的高频问题。本…...
从万维网到人工智能基石:大数据技术三十年演进史(1991-2025)
一、万维网的创世纪(1991) 1.1 信息共享的革命性突破 1991年8月6日,蒂姆伯纳斯-李在欧洲核子研究中心(CERN)发布首个万维网(World Wide Web)网站,构建了信息互联的三项核心技术&…...
Buildroot编译过程中下载源码失败
RK3588编译一下recovery,需要把buildroot源码编译一遍。遇到好几个文件都下载失败,如下所示 pm-utils 1.4.1这个包下载失败,下载地址http://pm-utils.freedesktop.org/releases 解决办法,换个网络用windows浏览器下载后ÿ…...
【Rust基础】crossbeam带来的阻塞问题
背景 最近正在做AI知识库的相关内容,web框架使用Rocket,需要使用SSE处理模型的流式输出,而Rocket的SSE功能比较单一,没有进行全局状态管理,因此需要手动处理SSE连接,而对于web环境下,必然会涉及…...
OpenCV 图形API(43)颜色空间转换-----将 BGR 图像转换为 LUV 色彩空间函数BGR2LUV()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 将图像从BGR色彩空间转换为LUV色彩空间。 该函数将输入图像从BGR色彩空间转换为LUV。B、G和R通道值的传统范围是0到255。 输出图像必须是8位无符…...
自问自答模式(Operation是什么)
自问自答 问:Operation 注解来自哪里? 答:Operation 是 OpenAPI(Swagger)规范中,来自 io.swagger.v3.oas.annotations 包的一个注解,用于给 REST 接口增加文档元数据。 问:summary …...
996引擎-实战笔记:Lua 的 NPC 面板获取 Input 内容
996引擎-实战笔记:Lua 的 NPC 面板获取 Input 内容 获取 Input 内容测试NPC参考资料获取 Input 内容 测试NPC -- NPC入口函数 function main(player)local msg = [[<Img|id=9527|x=0|y=0|width=300|height=150|img=public/bg_npc_01.png|bg=1|move=1|reset=1|show=0|layer…...
少数服从多数悖论、黑白颠倒与众人孤立现象之如何应对(一)
观己之前,也可先观众生 如果当时没有袖手旁观,或许唇不亡齿也不会寒 ■如何轻松/更好应对个别被众人孤立(他人、辨别、自己) ●他人被孤立 不参与 有余力,助弱者 被孤立者本身有问题 •不参与:不会辨…...
leetcode0058. 最后一个单词的长度-easy
1 题目:最后一个单词的长度 官方标定难度:易 给你一个字符串 s,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。 单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。 示例 1&#x…...
新一代电子海图S-100标准
随着航海技术的不断发展,国际海事组织(IMO)和国际航道测量组织(IHO)不断推动电子海图标准的更新,以提高航行安全和效率。S-100标准作为新一代电子海图标准,为电子海图显示和信息系统(…...
