【android bluetooth 框架分析 01】【关键线程 3】【bt_jni_thread 线程介绍】
1. bt_jni_thread 职责介绍
bt_jni_thread 这个线程的作用是专门负责处理蓝牙 JNI 层的消息循环,也可以说是 C++ 层和 Java 层交互的桥梁线程。
1.1 什么是 JNI 层?为什么需要这个线程?
JNI(Java Native Interface)是 Android 用来让 Java 和 C/C++ 代码通信的机制。在蓝牙协议栈中:
- Java 层(APP 或框架)发送蓝牙指令。
- C++ 层(Native 蓝牙协议栈)负责底层逻辑,比如连接蓝牙设备、传输数据等。
- 中间就是 JNI 层,它用来做“翻译官” —— 把 Java 的指令转成 C++ 能理解的函数调用,反之也一样。
1.2 职责总结
-
消息分发器(Message Dispatcher)
所有 JNI 相关的调用(比如设备连接状态回调、扫描结果等),都通过这个线程发送回 Java 层。 -
避免阻塞主线程(Non-blocking)
蓝牙操作可能会耗时,比如搜索设备、建立连接等。如果不单独开线程处理,会拖慢系统,甚至 ANR(应用无响应)。 -
保证线程安全(Thread Safety)
蓝牙操作涉及很多共享资源(socket、状态等),用一个专门的线程可以避免多线程访问冲突。 -
事件循环(Message Loop)
这个线程运行的是一个“消息循环”,就是不断读取事件队列中的消息,然后按顺序处理。
1.3 为什么这样设计很重要?
-
安全性(Safe):
如果所有 JNI 回调都在主线程或者随便哪个线程跑,容易出现崩溃或者状态错乱。 -
效率(Efficient):
用单独线程做专门的事,可以提高系统响应速度,用户操作不会被蓝牙事件卡住。 -
解耦(Decoupled):
Java 层、JNI 层、Native 层之间职责分明,出了问题更容易排查和维护。
1.4 总结一句话:
bt_jni_thread 就像是蓝牙 JNI 层的“接线员”,负责有序、高效、安全地处理 Java 与 C++ 层的消息传递,是整个 Android 蓝牙系统稳定运行的重要一环。
2. 如何工作的?
既然 bt_jni_thread 职责已经很清晰了, 那我们就探索一下, bt_jni_thread 是如何启动 , 如何接收事件, 以及处理事件的?
- system/btif/src/btif_core.cc
2.1 线程何时启动
static MessageLoopThread jni_thread("bt_jni_thread");bt_status_t btif_init_bluetooth() {LOG_INFO("%s entered", __func__);exit_manager = new base::AtExitManager();jni_thread.StartUp(); // 创建 bt_jni_thread 线程invoke_thread_evt_cb(ASSOCIATE_JVM);LOG_INFO("%s finished", __func__);return BT_STATUS_SUCCESS;
}
调用流程
[stack_manager.cc:event_init_stack] ->[btif_init_bluetooth]
- 是在 调用 stack_manager 的 event_init_stack 函数阶段触发的, event_init_stack的调用流程, 请参考 之前的文章 介绍 bt_stack_manager_thread
2.2 如何下发任务到 该线程
bt_jni_thread 线程已经启动, 那我们如何下发任务给 该线程去处理呢?
答案是通过 do_in_jni_thread 函数
1. do_in_jni_thread
bt_status_t do_in_jni_thread(const base::Location& from_here,base::OnceClosure task) {if (!jni_thread.DoInThread(from_here, std::move(task))) {LOG(ERROR) << __func__ << ": Post task to task runner failed!";return BT_STATUS_FAIL;}return BT_STATUS_SUCCESS;
}
这个函数的目的是:
把你传进来的任务
task
安排到bt_jni_thread
线程里去执行。
参数解释:
from_here
:记录任务来源,用于调试(比如你从哪个文件、哪一行调用的这个函数)。task
:要在线程中执行的逻辑(闭包或 lambda 表达式)。
实现逻辑:
-
它调用了:
jni_thread.DoInThread(from_here, std::move(task));
-
让
bt_jni_thread
来执行这个任务。如果失败(比如线程未启动),就返回BT_STATUS_FAIL
。 -
那些场景会用到 do_in_jni_thread
-
从搜素结果来看, 凡是设计到 jni 交互的场景都 在使用。 这个也验证了 本章已开始就介绍的 bt_jni_thread 职责。
这个函数主要用途:
-
线程切换:你可能当前在蓝牙主线程、HAL 线程、APP 线程,但 JNI 的东西要在
bt_jni_thread
里跑,必须切过去。 -
线程安全:统一通过
bt_jni_thread
来操作 JNI,避免多线程同时调用 JNI 导致 crash。 -
任务封装:代码结构更清晰,异步任务都包成一个
Closure
任务发送。
2. btif_transfer_context
bt_status_t btif_transfer_context(tBTIF_CBACK* p_cback, uint16_t event,char* p_params, int param_len,tBTIF_COPY_CBACK* p_copy_cback) {tBTIF_CONTEXT_SWITCH_CBACK* p_msg = (tBTIF_CONTEXT_SWITCH_CBACK*)osi_malloc(sizeof(tBTIF_CONTEXT_SWITCH_CBACK) + param_len);BTIF_TRACE_VERBOSE("btif_transfer_context event %d, len %d", event,param_len);/* allocate and send message that will be executed in btif context */p_msg->hdr.event = BT_EVT_CONTEXT_SWITCH_EVT; /* internal event */p_msg->p_cb = p_cback;p_msg->event = event; /* callback event *//* check if caller has provided a copy callback to do the deep copy */if (p_copy_cback) {p_copy_cback(event, p_msg->p_param, p_params);} else if (p_params) {memcpy(p_msg->p_param, p_params, param_len); /* callback parameter data */}do_in_jni_thread(base::Bind(&bt_jni_msg_ready, p_msg));return BT_STATUS_SUCCESS;
}static void bt_jni_msg_ready(void* context) {tBTIF_CONTEXT_SWITCH_CBACK* p = (tBTIF_CONTEXT_SWITCH_CBACK*)context;if (p->p_cb) p->p_cb(p->event, p->p_param);osi_free(p);
}
btif_transfer_context(...)
,它的作用是:
把来自 HAL 或 Stack 的事件,转移(transfer)到 JNI 线程(bt_jni_thread)中执行,并调用对应的回调函数。
这个函数起到了核心的“线程桥梁”和“事件分发器”的作用。
函数入参解释
参数 | 类型 | 作用 |
---|---|---|
p_cback | tBTIF_CBACK* | 你希望在 bt_jni_thread 中被调用的回调函数 |
event | uint16_t | 表示是什么事件,通常用枚举,如 BTIF_DM_CB_DISCOVERY_STARTED |
p_params | char* | 回调函数要用的参数 |
param_len | int | 参数长度 |
p_copy_cback | tBTIF_COPY_CBACK* | 自定义的“深拷贝”函数,用于复杂结构的复制(可选) |
-
分配内存
tBTIF_CONTEXT_SWITCH_CBACK* p_msg = (tBTIF_CONTEXT_SWITCH_CBACK*)osi_malloc(...);
创建一个消息对象
p_msg
,用来封装要执行的任务。 -
设置元数据
p_msg->hdr.event = BT_EVT_CONTEXT_SWITCH_EVT; p_msg->p_cb = p_cback; p_msg->event = event;
标记这个是“上下文切换事件”,并把事件编号和要执行的回调函数绑定进来。
-
拷贝参数
如果参数非空,尝试用p_copy_cback()
深拷贝参数;否则直接用memcpy()
复制。 -
投递给 JNI 线程执行
do_in_jni_thread(base::Bind(&bt_jni_msg_ready, p_msg));
把这个封装好的消息送到
bt_jni_thread
中,由bt_jni_msg_ready()
函数去调度并执行真正的回调。 -
返回成功
return BT_STATUS_SUCCESS;
这样设计意义是什么?
设计点 | 意义 |
---|---|
线程安全 | 所有 JNI 回调都在同一个线程中执行,避免多线程问题 |
可扩展 | 不同事件、不同回调都能统一走这个机制 |
解耦 | HAL 层不用关心 JNI 层线程情况,只管调用 transfer |
支持复杂数据 | 可通过 p_copy_cback 自定义深拷贝参数结构体 |
总结
-
btif_transfer_context()
是一个线程切换和任务派发的机制。 -
它不是 Java 调 Native 的唯一通道,但在需要切换到
bt_jni_thread
执行的下行任务中非常关键。 -
它和
do_in_jni_thread()
一起构成了 Android 蓝牙协议栈中线程调度和事件派发的基础工具。
3. 使用举例
1. 上行 native -> java
01-10 06:48:39.160 2024 3120 I bluetooth: packages/modules/Bluetooth/system/bta/dm/bta_dm_main.cc:65 bta_dm_search_sm_execute: bta_dm_search_sm_execute state:1, event:0x20501-10 06:48:39.160 2024 3120 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: btif_dm_search_devices_evt event=BTA_DM_DISC_CMPL_EVT01-10 06:48:39.161 2024 2493 I bt_btif : packages/modules/Bluetooth/system/main/bte_logmsg.cc:198 LogMsg: operator(): HAL bt_hal_cbacks->discovery_state_changed_cb01-10 06:48:39.161 2024 2493 I AdapterProperties: Callback:discoveryStateChangeCallback with state:0
当我们发起扫描, 扫描结束后, 会上报一个状态给 java 层。
我们来梳理一下这个调用流程:
- system/btif/src/btif_dm.cc
static int start_discovery(void) {if (!interface_ready()) return BT_STATUS_NOT_READY;// 当我们触发 扫描时, 是跑在 main_thread 中的do_in_main_thread(FROM_HERE, base::BindOnce(btif_dm_start_discovery));return BT_STATUS_SUCCESS;
}void btif_dm_start_discovery(void) {BTIF_TRACE_EVENT("%s", __func__);/* no race here because we're guaranteed to be in the main thread */if (bta_dm_is_search_request_queued()) {LOG_INFO("%s skipping start discovery because a request is queued",__func__);return;}/* Will be enabled to true once inquiry busy level has been received */btif_dm_inquiry_in_progress = false;/* find nearby devices */BTA_DmSearch(btif_dm_search_devices_evt);
}
- app 侧发起扫描, 就会触发 btif_dm_start_discovery 调用,
- 在触发扫描时,我们注册了 btif_dm_search_devices_evt 回调函数
当芯片上报扫描 结束时,就会 回调 btif_dm_search_devices_evt 。 这个过程怎么回调到的,暂时不表, 后面有机会,专门单独表述,这种机制。
- system/btif/src/btif_dm.cc
static void btif_dm_search_devices_evt(tBTA_DM_SEARCH_EVT event,tBTA_DM_SEARCH* p_search_data) {// 此时发现收到了 扫描结束事件case BTA_DM_DISC_CMPL_EVT: {// 这里会触发invoke_discovery_state_changed_cb(BT_DISCOVERY_STOPPED);} break; }
-
触发调用 invoke_discovery_state_changed_cb
-
system/btif/src/bluetooth.cc
void invoke_discovery_state_changed_cb(bt_discovery_state_t state) {do_in_jni_thread(FROM_HERE, base::BindOnce([](bt_discovery_state_t state) {HAL_CBACK(bt_hal_cbacks,discovery_state_changed_cb,state);},state));
}
- HAL_CBACK(bt_hal_cbacks, discovery_state_changed_cb,…)
- 上述的 调用会打印如下log
- HAL bt_hal_cbacks->discovery_state_changed_cb
- 此时就会调用到 discovery_state_changed_cb
- 从这里开始 回调 hal 层, 将 discovery_state_changed_cb 回调放置到 bt_jni_thread 线程中去处理, 从这里往下都是跑在 bt_jni_thread 中。
- android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp
static bt_callbacks_t sBluetoothCallbacks = {
...discovery_state_changed_callback, // 这里会被回调到...
};static void discovery_state_changed_callback(bt_discovery_state_t state) {CallbackEnv sCallbackEnv(__func__);if (!sCallbackEnv.valid()) return;ALOGV("%s: DiscoveryState:%d ", __func__, state);sCallbackEnv->CallVoidMethod(sJniCallbacksObj, method_discoveryStateChangeCallback, (jint)state);
}static void classInitNative(JNIEnv* env, jclass clazz) {
...method_discoveryStateChangeCallback = env->GetMethodID(jniCallbackClass, "discoveryStateChangeCallback", "(I)V");
}
-
此时回调到 discovery_state_changed_callback 他最终回调到 java 侧的 AdapterProperties::discoveryStateChangeCallback
-
android/app/src/com/android/bluetooth/btservice/AdapterProperties.java
void discoveryStateChangeCallback(int state) {infoLog("Callback:discoveryStateChangeCallback with state:" + state);synchronized (mObject) {Intent intent;if (state == AbstractionLayer.BT_DISCOVERY_STOPPED) {mDiscovering = false;mService.clearDiscoveringPackages();mDiscoveryEndMs = System.currentTimeMillis();intent = newIntent(BluetoothAdapter.ACTION_DISCOVERY_FINISHED,BluetoothAdapterExt.ACTION_DISCOVERY_FINISHED);mService.sendBroadcast(intent, BLUETOOTH_SCAN,Utils.getTempAllowlistBroadcastOptions());} else if (state == AbstractionLayer.BT_DISCOVERY_STARTED) {mDiscovering = true;mDiscoveryEndMs = System.currentTimeMillis() + DEFAULT_DISCOVERY_TIMEOUT_MS;intent = newIntent(BluetoothAdapter.ACTION_DISCOVERY_STARTED,BluetoothAdapterExt.ACTION_DISCOVERY_STARTED);mService.sendBroadcast(intent, BLUETOOTH_SCAN,Utils.getTempAllowlistBroadcastOptions());}}}
2. 下行 java -> native
在车机的蓝牙电话通话过程中, 我们可以随意在 车机上切换当前来电是在手机接听,还是车机接听。 也就是 hfp 中 SCO 的建立和断开。 这里我们看一下 主动去建立 SCO的流程。
- android/app/jni/com_android_bluetooth_hfpclient.cpp
static jboolean connectAudioNative(JNIEnv* env, jobject object,jbyteArray address) {...bt_status_t status =sBluetoothHfpClientInterface->connect_audio((const RawAddress*)addr);...
}
- system/btif/src/btif_hf_client.cc
static bt_status_t connect_audio(const RawAddress* bd_addr) {btif_hf_client_cb_t* cb = btif_hf_client_get_cb_by_bda(*bd_addr);if (cb == NULL || !is_connected(cb)) return BT_STATUS_FAIL;CHECK_BTHF_CLIENT_SLC_CONNECTED(cb);if ((get_default_hf_client_features() & BTA_HF_CLIENT_FEAT_CODEC) &&(cb->peer_feat & BTA_HF_CLIENT_PEER_CODEC)) {BTA_HfClientSendAT(cb->handle, BTA_HF_CLIENT_AT_CMD_BCC, 0, 0, NULL);} else {BTA_HfClientAudioOpen(cb->handle);}/* Inform the application that the audio connection has been initiated* successfully */// 之间加入到 bt_jni_thread 中去执行btif_transfer_context(btif_in_hf_client_generic_evt,BTIF_HF_CLIENT_CB_AUDIO_CONNECTING, (char*)bd_addr,sizeof(RawAddress), NULL);return BT_STATUS_SUCCESS;
}
- 这里通过 btif_transfer_context 将建立 sco 的操作放置到 bt_jni_thread 线程中去执行了。
2.3 线程何时终止
bt_status_t btif_cleanup_bluetooth() {LOG_INFO("%s entered", __func__);btif_dm_cleanup();invoke_thread_evt_cb(DISASSOCIATE_JVM);btif_queue_release();jni_thread.ShutDown();delete exit_manager;exit_manager = nullptr;btif_dut_mode = 0;LOG_INFO("%s finished", __func__);return BT_STATUS_SUCCESS;
}
- system/btif/src/stack_manager.cc
static void event_clean_up_stack(std::promise<void> promise) {...btif_cleanup_bluetooth();...
}
- 当我们点击关闭蓝牙时 bt_stack_manager_thread 线程会去触发 event_clean_up_stack 调用, 在这个里面,会去讲我们的 bt_jni_thread 线程终止的。
3. bt_hal_cbacks 介绍
3.1 HAL_CBACK
在上面 2.2.2.1 小结介绍上行 流时, 我们看到了 HAL_CBACK 的调用,本节就来看看 这部分是如何 做到的。
void invoke_discovery_state_changed_cb(bt_discovery_state_t state) {do_in_jni_thread(FROM_HERE, base::BindOnce([](bt_discovery_state_t state) {HAL_CBACK(bt_hal_cbacks,discovery_state_changed_cb,state);},state));
}
#define HAL_CBACK(P_CB, P_CBACK, ...) \do { \if ((P_CB) && (P_CB)->P_CBACK) { \BTIF_TRACE_API("%s: HAL %s->%s", __func__, #P_CB, #P_CBACK); \(P_CB)->P_CBACK(__VA_ARGS__); \} else { \ASSERTC(0, "Callback is NULL", 0); \} \} while (0)
- 这里是一个宏函数,
- 可以直接把 HAL_CBACK(bt_hal_cbacks, discovery_state_changed_cb 调用替换为:
- bt_hal_cbacks->discovery_state_changed_cb(state)
但是这不是重点, 重点是 bt_hal_cbacks 初始化以及 这些回调函数的含义
3.2 bt_callbacks_t
- system/btif/src/bluetooth.cc
static bt_callbacks_t* bt_hal_cbacks = NULL;
typedef struct {/** set to sizeof(bt_callbacks_t) */size_t size;adapter_state_changed_callback adapter_state_changed_cb;adapter_properties_callback adapter_properties_cb;remote_device_properties_callback remote_device_properties_cb;device_found_callback device_found_cb;discovery_state_changed_callback discovery_state_changed_cb; // 会调用到这里pin_request_callback pin_request_cb;ssp_request_callback ssp_request_cb;bond_state_changed_callback bond_state_changed_cb;acl_state_changed_callback acl_state_changed_cb;callback_thread_event thread_evt_cb;dut_mode_recv_callback dut_mode_recv_cb;le_test_mode_callback le_test_mode_cb;energy_info_callback energy_info_cb;
} bt_callbacks_t;
bt_callbacks_t
结构体是 Bluetooth HAL(硬件抽象层) 定义的一组回调接口,用于 Native 层通过回调与上层框架通信,比如 Java 层或者 JNI 层。
回调函数名 | 作用说明 | 触发时机 / 事件说明 |
---|---|---|
adapter_state_changed_cb | 通知适配器状态变化(开/关) | 当 BluetoothAdapter.enable() 或 disable() 被调用后,适配器状态变化时触发,如 BT_STATE_ON 或 BT_STATE_OFF |
adapter_properties_cb | 返回适配器属性(如名称、地址) | 当上层请求读取或设置蓝牙本地适配器属性,如调用 getAdapterProperty() 或 setAdapterProperty() |
remote_device_properties_cb | 返回远程设备的属性(如名称、class) | 上层请求远程设备属性,或发现设备时获取其属性后触发 |
device_found_cb | 通知发现了新的远程设备 | 调用 startDiscovery() 后,在扫描过程中每发现一个新设备就会触发 |
discovery_state_changed_cb | 扫描状态变化 | 调用 startDiscovery() 或 cancelDiscovery() 后,通知开始或结束扫描 |
pin_request_cb | 要求输入 PIN 码配对 | 当连接传统蓝牙设备(BR/EDR)时需要输入 PIN 码进行配对时触发 |
ssp_request_cb | 要求进行安全简单配对(SSP) | 设备配对时支持 SSP 模式,进行确认、比较、输入密钥等操作时触发 |
bond_state_changed_cb | 配对状态变化 | 设备配对成功或失败时调用,如从 BOND_BONDING → BOND_BONDED |
acl_state_changed_cb | ACL 连接状态变化 | 蓝牙链路层连接或断开时调用(所有设备连接都会有 ACL 层) |
thread_evt_cb | 通知线程附加或分离 | JNI 层使用,用于线程绑定和解绑当前线程到 JVM(Attach/Detach) |
dut_mode_recv_cb | DUT 模式接收数据 | 当设备处于 DUT 模式(测试模式)下收到测试数据时触发(调试使用) |
le_test_mode_cb | LE 测试模式回调 | BLE 专用的 TX/RX 测试事件的结果回调(蓝牙 SIG 测试场景) |
energy_info_cb | 蓝牙能耗信息回调 | 请求能耗信息时(比如上层调用 requestControllerEnergyInfo() )触发,回调耗电数据 |
3.3 bt_hal_cbacks 如何初始化的
- system/btif/src/bluetooth.cc
static bt_callbacks_t* bt_hal_cbacks = NULL;
那这里的 bt_hal_cbacks 是如何初始化的?
- android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp
static bool initNative(JNIEnv* env, jobject obj, jboolean isGuest,jboolean isCommonCriteriaMode, int configCompareResult,jobjectArray initFlags, jboolean isAtvDevice,jstring userDataDirectory) {// 将 sBluetoothCallbacks 传递出去
int ret = sBluetoothInterface->init(&sBluetoothCallbacks, isGuest == JNI_TRUE ? 1 : 0,isCommonCriteriaMode == JNI_TRUE ? 1 : 0, configCompareResult, flags,isAtvDevice == JNI_TRUE ? 1 : 0, user_data_directory);}static bt_callbacks_t sBluetoothCallbacks = {sizeof(sBluetoothCallbacks),adapter_state_change_callback,adapter_properties_callback,remote_device_properties_callback,device_found_callback,discovery_state_changed_callback,pin_request_callback,ssp_request_callback,bond_state_changed_callback,address_consolidate_callback,le_address_associate_callback,acl_state_changed_callback,callback_thread_event,dut_mode_recv_callback,le_test_mode_recv_callback,energy_info_recv_callback,link_quality_report_callback,generate_local_oob_data_callback,switch_buffer_size_callback,switch_codec_callback};
-
当我们拉起我们的 蓝牙进程服务时, 将触发 initNative 调用
-
此时通过 sBluetoothInterface->init( &sBluetoothCallbacks
- 将我们的 sBluetoothCallbacks 传递出去。
-
system/btif/src/bluetooth.cc
static int init(bt_callbacks_t* callbacks, bool start_restricted,bool is_common_criteria_mode, int config_compare_result,const char** init_flags, bool is_atv,const char* user_data_directory) {set_hal_cbacks(callbacks); // 直接将 callbacks 给了 bt_hal_cbacks}void set_hal_cbacks(bt_callbacks_t* callbacks) { bt_hal_cbacks = callbacks; }
- 这里所有的 hal 回调都将回调到 sBluetoothCallbacks 中。
4. 小结
bt_jni_thread
是 AOSP 蓝牙系统中 native → Java 方向的专用线程桥梁,同时也承担部分 profile 层轻量控制任务的执行职责。它不是全能线程,但在 JNI 回调中不可或缺。
为什么要有 bt_jni_thread
原因 | 解释 |
---|---|
保证 JVM attach | 不同 native 回调线程不一定 attach 了 JVM |
避免跨线程调用 Java | Android 不允许非 JVM 线程直接访问 Java |
提高模块解耦 | 各个 profile 回调逻辑统一封装、集中调度 |
提升线程安全 | 所有 JNI 回调集中在一个线程处理,避免竞态 |
核心职责
类别 | 说明 |
---|---|
上行事件调度 | 负责 native → Java 的事件回调,例如设备发现、配对状态变化等 |
JVM 安全桥梁 | 由于 native 回调来自蓝牙堆栈中的多个线程,bt_jni_thread 保证在 JVM 附着线程中调用 Java |
profile 模块轻量任务 | 某些模块(如 HFP client、AVRCP、GATT)内部异步任务也在此线程中执行 |
使用场景举例
上行事件来源 | 对应 Java 回调函数 |
---|---|
adapter_state_changed_cb | onAdapterStateChanged() |
device_found_cb | onDeviceFound() |
bond_state_changed_cb | onBondStateChanged() |
acl_state_changed_cb | onConnectionStateChanged() |
btif_gatt_client_* 回调 | GATT 连接、通知、读写特征值等 |
hfp_client_callbacks | HFP 状态变化、音频连接事件 |
看到这里大家可以思考一下问题:
- native <-> java 上下行的事件, 一定都要 放在 bt_jni_thread 线程中执行吗?
- 答案
相关文章:
【android bluetooth 框架分析 01】【关键线程 3】【bt_jni_thread 线程介绍】
1. bt_jni_thread 职责介绍 bt_jni_thread 这个线程的作用是专门负责处理蓝牙 JNI 层的消息循环,也可以说是 C 层和 Java 层交互的桥梁线程。 1.1 什么是 JNI 层?为什么需要这个线程? JNI(Java Native Interface)是 …...
CCF - GESP Python三级考试题目示例
CCF - GESP Python三级考试题目示例,你可以根据实际需求进行调整。 一、单选题(每题2分,共30分) 以下关于Python中函数的说法,错误的是( ) A. 函数定义使用def关键字 B. 函数必须有返回值 C.…...
Windows10系统更改盘符
Windows10系统更改盘符 导航 文章目录 Windows10系统更改盘符导航更改盘符 更改盘符 按下wini,再按k进入磁盘管理器 右击你想更改的磁盘,选择“更改驱动器号”和路径,选择好驱动器号后确定即可...
软件功能性测试有多重要?功能性测试工具有哪些?
软件功能性测试是指对软件应用程序进行的测试,旨在验证软件的每一个功能是否按预定要求正常运作。功能性测试通常基于软件的需求文档,从用户的角度出发,确保所有功能都能够满足用户的需求。 软件功能性测试在软件开发生命周期中扮演着至关重…...
未来生态映像:杭州的科技自然协奏曲
故事背景 故事发生在2050年的中国杭州,这座千年古城已蜕变为科技与自然完美交融的未来生态之城。从晨曦微露的西湖到暮色中的良渚文化村,每个角落都上演着人类智慧与自然韵律的动人对话。 故事摘要 当第一缕阳光亲吻西湖的生态浮岛,无人机携带…...
电商核心指标解析与行业趋势:数据驱动的增长策略【大模型总结】
电商核心指标解析与行业趋势:数据驱动的增长策略 在电商领域,数据是决策的核心。从流量监测到用户行为分析,从价格策略到技术赋能,每一个环节的优化都离不开对核心指标的深度理解。本文结合行业最新趋势与头部平台实践࿰…...
ubuntu自动更新--unattended-upgrades
ubuntu自动更新--unattended-upgrades 1 介绍2 发展历程3 配置与使用4 disable Auto update服务命令 参考 1 介绍 Unattended-Upgrades 是一个用于自动更新 Debian 及其衍生系统(如 Ubuntu)的工具。它的主要功能是自动检查、下载并安装系统更新…...
在Ubuntu 22.04上配置【C/C++编译环境】
在Ubuntu 22.04上配置C/C编译环境 如果你想在Ubuntu 22.04上编译和运行C或C程序,首先需要安装一个合适的编译器和相关工具。本文将为你提供详细的安装建议和操作步骤,帮助你快速搭建开发环境。 准备工作 在开始之前,确保你的系统可以通过终…...
费马小定理
快速幂 理论 a n a a ⋯ a a^n a a \cdots a anaa⋯a,暴力的计算需要 O(n) 的时间。 快速幂使用二进制拆分和倍增思想,仅需要 O(logn) 的时间。 对 n 做二进制拆分,例如, 3 13 3 ( 1101 ) 2 3 8 ⋅ 3 4 ⋅ 3 1 3^{13}…...
什么是音频预加重与去加重,预加重与去加重的原理是什么,在什么条件下会使用预加重与去加重?
音频预加重与去加重是音频处理中的两个重要概念,以下是对其原理及应用条件的详细介绍: 1、音频预加重与去加重的定义 预加重:在音频信号的发送端,对音频信号的高频部分进行提升,增加高频信号的幅度,使其在…...
程序代码篇---时间复杂度空间复杂度
文章目录 前言一、时间复杂度(Time Complexity)定义1. 常见时间复杂度类型2. 计算规则忽略常数项保留最高阶项循环嵌套递归算法 3. 代码示例(1)𝑂(1):常数时间(2)𝑂(&…...
化工企业数字化转型:从数据贯通到生态重构的实践路径
一、战略定位:破解行业核心痛点 化工行业面临生产安全风险高(全国危化品企业事故率年增5%)、能耗与排放压力大(占工业总能耗12%)、供应链协同低效(库存周转率低于制造业均值30%)三大挑战。《石…...
Mysql备忘记录
1、简介 Mysql操作经常忘记命令,本文将持续记录Mysql一些常用操作。 2、常见问题 2.1、忘记密码 # 1、首先停止Mysql服务 systemctl stop mysqld # windows 从任务管理器里面停 # 2、更改配置文件 my.cnf (windows是 ini文件) vim /etc/my.cnf 在[mysqld]下面添…...
Python 爬取 1688.item_get_factory 接口:获取工厂档案信息实战指南
在电商采购和供应链管理中,了解供应商的工厂信息是至关重要的一步。1688 作为国内领先的 B2B 平台,提供了丰富的供应商和工厂档案信息。通过 item_get_factory API 接口,开发者可以获取工厂的详细信息,包括工厂名称、地址、联系方…...
【Proteus仿真】【32单片机-A009】矩阵按键系统设计
目录 一、主要功能 二、使用步骤 三、硬件资源 四、软件设计 五、实验现象 联系作者 一、主要功能 1、按键值与LCD显示 2、矩阵按键 二、使用步骤 系统运行后,LCD1602显示当前的按键值; 当按下不同按键后显示屏更新对应的按键值。 三、硬件资…...
【11408学习记录】英语语法精析:主从复合句之定语从句完全指南——从规则到实战例句一网打尽
定语从句 英语语法总结——主从复合句定语从句定语从句的写法先行词的作用分类定语从句的使用补充 每日一句词汇第一步:找谓语第二步:断开第三步:简化第一句第二句第三句第四句 英语 语法总结——主从复合句 定语从句 定语从句就是一个句子…...
go游戏后端开发31:麻将游戏的碰牌与胡牌逻辑
以下是润色后的版本: 1. 碰牌逻辑 1.1 触发碰牌 当一个玩家弃牌后,其他玩家可以选择碰牌。如果当前玩家决定碰牌,系统需要通知所有玩家这一操作。碰牌操作完成后,当前玩家需要出一张牌,系统同样需要通知所有玩家。 …...
vLLM实战:多机多卡大模型分布式推理部署全流程指南
1. 环境准备与基础配置 1.1 系统要求 依赖组件: # 基础工具安装 sudo apt-get install -y lsof git-lfs nvidia-cuda-toolkit1.2 虚拟环境配置 使用conda创建隔离环境,避免依赖冲突: conda create -n vllm python3.10 -y conda activate…...
RAI Toolbox详解
RAI Toolbox详解 摘要 RAI Toolbox是一个综合性的工具集,旨在帮助开发者和AI系统利益相关者更负责任地开发和监控AI系统,并做出更好的数据驱动决策。本文将详细介绍RAI Toolbox的功能、使用场景以及与类似AI项目的对比,帮助读者全面了解RAI…...
Python高级爬虫之js逆向+安卓逆向1.3节:Python数据类型
目录 引言: 1.3.1 两大数据类型 1.3.2 不可变数据类型 1.3.3 可变数据类型 1.3.4 再不跳槽就老了 引言: 大神薯条老师的高级爬虫安卓逆向教程: 这套爬虫教程会系统讲解爬虫的初级,中级,高级知识,涵盖…...
The 2024 CCPC National Invitational Contest (Changchun),第17届吉林省赛 C
补题链接 题解是什么意思呢,首先我们需要知道的是,斐波那契数列可以用矩阵快速幂和矩阵乘法求解 ( F ( n 1 ) F ( n ) ) ( 1 1 1 0 ) ( F ( n ) F ( n − 1 ) ) M ( F ( n ) F ( n − 1 ) ) \begin{pmatrix}F(n1) \\ F(n)\end{pmatrix} \begin{pma…...
GPT-4o-image模型:开启AI图片编辑新时代
在生成式AI技术爆发式迭代的今天,智创聚合API率先突破多模态创作边界,正式发布集成GPT-4o-image模型的创作平台,以“文生图-图生图-循环编辑”三位一体的技术矩阵,重新定义数字内容生产流程。生成图像效率较传统工具提升300%&…...
Java流程控制【if分支三种形式】
if分支三种形式 ①if(条件表达式) 代码; }②if(条件表达式){ 代码1; }else{ 代码2; }③if(条件表达式1){ 代码1; }else if(条件表达式2){ 代码2; }else if(条件表达式3){ 代码3;...... }else{ 代码n&#…...
2025 年陕西消防设施操作员考试攻略:历史文化名城的消防传承与创新
陕西拥有丰富的历史文化遗产,众多古建筑分布其中,同时也在不断推进现代化建设,消防工作面临传承与创新的双重任务,这在考试中也有所体现。 考点融合与特色:一方面,古建筑的消防保护是重点,包…...
关于node.js 隐式修改数组长度的问题
// 删除手牌 proto.remove function (handCards, _cards) { let saveHandCards this.deepCloneTL(handCards);//保存原先的牌 console.warn("删除手牌000:",JSON.stringify(handCards)); console.warn("删除手牌111:",JSON.stringify(_card…...
探索文件与流的世界:File、InputStream 和 OutputStream 的奇妙之旅
目录 一、File 类——文件管理的小能手 1. 创建和删除文件 2. 检查文件属性 3. 路径操作 二、InputStream——读取数据的管道 1. 读取单个字节 2. 批量读取字节 3. 关闭流 三、OutputStream——写入数据的通道 1. 写入单个字节 2. 批量写入字节 3. 刷新和…...
nginx镜像创建docker容器,及其可能遇到的问题
一、基本流程 1、目录准备 我喜欢把文件放在/data下。 # 进入根目录的data目录 cd /data# 创建nginx目录 mkdir nginx# 进入nginx目录 cd nginx# 创建conf目录 mkdir conf# 进入conf目录 cd conf# 创建conf.d目录 mkdir conf.d 2、文件准备 (1)nginx…...
测试用例 [软件测试 基础]
目录 测试用例 1. 概念 1.1 什么是测试用例 1.2 什么是要素 1.3 为什么需要测试用例 2. 设计测试用例的万能公式 2.1 常规思维 逆向思维 发散性思维 2.2 万能公式 3. 设计测试用例的方法 3.1 基于需求的设计方法 3.2 具体的设计方法 3.3 更多用例练习 测试用例 …...
树的直径 (dp或贪心)
B4016 树的直径 - 洛谷 题目大意: 给定一棵 n 个结点的树,树没有边权。请求出树的直径是多少,即树上的最长路径长度是多少。 思路: 树形 d p dp dp 求树的直径 定义 d [ x ] d[x] d[x] 表示以 x x x 节点出发走向 x x x 的…...
C++ 入门二:C++ 编程预备知识
一、使用 Qt Creator 创建 C 工程 1.1 打开软件 在计算机中找到 Qt Creator 的应用程序图标并双击打开它。如果是首次打开,可能需要进行一些初始化设置,如选择默认的开发环境等。 1.2 选择 C 工程 打开 Qt Creator 后,在欢迎界面中点击 “…...
IT管理思路
甲方CIO和IT管理者-如何做好组织级IT能力提升_哔哩哔哩_bilibili...
C++17模板编程与if constexpr深度解析
一、原理深化 1.1 模板编程 1.1.1 编译器如何处理模板(补充) 模板的实例化机制存在两种模式: 隐式实例化:编译器在遇到模板具体使用时自动生成代码,可能导致多翻译单元重复实例化,增加编译时间。显式实…...
指令层级:训练大型语言模型优先处理特权指令
指令层级:训练大型语言模型优先处理特权指令 The Instruction Hierarchy: Training LLMs to Prioritize Privileged Instructions关键要点:快速浏览:1. 引言(Introduction)2. 指令层级(The Instruction Hie…...
高效人脸关键点检测算法HRNet-Facial-Landmark-Detection
高效人脸关键点检测算法HRNet-Facial-Landmark-Detection 300 W data 数据标注格式 helen/trainset/1271089376_2.jpg,2.105,642.5,643.5,453.88947199999996,562.431791,457.882374,607.338625,471.39681399999995,655.136824,489.458724,702.023327,508.36972599999996,743.…...
LeetCode 3375 题解
题解 如题所示,允许暴力,虽然是暴力,但复杂度也就O(n) 还是如昨天的题目一样,使用Set.add的方法去判断即可 分三种情况 因为是set集合的原因,所以可以排除值相同的原因 当遍历数组有值小于k就return -1 当遍历数组遇…...
leetcode_数组 189. 轮转数组
189. 轮转数组 给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数 示例 1: 输入: nums [1,2,3,4,5,6,7], k 3输出: [5,6,7,1,2,3,4] 示例 2: 输入:nums [-1,-100,3,99], k 2输出:[3,99,-1,-100] 思…...
MySQL | 三大日志文件
Undo Log(回滚日志) 实现原理与分类 原理:Undo Log 记录的是数据修改前的旧值,通过这些旧值可以将数据恢复到修改之前的状态。它采用的是逻辑日志,即记录的是如何撤销操作,而不是物理数据的实际值。 分类…...
GIS-AI 融合引擎架构:智慧景区导览系统的毫秒级响应与千级并发优化实战
本文面向 文旅行业技术决策者、GIS 开发者、AI 算法工程师,旨在解决传统景区导览系统 定位精度低、交互体验差、运营成本高 的核心痛点,提供从技术选型到落地部署的全链路解决方案。 如需获取智慧景区导览系统解决方案请前往文章最下方获取,如…...
WSA(Windows 安卓子系统)过检测教程
windows安卓子系统WSA的root和magisk的安装教程 安卓子系统WSLWSA的rootmagisk安装 WSA(Windows 安卓子系统)过检测的方法与思路 一、引言 Windows 安卓子系统(WSA)为 Windows 用户提供了在电脑上运行安卓应用的便利。然而&…...
Spark原理及代码
一、 Spark运行架构 运行架构 Spark 框架的核心是一个计算引擎,整体来说,它采用了标准 master-slave 的结构。 Spark 框架有两个核心组件: 1、Driver Spark 驱动器节点,用于执行 Spark 任务中的 main 方法,负责实…...
esp32cam -> 服务器 | 手机 -> 服务器 直接服务器传输图片
服务器先下载python : 一、Python环境搭建(CentOS/Ubuntu通用) 一条一条执行 安装基础依赖 # CentOS sudo yum install gcc openssl-devel bzip2-devel libffi-devel zlib-devel # Ubuntu sudo apt update && sudo apt install b…...
RHCSA Linux系统 数据流和重定向 tee 命令
一.数据流和重定向 1. 数据流 (1) 标准输入(stdin,代码 0):默认从键盘获取输入,只读。 (2) 标准输出(stdout,代码 1):命令执行正确信息默认输出到屏幕,只写…...
libev实现Io复用及定时器事件服务器
客户端和服务器都绑定在了enp2s0网卡,需要SERVER_IP和SERVER_PORT改为其ip,注意不能是127.0.0.1,因为这个是lo虚拟网口。 安装libev sudo apt-get install libev-dev客户端: #include <iostream> #include <string>…...
【项目实训项目博客】prompt初版实践
通过对camel技术的理解,我们向其中添加了市场营销角色的prompt 初版设计如下: chatchainconfig.json { "chain": [ { "phase": "DemandAnalysis", "phaseType": "SimplePhase", "max_turn_step…...
底盘---全向轮(Omni Wheel)
一、基本定义与起源 定义: 全向轮是一种通过在主轮外周安装多个 垂直于主轮轴线的横向小滚轮 实现多向移动的轮式结构。小滚轮可自由转动,允许设备在纵向(主轮驱动方向)和横向(小滚轮滚动方向)同时受力&…...
Python标准库json完全指南:高效处理JSON数据
一、json库概述 JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,Python的json模块提供了JSON数据的编码和解码功能。该模块可以将Python对象转换为JSON字符串(序列化),也可以将JSON字符串转换为Python对象…...
用一个实际例子快速理解MCP应用的工作步骤
已经有很多的文章介绍MCP server,MCP Client工作原理,这里不做太多介绍。但是很多介绍都只是侧重介绍概念,实际的工作原理理解起来对初学者还是不太友好。本文以一个智能旅游咨询系统为例,详细说明在利用 Model Context Protocol&…...
Element Plus 图标使用方式整理
Element Plus 图标使用方式整理 以下是 Element Plus 图标的所有使用方式,包含完整代码示例和总结表格: 1. 按需引入图标组件 适用场景:仅需少量图标时,按需导入减少打包体积 示例代码: <template><div>…...
力扣题解:142. 环形链表 II
在链表学习中,我们已经了解了单链表和双链表,两者的最后一个结点都会指向NULL;今天我们介绍的循环列表则不同,其末尾结点指向的这是链表中的一个结点。 循环链表是一种特殊类型的链表,其尾节点的指针指向头节点&#…...
图灵逆向——题七-千山鸟飞绝
目录列表 过程分析headers头部M参数分析载荷x参数分析响应数据解密分析 代码实现 一进来还是一个无限debugger,前面有讲怎么过,这里直接过掉~ 老规矩,养成习惯,先看请求头里有没有加密参数发现好像是有个M,它是个32位…...