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

【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 都要执行那下面几步:

  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);
    • 我们在 HciHal 模块中 依赖 Btaa, 所以这里将会触发 Btaa 模块的加载, 跳到第1步开始加载 Btaa
  4. 最后调用自己的 Start() 函数

    • instance->Start();
  5. 将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 模块启动流程

  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);
    • 我们在 HciHal 模块中 依赖 Btaa, 所以这里将会触发 Btaa 模块的加载, 跳到第1步开始加载 Btaa
  4. 最后调用自己的 Start() 函数

    • instance->Start();
  5. 将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::implActivityAttribution 的实现部分,负责:

  • 注册唤醒和 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 ?
当以下事情发生,系统就会调用你的回调:

  1. 蓝牙子系统调用 acquire_wake_lock()
    acquire_wake_lock(PARTIAL_WAKE_LOCK, kBtWakelockName);

  2. 内核记录 wakelock 获取/释放事件

    • wakelock 内核驱动位于 /sys/power/wake_lock 或使用 binder suspend control 通信
    • suspend_control 服务接收到 wakelock 状态变更事件
  3. suspend_control 通过 AIDL 通知注册的模块

    • 此时就调用你注册的 notifyAcquired()notifyReleased() 函数

假设某个蓝牙应用准备传输数据(如文件传输、BLE 连接):

  1. 蓝牙堆栈(比如 hci_layer)申请 wakelock,防止 CPU 睡眠:
    acquire_wake_lock(PARTIAL_WAKE_LOCK, "bluetooth");

  2. suspend_control 服务被通知 wakelock 被申请:

    • 查找是否有注册的 WakelockCallback
    • 调用你注册的 notifyAcquired()
  3. 你的 BTAA 模块中 wakelock_processor_ 开始计时:
    wakelock_processor_.OnWakelockAcquired();

  4. 任务完成后,蓝牙释放 wakelock:
    release_wake_lock("bluetooth");

  5. suspend_control 服务通知你回调 notifyReleased()

  6. 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 模块时&#xff0c;有如下代码 // 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&#xff08;CD8180&#xff09;12 核 Armv9 处理器&#xff0c;拥有高达30T算力的NPU和高性能的GPU&#xff0c;最高配备64GB LPDDR内存&#xff0c;并提供了如 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)# 推理&#xff08;可以传文件路径…...

数据通信学习笔记之OSPF路由汇总

区域间路由汇总 路由汇总又被称为路由聚合&#xff0c;即是将一组前缀相同的路由汇聚成一条路由&#xff0c;从而达到减小路由表规模以及优化设备资源利用率的目的&#xff0c;我们把汇聚之前的这组路由称为精细路由或明细路由&#xff0c;把汇聚之后的这条路由称为汇总路由或…...

ASP.NET Core Web API 配置系统集成

文章目录 前言一、配置源与默认设置二、使用步骤1&#xff09;创建项目并添加配置2&#xff09;配置文件3&#xff09;强类型配置类4&#xff09;配置Program.cs5&#xff09;控制器中使用配置6&#xff09;配置优先级测试7&#xff09;动态重载配置测试8&#xff09;运行结果示…...

如何判断单片机性能极限?

目录 1、CPU 负载 2、内存使用情况 3、实时性能 4、外设带宽 5、功耗与温度 在嵌入式系统设计中&#xff0c;当系统变得复杂、功能增加时&#xff0c;单片机可能会逐渐逼近其性能极限。及时识别这些极限点对于保证产品质量、稳定性和用户体验至关重要。 当你的嵌入式系统…...

AI在多Agent协同领域的核心概念、技术方法、应用场景及挑战 的详细解析

以下是 AI在多Agent协同领域的核心概念、技术方法、应用场景及挑战 的详细解析&#xff1a; 1. 多Agent协同的定义与核心目标 多Agent系统&#xff08;MAS, Multi-Agent System&#xff09;&#xff1a; 由多个独立或协作的智能体&#xff08;Agent&#xff09;组成&#xff…...

1.凸包、极点、极边基础概念

目录 1.凸包 2.调色问题 3.极性(Extrem) 4.凸组合(Convex Combination) 5.问题转化(Strategy)​编辑 6.In-Triangle test 7.To-Left-test 8.极边&#xff08;Extream Edges&#xff09; 1.凸包 凸包就是上面蓝色皮筋围出来的范围 这些钉子可以转换到坐标轴中&#xff0…...

OSCP - Proving Grounds - DriftingBlues6

主要知识点 路径爆破dirtycow内核漏洞提权 具体步骤 总体来讲&#xff0c;这台靶机还是比较直接的&#xff0c;没有那么多的陷阱,非常适合用来学习 依旧是nmap开始,只开放了80端口 Nmap scan report for 192.168.192.219 Host is up (0.42s latency). Not shown: 65534 cl…...

深度理解指针之例题

文章目录 前言题目分析与讲解涉及知识点 前言 对指针有一定了解后&#xff0c;讲一下一道初学者的易错题 题目分析与讲解 先定义一个数组跟一个指针变量 然后把数组名赋值给指针变量————也就是把首地址传到pulPtr中 重点是分析这一句&#xff1a; *&#xff08;pulPtr…...

java 设计模式之策略模式

简介 策略模式&#xff1a;策略模式可以定制目标对象的行为&#xff0c;它尅通过传入不同的策略实现&#xff0c;来配置目标对象的行为。使用策略模式&#xff0c;就是为了定制目标对象在某个关键点的行为。 策略模式中的角色&#xff1a; 上下文类&#xff1a;持有一个策略…...

LeetCode算法题(Go语言实现)_51

题目 给你两个下标从 0 开始的整数数组 nums1 和 nums2 &#xff0c;两者长度都是 n &#xff0c;再给你一个正整数 k 。你必须从 nums1 中选一个长度为 k 的 子序列 对应的下标。 对于选择的下标 i0 &#xff0c;i1 &#xff0c;…&#xff0c; ik - 1 &#xff0c;你的 分数 …...

条件变量condition_variable

1.条件变量用来控制线程同步和协调。 2.调用wait方法&#xff0c;如果条件满足就不挂起&#xff0c;如果条件不满足就挂起线程并释放锁。 3.等到通知后&#xff0c;挂起线程先 竞争锁&#xff0c;然后 判断条件,如果满足条件就往下走&#xff0c;如果不满足就再次挂起并释放锁…...

Solon AI MCP Server 入门:Helloworld (支持 java8 到 java24。国产解决方案)

目前网上能看到的 MCP Server 基本上都是基于 Python 或者 nodejs &#xff0c;虽然也有 Java 版本的 MCP SDK&#xff0c;但是鲜有基于 Java 开发的。 作为Java 开发中的国产顶级框架 Solon 已经基于 MCP SDK 在进行 Solon AI MCP 框架开发了&#xff0c;本文将使用 Solon AI …...

Maven工具学习使用(十一)——部署项目到仓库

1、使用Maven默认方式 Maven 部署项目时默认使用的上传文件方式是通过 HTTP/HTTPS 协议。要在 Maven 项目中配置部署&#xff0c;您需要在项目的 pom.xml 文件中添加 部分。这个部分定义了如何部署项目的构件&#xff08;如 JAR 文件&#xff09;到仓库。。这个部分定义了如何…...

公司内部自建知识共享的方式分类、详细步骤及表格总结,分为开源(对外公开)和闭源(仅限内部),以及公共(全员可访问)和内部(特定团队/项目组)四个维度

以下是公司内部自建知识共享的方式分类、详细步骤及表格总结&#xff0c;分为开源&#xff08;对外公开&#xff09;和闭源&#xff08;仅限内部&#xff09;&#xff0c;以及公共&#xff08;全员可访问&#xff09;和内部&#xff08;特定团队/项目组&#xff09;四个维度&am…...

Oracle 19c部署之初始化实例(三)

上一篇文章中&#xff0c;我们已经完成了数据库软件安装&#xff0c;接下来我们需要进行实例初始化工作。 一、初始化实例的两种方式 1.1 图形化初始化实例 描述&#xff1a;图形化初始化实例是通过Oracle的Database Configuration Assistant (DBCA)工具完成的。用户通过一系…...

医疗设备预测性维护合规架构:从法规遵循到技术实现的深度解析

在医疗行业数字化转型加速推进的当下&#xff0c;医疗设备预测性维护已成为提升设备可用性、保障医疗安全的核心技术。然而&#xff0c;该技术的有效落地必须建立在严格的合规框架之上。医疗设备直接关乎患者生命健康&#xff0c;其维护过程涉及医疗法规、数据安全、质量管控等…...

Openfeign的最佳实践

文章目录 问题引入一、继承的方式1. 建立独立的Moudle服务2. 服务调用方继承jar包中的接口3. 直接注入继承后的接口进行使用 二、抽取的方式1. 建立独立的Moudle服务2.服务调用方依赖注入 问题引入 openfeign接口的实现和服务提供方的controller非常相似&#xff0c;例如&…...

Python中如何加密/解密敏感信息(如用户密码、token)

敏感信息,如用户密码、API密钥、访问令牌(token)、信用卡号以及其他个人身份信息(PII),构成了现代应用程序和系统中最为关键的部分。这些信息一旦被未经授权的第三方获取,可能引发灾难性的后果,从个人隐私泄露到企业经济损失,甚至是大规模的社会安全问题。保护这些敏感…...

【Java面试系列】Spring Cloud微服务架构中的分布式事务解决方案与Seata框架实现原理详解 - 3-5年Java开发必备知识

【Java面试系列】Spring Cloud微服务架构中的分布式事务解决方案与Seata框架实现原理详解 - 3-5年Java开发必备知识 引言 在微服务架构中&#xff0c;分布式事务是一个不可避免的挑战。随着业务复杂度的提升&#xff0c;如何保证跨服务的数据一致性成为了面试中的高频问题。本…...

从万维网到人工智能基石:大数据技术三十年演进史(1991-2025)

一、万维网的创世纪&#xff08;1991&#xff09; 1.1 信息共享的革命性突破 1991年8月6日&#xff0c;蒂姆伯纳斯-李在欧洲核子研究中心&#xff08;CERN&#xff09;发布首个万维网&#xff08;World Wide Web&#xff09;网站&#xff0c;构建了信息互联的三项核心技术&…...

Buildroot编译过程中下载源码失败

RK3588编译一下recovery&#xff0c;需要把buildroot源码编译一遍。遇到好几个文件都下载失败&#xff0c;如下所示 pm-utils 1.4.1这个包下载失败&#xff0c;下载地址http://pm-utils.freedesktop.org/releases 解决办法&#xff0c;换个网络用windows浏览器下载后&#xff…...

【Rust基础】crossbeam带来的阻塞问题

背景 最近正在做AI知识库的相关内容&#xff0c;web框架使用Rocket&#xff0c;需要使用SSE处理模型的流式输出&#xff0c;而Rocket的SSE功能比较单一&#xff0c;没有进行全局状态管理&#xff0c;因此需要手动处理SSE连接&#xff0c;而对于web环境下&#xff0c;必然会涉及…...

OpenCV 图形API(43)颜色空间转换-----将 BGR 图像转换为 LUV 色彩空间函数BGR2LUV()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 将图像从BGR色彩空间转换为LUV色彩空间。 该函数将输入图像从BGR色彩空间转换为LUV。B、G和R通道值的传统范围是0到255。 输出图像必须是8位无符…...

自问自答模式(Operation是什么)

自问自答 问&#xff1a;Operation 注解来自哪里&#xff1f; 答&#xff1a;Operation 是 OpenAPI&#xff08;Swagger&#xff09;规范中&#xff0c;来自 io.swagger.v3.oas.annotations 包的一个注解&#xff0c;用于给 REST 接口增加文档元数据。 问&#xff1a;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…...

少数服从多数悖论、黑白颠倒与众人孤立现象之如何应对(一)

观己之前&#xff0c;也可先观众生 如果当时没有袖手旁观&#xff0c;或许唇不亡齿也不会寒 ■如何轻松/更好应对个别被众人孤立&#xff08;他人、辨别、自己&#xff09; ●他人被孤立 不参与 有余力&#xff0c;助弱者 被孤立者本身有问题 •不参与&#xff1a;不会辨…...

leetcode0058. 最后一个单词的长度-easy

1 题目&#xff1a;最后一个单词的长度 官方标定难度&#xff1a;易 给你一个字符串 s&#xff0c;由若干单词组成&#xff0c;单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。 单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。 示例 1&#x…...

新一代电子海图S-100标准

随着航海技术的不断发展&#xff0c;国际海事组织&#xff08;IMO&#xff09;和国际航道测量组织&#xff08;IHO&#xff09;不断推动电子海图标准的更新&#xff0c;以提高航行安全和效率。S-100标准作为新一代电子海图标准&#xff0c;为电子海图显示和信息系统&#xff08…...