【Bluedroid】蓝牙HID DEVICE 报告发送与电源管理源码解析
本文基于Android蓝牙协议栈代码,深度解析HID设备(如键盘、鼠标)从应用层发送输入报告到主机设备的完整流程,涵盖数据封装、通道选择、L2CAP传输、电源管理四大核心模块。通过函数调用链(send_report
→ BTA_HdSendReport
→ HID_DevSendReport
→ hidd_conn_send_data
)的逐层分析,重点剖析以下机制:
-
中断通道与控制通道的动态选择策略
-
蓝牙协议栈异步消息队列与状态机协作
-
BUSY/IDLE状态下的电源模式动态调整
-
低功耗模式(Sniff/Hold/Park)的冲突仲裁机制
一、流程概述
1.1 数据发送主流程
①应用层触发
send_report()
:接收上层输入数据,校验HID服务状态(注册/启用),将BTHD_REPORT_TYPE_INTRDATA
强制映射为中断通道输入报告。
②协议栈封装
BTA_HdSendReport()
:检查MTU大小,通过osi_malloc
分配消息缓冲区,构建tBTA_HD_SEND_REPORT
结构体,拷贝数据并投递到系统消息队列。
③事件路由
bta_hd_hdl_event()
:通过BTA_HD_API_SEND_REPORT_EVT
事件触发状态机跳转至BTA_HD_IDLE_ST
,调用动作函数bta_hd_send_report_act
。
④通道决策
bta_hd_send_report_act()
:根据use_intr
标志选择通道(HID_CHANNEL_INTR
或HID_CHANNEL_CTRL
),处理报告ID兼容性(Boot Mode适配)。
⑤物理层传输
-
HID_DevSendReport()
:验证通道与报告类型合法性(中断通道仅允许输入报告),调用hidd_conn_send_data
。 -
hidd_conn_send_data()
:封装L2CAP数据包,处理拥塞检测(HID_CONN_FLAGS_CONGESTED
),动态分配缓冲区,通过L2CA_DataWrite
发送。
1.2 电源管理协同流程
①BUSY状态标记
bta_sys_busy()
:通知电源管理模块(PPM/PRM)连接进入繁忙状态,暂停低功耗模式计时器。
②动态策略调整
bta_dm_pm_cback()
:针对BTA_SYS_CONN_BUSY
状态:
-
为A2DP音频流设置专用SSR4参数(最大延迟优化)
-
更新服务状态为
BTA_DM_PM_NEW_REQ
,重置失败计数器 -
跳过常规SSR设置,维持活跃连接状态
③模式仲裁与切换
bta_dm_pm_set_mode()
:综合各服务的电源需求(allowed_modes
),通过优先级仲裁选择最终模式:
-
冲突解决:若嗅探模式(SNIFF)被部分服务禁止,回退至PARK或保持ACTIVE
-
定时器管理:为延迟执行的模式设置分级超时(如
BTA_DM_PM_SNIFF_TIMER_3
)
④IDLE状态恢复
bta_sys_idle()
:通知系统恢复空闲状态,重新评估低功耗条件,可能触发BTA_DM_PM_SSR0
参数重置。
1.3 关键机制协作
-
音频优化:为音频子系统提供专用 SSR 参数,确保低延迟传输
-
冲突处理:当多服务对电源模式有冲突需求时,选择兼容性最高的模式
-
失败恢复:记录失败的电源模式尝试,避免重复失败
-
动态调整:根据实时连接状态和服务需求,动态调整电源策略
二、源码解析
send_report
/packages/modules/Bluetooth/system/btif/src/btif_hd.cc
/********************************************************************************* Function send_report** Description Sends Reports to hid host** Returns bt_status_t*******************************************************************************/
static bt_status_t send_report(bthd_report_type_t type, uint8_t id,uint16_t len, uint8_t* p_data) {tBTA_HD_REPORT report;log::verbose("type={} id={} len={}", type, id, len);// 状态检查if (!btif_hd_cb.app_registered) {log::warn("application not yet registered");return BT_STATUS_NOT_READY;}if (btif_hd_cb.status != BTIF_HD_ENABLED) {log::warn("BT-HD not enabled, status={}", btif_hd_cb.status);return BT_STATUS_NOT_READY;}// 报告类型处理if (type == BTHD_REPORT_TYPE_INTRDATA) {report.type = BTHD_REPORT_TYPE_INPUT;report.use_intr = TRUE;} else {report.type = (type & 0x03);report.use_intr = FALSE;}// 构建报告结构体report.id = id;report.len = len;report.p_data = p_data;// 调用底层发送函数BTA_HdSendReport(&report);return BT_STATUS_SUCCESS;
}
用于向 HID (Human Interface Device) 主机发送报告,将上层输入数据封装成标准HID报告格式并通过蓝牙传输。
BTA_HdSendReport
packages/modules/Bluetooth/system/bta/hd/bta_hd_api.cc
/********************************************************************************* Function BTA_HdSendReport** Description This function is called when report is to be sent** Returns void*******************************************************************************/
void BTA_HdSendReport(tBTA_HD_REPORT* p_report) {log::verbose("");if (p_report->len > BTA_HD_REPORT_LEN) {log::warn("report len ({}) > MTU len ({}), can't send report. Increase value of ""HID_DEV_MTU_SIZE to send larger reports",p_report->len, BTA_HD_REPORT_LEN);return;}tBTA_HD_SEND_REPORT* p_buf =(tBTA_HD_SEND_REPORT*)osi_malloc(sizeof(tBTA_HD_SEND_REPORT));p_buf->hdr.event = BTA_HD_API_SEND_REPORT_EVT;p_buf->use_intr = p_report->use_intr;p_buf->type = p_report->type;p_buf->id = p_report->id;p_buf->len = p_report->len;memcpy(p_buf->data, p_report->p_data, p_report->len);bta_sys_sendmsg(p_buf); // 发送消息到系统队列
}
将应用层的 HID 报告转换为蓝牙协议可传输的格式,并通过消息队列机制实现异步发送。
bta_hd_hdl_event(BTA_HD_API_SEND_REPORT_EVT)
packages/modules/Bluetooth/system/bta/hd/bta_hd_main.cc
/********************************************************************************* Function bta_hd_hdl_event** Description HID device main event handling function.** Returns void*******************************************************************************/
bool bta_hd_hdl_event(const BT_HDR_RIGID* p_msg) {log::verbose("p_msg->event={}", p_msg->event);switch (p_msg->event) {case BTA_HD_API_ENABLE_EVT:bta_hd_api_enable((tBTA_HD_DATA*)p_msg);break;case BTA_HD_API_DISABLE_EVT:if (bta_hd_cb.state == BTA_HD_CONN_ST) {log::warn("host connected, disconnect before disabling");// unregister (and disconnect)bta_hd_cb.disable_w4_close = TRUE;bta_hd_better_state_machine(BTA_HD_API_UNREGISTER_APP_EVT,(tBTA_HD_DATA*)p_msg);} else {bta_hd_api_disable();}break;default:bta_hd_better_state_machine(p_msg->event, (tBTA_HD_DATA*)p_msg);}return (TRUE);
}
事件被路由到 bta_hd_better_state_machine
处理。
bta_hd_better_state_machine(BTA_HD_API_SEND_REPORT_EVT)
packages/modules/Bluetooth/system/bta/hd/bta_hd_main.cc
static void bta_hd_better_state_machine(uint16_t event, tBTA_HD_DATA* p_data) {switch (get_state()) {...case BTA_HD_IDLE_ST:switch (event) {...case BTA_HD_API_SEND_REPORT_EVT:bta_hd_send_report_act(p_data);break;...
bta_hd_send_report_act
/packages/modules/Bluetooth/system/bta/hd/bta_hd_act.cc
/********************************************************************************* Function bta_hd_send_report_act** Description Sends report** Returns void*******************************************************************************/
void bta_hd_send_report_act(tBTA_HD_DATA* p_data) {tBTA_HD_SEND_REPORT* p_report = (tBTA_HD_SEND_REPORT*)p_data;uint8_t channel;uint8_t report_id;log::verbose("");// 通道选择channel = p_report->use_intr ? HID_CHANNEL_INTR : HID_CHANNEL_CTRL;report_id =(bta_hd_cb.use_report_id || bta_hd_cb.boot_mode) ? p_report->id : 0x00;HID_DevSendReport(channel, p_report->type, report_id, p_report->len,p_report->data);/* trigger PM */bta_sys_busy(BTA_ID_HD, 1, bta_hd_cb.bd_addr); // 通知系统蓝牙 HID 模块处于忙碌状态(发送数据)bta_sys_idle(BTA_ID_HD, 1, bta_hd_cb.bd_addr); // 立即通知系统模块恢复空闲状态
}
负责实际发送 HID 报告。接收之前封装的报告数据,选择适当的通道,并调用底层 HID 驱动接口完成数据传输。
根据 use_intr
标志决定使用中断通道还是控制通道
-
中断通道 (
HID_CHANNEL_INTR
):用于实时数据传输(如键盘、鼠标输入) -
控制通道 (
HID_CHANNEL_CTRL
):用于非实时控制和配置数据
HID_DevSendReport
packages/modules/Bluetooth/system/stack/hid/hidd_api.cc
/********************************************************************************* Function HID_DevSendReport** Description Sends report** Returns tHID_STATUS*******************************************************************************/
tHID_STATUS HID_DevSendReport(uint8_t channel, uint8_t type, uint8_t id,uint16_t len, uint8_t* p_data) {log::verbose("channel={} type={} id={} len={}", channel, type, id, len);if (channel == HID_CHANNEL_CTRL) {// 控制通道可用于发送任何类型的 HID 报告(输入、输出、特征)return hidd_conn_send_data(HID_CHANNEL_CTRL, HID_TRANS_DATA, type, id, len,p_data);}if (channel == HID_CHANNEL_INTR && type == HID_PAR_REP_TYPE_INPUT) {// on INTR we can only send INPUT// 中断通道仅允许发送输入报告,因为中断通道专为实时数据(如键盘、鼠标输入)设计return hidd_conn_send_data(HID_CHANNEL_INTR, HID_TRANS_DATA,HID_PAR_REP_TYPE_INPUT, id, len, p_data);}log_counter_metrics(android::bluetooth::CodePathCounterKeyEnum::HIDD_ERR_INVALID_PARAM_SEND_REPORT,1);return HID_ERR_INVALID_PARAM;
}
将 HID 报告通过指定通道发送到主机设备。
hidd_conn_send_data
/packages/modules/Bluetooth/system/stack/hid/hidd_conn.cc
/********************************************************************************* Function hidd_conn_send_data** Description Sends data to host** Returns tHID_STATUS*******************************************************************************/
tHID_STATUS hidd_conn_send_data(uint8_t channel, uint8_t msg_type,uint8_t param, uint8_t data, uint16_t len,uint8_t* p_data) {BT_HDR* p_buf;uint8_t* p_out;uint16_t cid;uint16_t buf_size;log::verbose("channel({}), msg_type({}), len({})", channel, msg_type, len);tHID_CONN* p_hcon = &hd_cb.device.conn;// 1. 拥塞检查if (p_hcon->conn_flags & HID_CONN_FLAGS_CONGESTED) {log_counter_metrics(android::bluetooth::CodePathCounterKeyEnum::HIDD_ERR_CONGESTED_AT_FLAG_CHECK,1);return HID_ERR_CONGESTED;}// 2. CID 和缓冲区大小确定// 根据消息类型和通道确定 L2CAP 通道 ID (CID)// 为控制和中断通道分配不同的缓冲区大小switch (msg_type) {case HID_TRANS_HANDSHAKE:case HID_TRANS_CONTROL:cid = p_hcon->ctrl_cid;buf_size = HID_CONTROL_BUF_SIZE;break;case HID_TRANS_DATA:if (channel == HID_CHANNEL_CTRL) {cid = p_hcon->ctrl_cid;buf_size = HID_CONTROL_BUF_SIZE;} else {cid = p_hcon->intr_cid;buf_size = HID_INTERRUPT_BUF_SIZE;}break;default:log_counter_metrics(android::bluetooth::CodePathCounterKeyEnum::HIDD_ERR_INVALID_PARAM,1);return (HID_ERR_INVALID_PARAM);}// 3. 内存分配与数据封装p_buf = (BT_HDR*)osi_malloc(buf_size);if (p_buf == NULL) {log_counter_metrics(android::bluetooth::CodePathCounterKeyEnum::HIDD_ERR_NO_RESOURCES, 1);return (HID_ERR_NO_RESOURCES);}p_buf->offset = L2CAP_MIN_OFFSET;p_out = (uint8_t*)(p_buf + 1) + p_buf->offset;*p_out = HID_BUILD_HDR(msg_type, param); // 构建 HID 头p_out++;p_buf->len = 1; // start with header only// add report id prefix only if non-zero (which is reserved)// 添加报告 ID(如果非零)if (msg_type == HID_TRANS_DATA && (data || param == HID_PAR_REP_TYPE_OTHER)) {*p_out = data; // report_idp_out++;p_buf->len++;}// 添加数据if (len > 0 && p_data != NULL) {memcpy(p_out, p_data, len);p_buf->len += len;}// check if connected// 4. 连接状态检查if (hd_cb.device.state != HIDD_DEV_CONNECTED) {// for DATA on intr we hold transfer and try to reconnect// 如果未连接但发送的是中断通道数据,保存数据并尝试重新连接if (msg_type == HID_TRANS_DATA && cid == p_hcon->intr_cid) {// drop previous data, we do not queue it for nowif (hd_cb.pending_data) {osi_free(hd_cb.pending_data);}hd_cb.pending_data = p_buf;if (hd_cb.device.conn.conn_state == HID_CONN_STATE_UNUSED) {hidd_conn_initiate();}return HID_SUCCESS;}log_counter_metrics(android::bluetooth::CodePathCounterKeyEnum::HIDD_ERR_NO_CONNECTION_AT_SEND_DATA,1);return HID_ERR_NO_CONNECTION;}log::verbose("report sent");// 5. L2CAP 数据发送if (!L2CA_DataWrite(cid, p_buf)) {log_counter_metrics(android::bluetooth::CodePathCounterKeyEnum::HIDD_ERR_CONGESTED_AT_DATA_WRITE,1);return (HID_ERR_CONGESTED);}return (HID_SUCCESS);
}
将 HID 数据封装为 L2CAP 数据包并发送到主机设备。
bta_sys_busy
/packages/modules/Bluetooth/system/bta/sys/bta_sys_conn.cc
/********************************************************************************* Function bta_sys_busy** Description Called by BTA subsystems to indicate that the connection to* peer device is busy** Returns void*******************************************************************************/
void bta_sys_busy(tBTA_SYS_ID id, uint8_t app_id, const RawAddress& peer_addr) {if (bta_sys_cb.prm_cb) { // 通知电源管理模块(PRM)bta_sys_cb.prm_cb(BTA_SYS_CONN_BUSY, id, app_id, peer_addr);}if (bta_sys_cb.ppm_cb) { // 通知策略管理模块(PPM)bta_sys_cb.ppm_cb(BTA_SYS_CONN_BUSY, id, app_id, peer_addr);}
}
通知电源管理和连接管理模块,当前与对等设备的连接正处于繁忙状态,暂时不宜进入低功耗模式。
bta_dm_rm_cback(BTA_SYS_CONN_BUSY)
packages/modules/Bluetooth/system/bta/dm/bta_dm_act.cc
********************************************************************************* Function bta_dm_rm_cback** Description Role management callback from sys*** Returns void*******************************************************************************/
static void bta_dm_rm_cback(tBTA_SYS_CONN_STATUS status, tBTA_SYS_ID id,uint8_t app_id, const RawAddress& peer_addr) {uint8_t j;tBTA_PREF_ROLES role;tBTA_DM_PEER_DEVICE* p_dev;log::debug("BTA Role management callback count:{} status:{} peer:{}",bta_dm_cb.cur_av_count, bta_sys_conn_status_text(status),ADDRESS_TO_LOGGABLE_CSTR(peer_addr));// 1. 设备查找与连接状态更新p_dev = bta_dm_find_peer_device(peer_addr);if (status == BTA_SYS_CONN_OPEN) {if (p_dev) {/* Do not set to connected if we are in the middle of unpairing. When AV* stream is* started it fakes out a SYS_CONN_OPEN to potentially trigger a role* switch command.* But this should not be done if we are in the middle of unpairing.*/if (p_dev->conn_state != BTA_DM_UNPAIRING)p_dev->conn_state = BTA_DM_CONNECTED;// 2. 首选角色管理for (j = 1; j <= p_bta_dm_rm_cfg[0].app_id; j++) {if (((p_bta_dm_rm_cfg[j].app_id == app_id) ||(p_bta_dm_rm_cfg[j].app_id == BTA_ALL_APP_ID)) &&(p_bta_dm_rm_cfg[j].id == id)) {ASSERT_LOG(p_bta_dm_rm_cfg[j].cfg <= BTA_PERIPHERAL_ROLE_ONLY,"Passing illegal preferred role:0x%02x [0x%02x<=>0x%02x]",p_bta_dm_rm_cfg[j].cfg, BTA_ANY_ROLE,BTA_PERIPHERAL_ROLE_ONLY);role = static_cast<tBTA_PREF_ROLES>(p_bta_dm_rm_cfg[j].cfg);if (role > p_dev->pref_role) p_dev->pref_role = role;break;}}}}// 3. 音频连接状态管理if (BTA_ID_AV == id) {if (status == BTA_SYS_CONN_BUSY) { // 连接繁忙时标记音频活跃并更新计数if (p_dev) p_dev->set_av_active();/* AV calls bta_sys_conn_open with the A2DP stream count as app_id */if (BTA_ID_AV == id) bta_dm_cb.cur_av_count = bta_dm_get_av_count();} else if (status == BTA_SYS_CONN_IDLE) { // 连接空闲时重置音频活跃状态并更新计数if (p_dev) p_dev->reset_av_active();/* get cur_av_count from connected services */if (BTA_ID_AV == id) bta_dm_cb.cur_av_count = bta_dm_get_av_count();}}// 4. 角色调整/* Don't adjust roles for each busy/idle state transition to avoidexcessive switch requests when individual profile busy/idle statuschanges */if ((status != BTA_SYS_CONN_BUSY) && (status != BTA_SYS_CONN_IDLE))bta_dm_adjust_roles(false);
}
处理角色管理回调,用于响应系统连接状态变化,更新对等设备的连接状态和首选角色,并根据音频(AV)连接状态进行相应管理。
bta_dm_pm_cback(BTA_SYS_CONN_BUSY)
packages/modules/Bluetooth/system/bta/dm/bta_dm_pm.cc
/********************************************************************************* Function bta_dm_pm_cback** Description Conn change callback from sys for low power management*** Returns void*******************************************************************************/
static void bta_dm_pm_cback(tBTA_SYS_CONN_STATUS status, const tBTA_SYS_ID id,uint8_t app_id, const RawAddress& peer_addr) {uint8_t i, j;tBTA_DM_PEER_DEVICE* p_dev;tBTA_DM_PM_REQ pm_req = BTA_DM_PM_NEW_REQ;log::debug("Power management callback status:{}[{}] id:{}[{}], app:{}",bta_sys_conn_status_text(status), status, BtaIdSysText(id), id,app_id);// 1. 配置查找与过滤/* find if there is an power mode entry for the service */for (i = 1; i <= p_bta_dm_pm_cfg[0].app_id; i++) {if ((p_bta_dm_pm_cfg[i].id == id) &&((p_bta_dm_pm_cfg[i].app_id == BTA_ALL_APP_ID) ||(p_bta_dm_pm_cfg[i].app_id == app_id)))break;}/* if no entries are there for the app_id and subsystem in* get_bta_dm_pm_spec()*/if (i > p_bta_dm_pm_cfg[0].app_id) {log::debug("Ignoring power management callback as no service entries exist");return;}log::debug("Stopped all timers for service to device:{} id:{}[{}]",ADDRESS_TO_LOGGABLE_CSTR(peer_addr), BtaIdSysText(id), id);// 2. 定时器管理// 停止与该服务相关的所有定时器// 防止旧的定时任务干扰新的电源管理决策bta_dm_pm_stop_timer_by_srvc_id(peer_addr, static_cast<uint8_t>(id));p_dev = bta_dm_find_peer_device(peer_addr);if (p_dev) {log::debug("Device info:{}", p_dev->info_text());} else {log::error("Unable to find peer device...yet soldiering on...");}// 3. SSR (Sniff Subrating) 参数设置/* set SSR parameters on SYS CONN OPEN */int index = BTA_DM_PM_SSR0;if ((BTA_SYS_CONN_OPEN == status) && p_dev && (p_dev->is_ssr_active())) {index = get_bta_dm_pm_spec()[p_bta_dm_pm_cfg[i].spec_idx].ssr;} else if (BTA_ID_AV == id) { // 音频子系统在繁忙状态下使用特殊的 SSR 设置if (BTA_SYS_CONN_BUSY == status) {/* set SSR4 for A2DP on SYS CONN BUSY */index = BTA_DM_PM_SSR4;} else if (BTA_SYS_CONN_IDLE == status) {index = get_bta_dm_pm_spec()[p_bta_dm_pm_cfg[i].spec_idx].ssr;}}/* if no action for the event */if (get_bta_dm_pm_spec()[p_bta_dm_pm_cfg[i].spec_idx].actn_tbl[status][0].power_mode == BTA_DM_PM_NO_ACTION) {if (BTA_DM_PM_SSR0 == index) /* and do not need to set SSR, return. */return;}// 4. 连接服务管理for (j = 0; j < bta_dm_conn_srvcs.count; j++) {/* check if an entry already present */if ((bta_dm_conn_srvcs.conn_srvc[j].id == id) &&(bta_dm_conn_srvcs.conn_srvc[j].app_id == app_id) &&bta_dm_conn_srvcs.conn_srvc[j].peer_bdaddr == peer_addr) {bta_dm_conn_srvcs.conn_srvc[j].new_request = true;break;}}/* if subsystem has no more preference on the power mode removethe cb */if (get_bta_dm_pm_spec()[p_bta_dm_pm_cfg[i].spec_idx].actn_tbl[status][0].power_mode == BTA_DM_PM_NO_PREF) {if (j != bta_dm_conn_srvcs.count) {bta_dm_conn_srvcs.count--;for (; j < bta_dm_conn_srvcs.count; j++) {memcpy(&bta_dm_conn_srvcs.conn_srvc[j],&bta_dm_conn_srvcs.conn_srvc[j + 1],sizeof(bta_dm_conn_srvcs.conn_srvc[j]));}} else {log::warn("bta_dm_act no entry for connected service cbs");return;}} else if (j == bta_dm_conn_srvcs.count) {/* check if we have more connected service that cbs */if (bta_dm_conn_srvcs.count == BTA_DM_NUM_CONN_SRVS) {log::warn("bta_dm_act no more connected service cbs");return;}/* fill in a new cb */bta_dm_conn_srvcs.conn_srvc[j].id = id;bta_dm_conn_srvcs.conn_srvc[j].app_id = app_id;bta_dm_conn_srvcs.conn_srvc[j].new_request = true;bta_dm_conn_srvcs.conn_srvc[j].peer_bdaddr = peer_addr;log::info("New connection service:{}[{}] app_id:{}", BtaIdSysText(id), id,app_id);bta_dm_conn_srvcs.count++;bta_dm_conn_srvcs.conn_srvc[j].state = status; // 更新为 BUSY 状态} else {/* no service is added or removed. only updating status. */bta_dm_conn_srvcs.conn_srvc[j].state = status;}/* stop timer */// 停止与该设备相关的所有电源管理定时器,若仍有其他服务连接,则标记需要重启定时器bta_dm_pm_stop_timer(peer_addr);if (bta_dm_conn_srvcs.count > 0) {pm_req = BTA_DM_PM_RESTART;log::verbose("bta_dm_pm_stop_timer for current service, restart other service ""timers: count = {}",bta_dm_conn_srvcs.count);}if (p_dev) {p_dev->pm_mode_attempted = 0;p_dev->pm_mode_failed = 0;}// 5. SSR 应用与电源模式设置if (p_bta_dm_ssr_spec[index].max_lat || index == BTA_DM_PM_SSR_HH) {/* do not perform ssr for AVDTP start */// 对于音频子系统在 BUSY 状态下,不执行 SSR 参数设置(已在前面单独处理)if (id != BTA_ID_AV || status != BTA_SYS_CONN_BUSY) {bta_dm_pm_ssr(peer_addr, index);} else {log::debug("Do not perform SSR when AVDTP start");}} else {uint8_t* p = NULL;if (bluetooth::shim::GetController()->SupportsSniffSubrating() &&((NULL != (p = get_btm_client_interface().peer.BTM_ReadRemoteFeatures(peer_addr))) &&HCI_SNIFF_SUB_RATE_SUPPORTED(p)) &&(index == BTA_DM_PM_SSR0)) {if (status == BTA_SYS_SCO_OPEN) {log::verbose("SCO inactive, reset SSR to zero");get_btm_client_interface().link_policy.BTM_SetSsrParams(peer_addr, 0, 0,0);} else if (status == BTA_SYS_SCO_CLOSE) {log::verbose("SCO active, back to old SSR");bta_dm_pm_ssr(peer_addr, BTA_DM_PM_SSR0);}}}bta_dm_pm_set_mode(peer_addr, BTA_DM_PM_NO_ACTION, pm_req); // 设置最终的电源模式
}
处理电源管理回调,根据系统连接状态变化,管理蓝牙设备的低功耗模式,包括嗅探模式(Sniff Mode)、保持模式(Hold Mode)和停止模式(Park Mode)等。通过合理配置和动态调整低功耗模式,在保证蓝牙连接质量的同时,最大限度地降低设备功耗,延长电池续航时间。
BTA_SYS_CONN_BUSY
状态主要针对设备连接繁忙时的电源管理策略调整。这个状态表示设备正在进行数据传输,需要确保连接稳定性和低延迟,因此电源管理策略会相应调整。当接收到 BTA_SYS_CONN_BUSY
状态时,主要执行以下操作:
-
音频子系统优化:为音频流(如 A2DP)设置专门的 SSR 参数(SSR4),确保低延迟传输。
-
服务状态更新:记录服务的繁忙状态,用于后续电源策略决策。
-
定时器管理:暂停可能导致低功耗模式的定时器,维持连接活跃状态。
-
避免参数冲突:跳过对音频子系统的常规 SSR 参数设置,使用专用参数。
-
维持连接状态:通过
bta_dm_pm_set_mode
确保在数据传输期间不进入低功耗模式。
bta_dm_pm_set_mode(BTA_DM_PM_NO_ACTION)
/********************************************************************************* Function bta_dm_pm_set_mode** Description Set the power mode for the device*** Returns void*******************************************************************************/static void bta_dm_pm_set_mode(const RawAddress& peer_addr,tBTA_DM_PM_ACTION pm_request,tBTA_DM_PM_REQ pm_req) {tBTA_DM_PM_ACTION pm_action = BTA_DM_PM_NO_ACTION;uint64_t timeout_ms = 0;uint8_t i, j;tBTA_DM_PM_ACTION failed_pm = 0;tBTA_DM_PEER_DEVICE* p_peer_device = NULL;tBTA_DM_PM_ACTION allowed_modes = 0;tBTA_DM_PM_ACTION pref_modes = 0;const tBTA_DM_PM_CFG* p_pm_cfg;const tBTA_DM_PM_SPEC* p_pm_spec;const tBTA_DM_PM_ACTN* p_act0;const tBTA_DM_PM_ACTN* p_act1;tBTA_DM_SRVCS* p_srvcs = NULL;bool timer_started = false;uint8_t timer_idx, available_timer = BTA_DM_PM_MODE_TIMER_MAX;uint64_t remaining_ms = 0;if (!bta_dm_cb.device_list.count) {log::info("Device list count is zero");return;}// 1. 设备与失败模式检查/* see if any attempt to put device in low power mode failed */p_peer_device = bta_dm_find_peer_device(peer_addr);/* if no peer device found return */if (p_peer_device == NULL) {log::info("No peer device found");return;}failed_pm = p_peer_device->pm_mode_failed; // 获取之前尝试过但失败的电源模式// 2 .遍历连接服务,确定允许的电源模式for (i = 0; i < bta_dm_conn_srvcs.count; i++) {p_srvcs = &bta_dm_conn_srvcs.conn_srvc[i];if (p_srvcs->peer_bdaddr == peer_addr) {/* p_bta_dm_pm_cfg[0].app_id is the number of entries */for (j = 1; j <= p_bta_dm_pm_cfg[0].app_id; j++) {if ((p_bta_dm_pm_cfg[j].id == p_srvcs->id) &&((p_bta_dm_pm_cfg[j].app_id == BTA_ALL_APP_ID) ||(p_bta_dm_pm_cfg[j].app_id == p_srvcs->app_id)))break;}p_pm_cfg = &p_bta_dm_pm_cfg[j];p_pm_spec = &get_bta_dm_pm_spec()[p_pm_cfg->spec_idx];p_act0 = &p_pm_spec->actn_tbl[p_srvcs->state][0];p_act1 = &p_pm_spec->actn_tbl[p_srvcs->state][1];// 查找服务对应的配置allowed_modes |= p_pm_spec->allow_mask;log::debug("Service:{}[{}] state:{}[{}] allowed_modes:0x{:02x} service_index:{}",BtaIdSysText(p_srvcs->id), p_srvcs->id,bta_sys_conn_status_text(p_srvcs->state), p_srvcs->state,allowed_modes, j);/* PM actions are in the order of strictness */// 优先选择第一个偏好的模式/* first check if the first preference is ok */if (!(failed_pm & p_act0->power_mode)) {pref_modes |= p_act0->power_mode;if (p_act0->power_mode >= pm_action) {pm_action = p_act0->power_mode;if (pm_req != BTA_DM_PM_NEW_REQ || p_srvcs->new_request) {p_srvcs->new_request = false;timeout_ms = p_act0->timeout;}}}/* if first preference has already failed, try second preference */// 若第一个偏好失败,选择第二个偏好else if (!(failed_pm & p_act1->power_mode)) {pref_modes |= p_act1->power_mode;if (p_act1->power_mode > pm_action) {pm_action = p_act1->power_mode;timeout_ms = p_act1->timeout;}}}}// 3. 模式兼容性检查与调整// 检查选择的电源模式是否被所有服务允许// 如有冲突,调整为兼容的最高优先级模式if (pm_action & (BTA_DM_PM_PARK | BTA_DM_PM_SNIFF)) {/* some service don't like the mode */if (!(allowed_modes & pm_action)) {/* select the other mode if its allowed and preferred, otherwise 0 which* is BTA_DM_PM_NO_ACTION */pm_action =(allowed_modes & (BTA_DM_PM_PARK | BTA_DM_PM_SNIFF) & pref_modes);/* no timeout needed if no action is required */if (pm_action == BTA_DM_PM_NO_ACTION) {timeout_ms = 0;}}}// 4. 定时器管理/* if need to start a timer */if ((pm_req != BTA_DM_PM_EXECUTE) && (timeout_ms > 0)) {for (i = 0; i < BTA_DM_NUM_PM_TIMER; i++) {if (bta_dm_cb.pm_timer[i].in_use &&bta_dm_cb.pm_timer[i].peer_bdaddr == peer_addr) {timer_idx = bta_pm_action_to_timer_idx(pm_action);if (timer_idx != BTA_DM_PM_MODE_TIMER_MAX) {remaining_ms =alarm_get_remaining_ms(bta_dm_cb.pm_timer[i].timer[timer_idx]);if (remaining_ms < timeout_ms) {/* Cancel and restart the timer *//** TODO: The value of pm_action[timer_idx] is* conditionally updated between the two function* calls below when the timer is restarted.* This logic is error-prone and should be eliminated* in the future.*/bta_dm_pm_stop_timer_by_index(&bta_dm_cb.pm_timer[i], timer_idx);bta_dm_pm_start_timer(&bta_dm_cb.pm_timer[i], timer_idx, timeout_ms,p_srvcs->id, pm_action);}timer_started = true;}break;} else if (!bta_dm_cb.pm_timer[i].in_use) {if (available_timer == BTA_DM_PM_MODE_TIMER_MAX) available_timer = i;}}/* new power mode for a new active connection */if (!timer_started) {if (available_timer != BTA_DM_PM_MODE_TIMER_MAX) {bta_dm_cb.pm_timer[available_timer].peer_bdaddr = peer_addr;timer_idx = bta_pm_action_to_timer_idx(pm_action);if (timer_idx != BTA_DM_PM_MODE_TIMER_MAX) {bta_dm_pm_start_timer(&bta_dm_cb.pm_timer[available_timer], timer_idx,timeout_ms, p_srvcs->id, pm_action);timer_started = true;}} else {log::warn("no more timers");}}return;}/* if pending power mode timer expires, and currecnt link is in alower power mode than current profile requirement, igonre it */if (pm_req == BTA_DM_PM_EXECUTE && pm_request < pm_action) {log::error("Ignore the power mode request: {}", pm_request);return;}// 5. 执行电源模式切换if (pm_action == BTA_DM_PM_PARK) {p_peer_device->pm_mode_attempted = BTA_DM_PM_PARK;bta_dm_pm_park(peer_addr);log::warn("DEPRECATED Setting link to park mode peer:{}",ADDRESS_TO_LOGGABLE_CSTR(peer_addr));} else if (pm_action & BTA_DM_PM_SNIFF) {/* dont initiate SNIFF, if link_policy has it disabled */if (BTM_is_sniff_allowed_for(peer_addr)) {log::debug("Link policy allows sniff mode so setting mode peer:{}",ADDRESS_TO_LOGGABLE_CSTR(peer_addr));p_peer_device->pm_mode_attempted = BTA_DM_PM_SNIFF;bta_dm_pm_sniff(p_peer_device, (uint8_t)(pm_action & 0x0F));} else {log::debug("Link policy disallows sniff mode, ignore request peer:{}",ADDRESS_TO_LOGGABLE_CSTR(peer_addr));}} else if (pm_action == BTA_DM_PM_ACTIVE) {log::debug("Setting link to active mode peer:{}",ADDRESS_TO_LOGGABLE_CSTR(peer_addr));bta_dm_pm_active(peer_addr);}
}
负责设置设备电源模式,根据各服务的电源需求,综合决策并应用适当的低功耗模式,包括嗅探模式(Sniff Mode)、保持模式(Hold Mode)和停止模式(Park Mode)等。
在 pm_request
为 BTA_DM_PM_NO_ACTION
且 pm_req
为 BTA_DM_PM_NEW_REQ
的情况下:
-
函数会正常遍历所有服务,根据它们的配置和优先级选择合适的电源模式
-
如果选择的模式需要延迟执行(通过定时器),函数会设置相应的定时器
-
BTA_DM_PM_NO_ACTION
参数本身不会阻止电源模式切换,除非所有服务都允许此模式
bta_sys_idle
packages/modules/Bluetooth/system/bta/sys/bta_sys_conn.cc
/********************************************************************************* Function bta_sys_idle** Description Called by BTA subsystems to indicate that the connection to* peer device is idle** Returns void*******************************************************************************/
void bta_sys_idle(tBTA_SYS_ID id, uint8_t app_id, const RawAddress& peer_addr) {if (bta_sys_cb.prm_cb) {bta_sys_cb.prm_cb(BTA_SYS_CONN_IDLE, id, app_id, peer_addr);}if (bta_sys_cb.ppm_cb) {bta_sys_cb.ppm_cb(BTA_SYS_CONN_IDLE, id, app_id, peer_addr);}
}
通知系统连接空闲状态,与之前分析的 bta_sys_busy
函数相对应,主要作用是通知电源管理和连接管理模块,当前与对等设备的连接已进入空闲状态,可以考虑进入低功耗模式。
三、时序图
四、典型问题与优化方向
①性能瓶颈
-
MTU碎片化:默认HID_DEV_MTU_SIZE(512B)可能不匹配高清HID设备(如触控笔)
-
优化建议:动态MTU协商(参考BLE ATT_MTU Exchange机制)
②功耗热点
-
虚假BUSY状态:高频小数据包(如游戏手柄)导致持续高功耗
-
优化建议:引入事件合并机制(Debounce算法)
③兼容性缺陷
-
老旧HID主机:对SSR4参数不支持引发连接抖动
-
优化建议:SSR能力嗅探与自适应回退
五、总结
蓝牙 HID 数据传输与电源管理是一个复杂的协同系统,涉及多层协议和多个功能模块的紧密协作。通过分层设计和状态驱动的机制,蓝牙协议栈实现了高效的数据传输和智能的电源管理。在数据传输方面,系统通过通道选择、拥塞控制和异步处理确保了数据的可靠性和实时性;在电源管理方面,通过动态状态监测、低功耗模式切换和参数优化,最大限度地降低了设备功耗。这种设计不仅满足了 HID 设备对数据传输的严格要求,也为电池供电的移动设备提供了良好的续航保障。
①双通道差异化传输
-
中断通道:硬实时保障(<125ms延迟),专用于输入报告,强制20MHz带宽锁定
-
控制通道:支持任意报告类型,但受协议栈QoS策略限制
②电源状态机设计
-
三级仲裁策略:服务优先级 > 历史失败记录 > 设备兼容性
-
嗅探参数动态化:根据服务类型(如A2DP/AVRCP)加载不同SSR预置模板
③错误弹性机制
-
拥塞降级:连续3次L2CAP发送失败触发控制通道回退
-
连接恢复:中断通道数据在断连时缓存并尝试自动重连
④功耗-性能平衡
-
BUSY锁频:维持Baseband Clock不降频以保吞吐量
-
IDLE快速休眠:在首个IDLE事件后500ms内进入SNIFF模式
相关文章:
【Bluedroid】蓝牙HID DEVICE 报告发送与电源管理源码解析
本文基于Android蓝牙协议栈代码,深度解析HID设备(如键盘、鼠标)从应用层发送输入报告到主机设备的完整流程,涵盖数据封装、通道选择、L2CAP传输、电源管理四大核心模块。通过函数调用链(send_report → BTA_HdSendRepo…...
day15-进程管理
1. 概述 运行起来的软件就是进程,在内存中运行守护进程/服务:一直运行的进程 2. 僵尸进程 2.1. 僵尸进程zombie 当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸…...
抖音视频下载工具 v1.1 自用分享
用免费的公益接口用AI写了个简单的抖音视频下载工具,自用的,不支持批量下载 内置两套API,解析失败会自动切换,支持视频预览播放,视频截图等操作 使用方法也很简单: 软件打开后会监听粘贴板,当检…...
46、什么是Windows服务,它的⽣命周期与标准的EXE程序有什么不同?
Windows服务是一种在Windows操作系统后台运行的特殊应用程序,与标准的EXE程序相比,其生命周期在启动方式、运行持续性、用户交互、运行账户、管理方式、进程状态及开发要求等方面存在显著差异。以下是对Windows服务及其与标准EXE程序生命周期差异的详细分…...
用 UniApp 构建习惯打卡 App —— HabitLoop 开发记
我正在参加CodeBuddy「首席试玩官」内容创作大赛,本文所使用的 CodeBuddy 免费下载链接:腾讯云代码助手 CodeBuddy - AI 时代的智能编程伙伴 当我脑海中突然冒出一个念头:“做一个自己能每天打卡的习惯 App 吧”,我立刻打开了 Cod…...
NB-IoT技术深度解析:部署模式与节能机制全指南
知识点1【NB-IoT的介绍】 NB-IoT 是指Narrow Band Internet of Things,聚集于低功耗窄带宽广域物联网。 1、License介绍 “有牌照”(license)频谱,指的是政府或者监管机构通过拍卖,划拨等方式,授予给各个…...
Vue百日学习计划Day28-32天详细计划-Gemini版
总目标: 在 Day 28-32 深入理解 Vue 3 的响应式机制,熟练掌握 Composition API 中的 setup, ref, reactive, toRefs, readonly, computed, watch, watchEffect 等核心 API 的使用。 所需资源: Vue 3 官方文档 (组合式 API): https://cn.vuejs.org/guide/introducti…...
Leetcode134加油站
题目链接 134 题意图解: 题目给了n个节点,这些节点呈现环状,每次到一个低点要消耗cost[i]的油量。 从中我们可以得出一个结论:看一个点能不能到下一个点,就要用当前的油量减去消耗的量,那么gas[i] - cost…...
用算术右移实现逻辑右移及用逻辑右移实现算术右移
函数srl()用算术右移实现逻辑右移,函数sra()用逻辑右移实现算术右移。 程序代码 int sra(int x,int k); unsigned int srl(unsigned int x, int k);void main() {int rx1,k,x1;unsigned int rx2,x2;k3;x10x8777;x20x8777;rx1sra(x1, k);rx2srl(x2, k);while(1); }…...
箭头函数及其与普通函数区别的详细解释
一、箭头函数的基本特性 语法简洁性 箭头函数使用 > 符号定义,省略 function 关键字,适合快速定义匿名函数或简单表达式。 // 普通函数 function sum(a, b) { return a b; } // 箭头函数 const sum (a, b) > a b;若函数体为单行表达式&#x…...
Denoising Score Matching with Langevin Dynamics
在自然图像等复杂数据集中,真实数据往往集中分布在一个低维流形上,概率密度函数的梯度(即得分函数)难以定义与估计。为缓解该问题,SMLD 提出使用不同强度的高斯噪声对数据进行扰动,扰动后的数据不再集中于低…...
Flink的时间问题
Apache Flink 中的 时间语义(Time Semantics) 是流处理的核心概念之一。Flink 支持多种时间类型,用于控制窗口计算、事件排序和状态管理等操作。 🕒 一、Flink 时间分类 类型名称描述Processing Time处理时间每个算子基于本地系统…...
3:OpenCV—视频播放
播放来自文件或相机的视频 在本教程中,我将向您展示如何使用OpenCV从文件或相机/网络摄像头捕获和播放视频。尽管OpenCV没有针对视频处理进行优化,但它提供了一个简单的API来播放视频。在我们的OpenCV程序中,我们所要做的就是从视频文件或相…...
AI工程 新技术追踪 探讨
文章目录 一、核心差异维度对比二、GitHub对AI工程师的独特价值三、需要警惕的陷阱四、推荐追踪策略五、与传统开发的平衡建议 以下内容整理来自 deepseek 作为AI工程师,追踪GitHub开源项目 对技术成长和职业发展的影响 比传统应用开发工程师 更为显著,…...
C++:函数模板
所谓函数模板就是定义一个通用的函数,不指定具体的参数,用一个虚拟参数代替; 当函数调用时,会根据实参判断具体的类型。 注意:不要使用默认参数;可以重载但尽量不要重载。 #include<iostream> usi…...
一款适配国内的视频软件,畅享大屏与局域网播放
软件介绍 今天要给大家安利一款超强视频播放软件——MXPlayer。它的解码实力堪称一绝,市面上不管是常见的 MP4、MKV 格式,还是对播放设备要求极高的超高清 4K、HDR 视频,甚至那些鲜为人知的冷门格式,它统统都能流畅播放ÿ…...
SONiC系统之高速数据遥测High Frequency Telemetry
SONiC系统之高速数据遥测High Frequency Telemetry 数据遥测 这篇文章介绍了SONiC系统支持Telemetry的软件架构以及gNMI接口中Telemetry Streaming功能Dial-in和Dial-out两者模式的区别。正如该文指出的那样,该结构面临扩展性问题,当AI训练、推理等大型…...
【Java ee初阶】jvm(3)
一、双亲委派机制(类加载机制中,最经常考到的问题) 类加载的第一个环节中,根据类的全限定类名(包名类名)找到对应的.class文件的过程。 JVM中进行类加载的操作,需要以来内部的模块“类加载器”…...
C++ for QWidget:connect(连接)
语法: QObject::connect(发射信号的对象,发射的信号,接收信号的对象,接收后执行的命令) #include "mainwindow.h" #include "ui_mainwindow.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow) {ui-&g…...
uni-app学习笔记七-vue3事件处理
本文主要用于记录vue3中的点击事件和change事件 点击事件:v-on:click,可简写为click change事件,用于监听值发生改变的的事件处理:change 示例代码: <template><view class"box" click"onClick"…...
【C++进阶篇】C++容器完全指南:掌握set和map的使用,提升编码效率
C容器的实践与应用:轻松掌握set、map与multimap的区别与用法 一. 序列式容器与关联式容器1.1 序列式容器 (Sequential Containers)1.2 关联式容器 (Associative Containers) 二. set系列使用2.1 set的构造和迭代器2.2 set的增删查2.2.1 插入2.2.2 查找2.2.3 删除 2.…...
吴恩达机器学习(1)——机器学习算法分类
1、机器学习算法 1、监督学习 在实际应用中最为常见的快速进度结果 2、非监督学习 2、监督学习(Supervised Learning) 2.1、回归算法 [!note] 监督学习 是指学习从 x -> y 或者从输入到输出映射的算法 监督学习的关键特征是你给学习算法提供学习…...
我的创作纪念日——512天
2023 年 12 月 19 日,我撰写了第 1 篇记录型博客《数据结构课程设计——报数问题》,这是我记录一段实践经验的开始。 回望那时的自己,还很稚嫩,刚刚迈入计算机的大门不久,一切都显得新鲜而充满挑战。今天是我成为创作者…...
SpringBoot快速上手
1.Maven Maven是项目管理工具,通过pom.xml文件来获取jar包,而不用手动去添加jar包 引入依赖之后需要刷新maven,以下这两个地方都可以 中央仓库 : Central Repository: Maven Repository: Central 2.Spring Boot 2.1创建项目…...
自动化:批量文件重命名
自动化:批量文件重命名 1、前言 2、效果图 3、源码 一、前言 今天来分享一款好玩的自动化脚:批量文件重命名 有时候呢,你的文件被下载下来文件名都是乱七八糟毫无规律,但是当时你下载的时候没办法重名或者你又不想另存为重新重…...
低延迟与高性能的技术优势解析:SmartPlayer VS VLC Media Player
在实时视频流的应用中,RTSP(Real-Time Streaming Protocol)播放器扮演着至关重要的角色,尤其是在视频监控、远程医疗、直播等高实时性需求的场景中。随着行业需求的不断升级,对播放器的低延迟、稳定性、兼容性等方面的…...
java中的Servlet2.x详解
一、Servlet 2.x 版本演进与核心特性 Servlet 2.x 是 Java Web 开发中承上启下的重要版本系列,主要包括 Servlet 2.4 和 Servlet 2.5 两个子版本。以下是其核心特性与改进: Servlet 2.4(2003年发布) XML Schema 支持:…...
大模型deepseek如何助力数据安全管理
敏感数据识别与处理 精准定位敏感信息 :运用多模态识别引擎技术,快速扫描上传文件与输入内容,精准识别身份证号、银行卡号、商业合同关键信息等各类敏感数据,并支持金融、医疗等行业定制化规则库,满足不同行业的特殊需…...
【linux驱动】【设备树】按键设备树讲解
设备树你添加电源键示例。 / {gpio-keys {compatible = "gpio-keys";#address-cells = <1>;#size-cells = <0>;button@1 {label = "Power Button";linux,code = <KEY_POWER>; // 按键编码,定义在include/uapi/linux/input-event-code…...
Vibe Coding:编程中的氛围与效率的艺术
引言 在软件开发的世界里,我们常常关注语言特性、框架选择和算法效率,却较少讨论一个同样重要的因素——编程时的"氛围"(vibe)。Vibe Coding是一种关注开发者心理状态、工作环境和整体"感觉"的编程方法论。它…...
算法岗实习八股整理——深度学习篇(不断更新中)
目录 激活函数特征典型例子sigmod函数tanh函数补充:零中心化输出优势非线性特性如何提升神经网络表现 激活函数 特征 非线性:激活函数满足非线性时,才不会被单层网络替代,神经网络才有意义可微性:优化器大多数是用梯…...
Java IO及Netty框架学习小结
Netty netty官网: Netty 什么是Netty? Netty 是 一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。Netty 是一个 NIO 客户端服务器框架,可以快速轻松地开发网络应用程序(例如协议服务器和客…...
理想AI Talk第二季-重点信息总结
一、TL;DR 理想为什么要做自己的基模:座舱家庭等特殊VLM场景,deepseek/openai没有解决理想的基模参数量:服务端-300B,VLencoder-32B/3.6B,日常工作使用-300B,VLA-4B为什么自动驾驶可以达成&…...
软件架构之-论软件系统架构评估以及应用
论软件系统架构评估以及应用 摘要正文 摘要 2023年2月,本人所在集团公司承接了长三角地区某省渔船图纸电子化审查系统项目开发,该项目旨在为长三角地区渔船建造设计院,以及渔船图纸审查机构提供一个便捷化的服务平台。在此项目中,…...
Java面试实战:从Spring Boot到分布式缓存的深度探索
Java面试实战:从Spring Boot到分布式缓存的深度探索 场景介绍 在一家著名的互联网大厂,面试官老王正对求职者“水货程序员”明哥进行Java技术面试。明哥带着一点紧张和自信,迎接这场技术“拷问”。 第一轮:基础问题 老王&#…...
2025年全国青少年信息素养大赛C++小学全年级初赛试题
一、单选题 1、在C中,表示逻辑运算符 "或" 的是?( )(5 分) A.|| B.& C. D. 解析:||是或者, &&是并且 2、…...
React中巧妙使用异步组件Suspense优化页面性能。
文章目录 前言一、为什么需要异步组件?1. 性能瓶颈分析2. 异步组件的价值 二、核心实现方式1. React.lazy Suspense(官方推荐)2. 路由级代码分割(React Router v6) 总结 前言 在 React 应用中,随着功能复…...
nginx相关面试题30道
一、基础概念与核心特性 1. 什么是 Nginx?它的主要用途有哪些? 答案: Nginx 是一款高性能的开源 Web 服务器、反向代理服务器及负载均衡器,基于事件驱动的异步非阻塞架构,擅长处理高并发场景。 主要用途:…...
java中的Servlet1.x详解
Servlet 1.x 是 Java Web 开发的早期规范,为后续版本奠定了基础。以下是其核心特性、使用方式及与现代版本的对比分析: 一、Servlet 1.x 的核心特性 基础接口与实现 Servlet 1.x 的核心是 javax.servlet.Servlet 接口,开发者必须直接实现其五…...
给大模型“贴膏药”:LoRA微调原理说明书
一、前言:当AI模型开始“叛逆” 某天,我决定教deepseek说方言。 第一次尝试(传统微调): 我:给deepseek灌了100G东北小品数据集,训练三天三夜。结果:AI确实会喊“老铁666”了…但英…...
【数字电路】第七章 脉冲波形的产生与整形电路
一、脉冲波形的产生与整形电路概述 1.矩形脉冲的获得方法 2.矩形脉冲的主要参数 3.本章所涉及的电路 4.稳态与暂稳态 电路的暂稳态实际上是通过RC电路的充放电来实现的。 5.TTL电路输入等效电路 6.TTL电路的输出等效电路 7.CMOS电路的输入等效电路 8.CMOS电路的输出等效电路 …...
React Flow 边的基础知识与示例:从基本属性到代码实例详解
本文为《React Agent:从零开始构建 AI 智能体》专栏系列文章。 专栏地址:https://blog.csdn.net/suiyingy/category_12933485.html。项目地址:https://gitee.com/fgai/react-agent(含完整代码示例与实战源)。完整介绍…...
DB-MongoDB-00002--Workload Generator for MongoDB
## DB-MongoDB-00002–Workload Generator for MongoDB 1、介绍 Workload Generator for MongoDB was designed to help MongoDB users effortlessly generate data and simulate workloads for both sharded and non-sharded clusters. The generated workloads include s…...
buck变换器的simulink/matlab仿真和python参数设计
什么是Buck电路? BUCK电路是一种降压斩波器,降压变换器输出电压平均值Uo总是小于输出电压UD。通常电感中的电流是否连续,取决于开关频率、滤波电感L和电容C的数值。BUCK也是DC-DC基本拓扑,或者称为电路结构,是最基本的DC-DC电路…...
谷歌地球引擎GEE将多个遥感影像作为多个波段合并成一张图像并下载的方法
本文介绍在谷歌地球引擎(Google Earth Engine,GEE)中,下载多年的逐日的ERA5土壤湿度数据,并在下载时,将每年同月份内的每一天的图像作为一个波段加以合并的方法。 在之前的文章GEE谷歌地球引擎批量下载逐日…...
Debezium快照事件监听器系统设计
Debezium快照事件监听器系统设计 1. 系统概述 1.1 设计目标 为 Debezium 的快照过程提供可扩展的事件监听机制允许外部系统在快照过程中执行自定义逻辑提供线程安全的事件分发机制确保监听器的异常不会影响主快照流程1.2 核心功能 表快照开始事件监听表快照完成事件监听行数据…...
选择之困:如何挑选合适的 Python 环境与工具——以 Google Colaboratory 为例
引言:选择之困与 Python 的多样性 在过去的十年中,Python 编程语言以其简洁的语法、强大的功能和广泛的适用性迅速崛起,成为全球最受欢迎的编程语言之一。从数据科学到 Web 开发,从自动化脚本到人工智能,Python 无处不在。然而,这种多样性和快速发展也带来了一个显著的问…...
基于Java+MySQL+Servlet的留言系统开发全解析
本系统基于Java Web技术栈开发,采用前后端分离架构,后端通过Servlet实现业务逻辑,前端使用HTML/CSS/JavaScript构建交互界面。本文将详细解析系统设计思路、技术实现与核心代码,助您快速掌握留言系统开发精髓。 一、项目简介 本留…...
实操分享java应用容器化,使用docker作为容器工具
### 一. 目的 将现有的java应用容器化,使用docker作为容器工具。 ### 二. 配置 #### 1. Java应用中的配置 ##### a. Java子项目中的pom文件配置 ```xml <build> <plugins> <plugin> <groupId>org.spring…...
李臻20242817_安全文件传输系统项目报告_第12周
安全文件传输系统项目报告(第 9 周) 1. 代码链接 Gitee 仓库地址:https://gitee.com/li-zhen1215/homework/tree/master/Secure-file 代码结构说明: project-root/├── src/ # 源代码目录│ ├── main.c # 主程序入口│ ├…...