【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 职责总结

  1. 消息分发器(Message Dispatcher)
    所有 JNI 相关的调用(比如设备连接状态回调、扫描结果等),都通过这个线程发送回 Java 层。

  2. 避免阻塞主线程(Non-blocking)
    蓝牙操作可能会耗时,比如搜索设备、建立连接等。如果不单独开线程处理,会拖慢系统,甚至 ANR(应用无响应)。

  3. 保证线程安全(Thread Safety)
    蓝牙操作涉及很多共享资源(socket、状态等),用一个专门的线程可以避免多线程访问冲突。

  4. 事件循环(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_cbacktBTIF_CBACK*你希望在 bt_jni_thread 中被调用的回调函数
eventuint16_t表示是什么事件,通常用枚举,如 BTIF_DM_CB_DISCOVERY_STARTED
p_paramschar*回调函数要用的参数
param_lenint参数长度
p_copy_cbacktBTIF_COPY_CBACK*自定义的“深拷贝”函数,用于复杂结构的复制(可选)
  1. 分配内存

    tBTIF_CONTEXT_SWITCH_CBACK* p_msg = (tBTIF_CONTEXT_SWITCH_CBACK*)osi_malloc(...);

    创建一个消息对象 p_msg,用来封装要执行的任务。

  2. 设置元数据

    p_msg->hdr.event = BT_EVT_CONTEXT_SWITCH_EVT; p_msg->p_cb = p_cback; p_msg->event = event;

    标记这个是“上下文切换事件”,并把事件编号和要执行的回调函数绑定进来。

  3. 拷贝参数
    如果参数非空,尝试用 p_copy_cback() 深拷贝参数;否则直接用 memcpy() 复制。

  4. 投递给 JNI 线程执行

    do_in_jni_thread(base::Bind(&bt_jni_msg_ready, p_msg));

    把这个封装好的消息送到 bt_jni_thread 中,由 bt_jni_msg_ready() 函数去调度并执行真正的回调。

  5. 返回成功

    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_ONBT_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_BONDINGBOND_BONDED
acl_state_changed_cbACL 连接状态变化蓝牙链路层连接或断开时调用(所有设备连接都会有 ACL 层)
thread_evt_cb通知线程附加或分离JNI 层使用,用于线程绑定和解绑当前线程到 JVM(Attach/Detach)
dut_mode_recv_cbDUT 模式接收数据当设备处于 DUT 模式(测试模式)下收到测试数据时触发(调试使用)
le_test_mode_cbLE 测试模式回调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
避免跨线程调用 JavaAndroid 不允许非 JVM 线程直接访问 Java
提高模块解耦各个 profile 回调逻辑统一封装、集中调度
提升线程安全所有 JNI 回调集中在一个线程处理,避免竞态

核心职责

类别说明
上行事件调度负责 native → Java 的事件回调,例如设备发现、配对状态变化等
JVM 安全桥梁由于 native 回调来自蓝牙堆栈中的多个线程,bt_jni_thread 保证在 JVM 附着线程中调用 Java
profile 模块轻量任务某些模块(如 HFP client、AVRCP、GATT)内部异步任务也在此线程中执行

使用场景举例

上行事件来源对应 Java 回调函数
adapter_state_changed_cbonAdapterStateChanged()
device_found_cbonDeviceFound()
bond_state_changed_cbonBondStateChanged()
acl_state_changed_cbonConnectionStateChanged()
btif_gatt_client_* 回调GATT 连接、通知、读写特征值等
hfp_client_callbacksHFP 状态变化、音频连接事件

看到这里大家可以思考一下问题:

  • native <-> java 上下行的事件, 一定都要 放在 bt_jni_thread 线程中执行吗?
  • 答案

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/77316.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于视觉语言模型的机器人实时探索系统!ClipRover:移动机器人零样本视觉语言探索和目标发现

作者&#xff1a;Yuxuan Zhang 1 ^{1} 1, Adnan Abdullah 2 ^{2} 2, Sanjeev J. Koppal 3 ^{3} 3, and Md Jahidul Islam 4 ^{4} 4单位&#xff1a; 2 , 4 ^{2,4} 2,4佛罗里达大学电气与计算机工程系RoboPI实验室&#xff0c; 1 , 3 ^{1,3} 1,3佛罗里达大学电气与计算机工程系F…

SpringBoot和微服务学习记录Day2

微服务 微服务将单体应用分割成更小的的独立服务&#xff0c;部署在不同的服务器上。服务间的关联通过暴露的api接口来实现 优点&#xff1a;高内聚低耦合&#xff0c;一个模块有问题不影响整个应用&#xff0c;增加可靠性&#xff0c;更新技术方便 缺点&#xff1a;增加运维…

网站集群批量管理-Ansible剧本与变量

复盘内容&#xff1a;链接指北 查看ansible命令文档 ansible-doc -s systemd一、剧本 何为剧本: playbook 文件,用于长久保存并且实现批量管理,维护,部署的文件. 类似于脚本存放命令和变量 剧本yaml格式,yaml格式的文件:空格,冒号. 剧本未来我们批量管理,运维必会的内容. …

如何在Dify中安装运行pandas、numpy库(离线、在线均支持,可提供远程指导)

pandas和numpy这两个库是数据科学和数据分析中经常使用的工具包&#xff0c;原生的Dify无法直接使用这两个库&#xff0c;需要手动安装后才可以使用。本文将介绍如何在Dify中安装pandas和numpy&#xff0c;并在代码执行节点中运行使用pandas和numpy。 Dify的代码执行节点中的py…

Helm核心概念与常见操作介绍

在管理Kubernetes集群里的应用时&#xff0c;Helm能帮上大忙&#xff0c;它把应用的部署、升级和管理变得简单多了&#xff0c;有如是Kubernetes的 “应用商店”。 Helm的三个重要概念 三大概念最直接的理解&#xff1a;Helm 安装 charts 到 Kubernetes 集群中&#xff0c;每…

rkmpp 解码 精简mpi_dec_test.c例程

rkmpp 解码流程&#xff08;除 MPP_VIDEO_CodingMJPEG 之外&#xff09; 源码 输入h264码流 输出nv12文件 /** Copyright 2015 Rockchip Electronics Co. LTD** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file exce…

用一个实际例子快速理解MCP应用的工作步骤

已经有很多的文章介绍MCP server&#xff0c;MCP Client工作原理&#xff0c;这里不做太多介绍。但是很多介绍都只是侧重介绍概念&#xff0c;实际的工作原理理解起来对初学者还是不太友好。本文以一个智能旅游咨询系统为例&#xff0c;详细说明在利用 Model Context Protocol&…

【LeetCode 题解】数据库:1321.餐馆营业额变化增长

一、问题描述 本题给定了一个名为 Customer 的表&#xff0c;记录了餐馆顾客的交易数据&#xff0c;包括顾客 ID、姓名、访问日期和消费金额。作为餐馆老板&#xff0c;我们的任务是分析营业额的变化增长情况&#xff0c;具体来说&#xff0c;就是计算以 7 天&#xff08;某日…

【Python】读取xlsb或xlsx的单一或连续单元格工具类

代码主要来自Kimi.ai&#xff0c;有修改。 优先使用工作表序号索引工作表&#xff0c;序号从1开始。 运行需要先安装openpyxl和pyxlsb两个第三方库。 import openpyxl from openpyxl.utils import range_boundaries from pyxlsb import open_workbook as open_xlsbclass Exc…

【蓝桥杯】动态规划:背包问题

这篇文章主要记录动态规划方面的学习。 动态规划的核心思想: 把大问题分解成小问题,记住小问题的解,避免重复计算。 动态规划(DP)的三大特点: ①最优子结构:大问题的最优解可以由小问题的最优解推导出来 ②重叠子问题:在求解过程中会反复遇到相同的小问题 ③无后效…

华为数字芯片机考2025合集1已校正

单选 1&#xff0e;以下低功耗措施中&#xff0c;哪种不是降低电路翻转率的方法? A.在不进行算术运算的时候&#xff0c;使这些模块的输入保持不变&#xff0c;不让新的操作数进来 B.采用Gray 码或One‐hot 码作为状态机编码 C.减少电路中的glitch D.重新安排“if‐else”表达…

React 列表渲染

开发环境&#xff1a;Reacttsantd 你可能经常需要通过 JavaScript 的数组方法 来操作数组中的数据&#xff0c;从而将一个数据集渲染成多个相似的组件。在这篇文章中&#xff0c;你将学会如何在 React 中使用 filter() 筛选需要渲染的组件和使用 map() 把数组转换成组件数组。 …

力扣刷题DAY11(动态规划-线性DP)

一、最长上升子序列 300. 最长递增子序列 &#xff08;一&#xff09;初版代码 class Solution { public:int lengthOfLIS(vector<int>& nums) {int n nums.size();vector<int> f(n 1, 1); //初始化为1&#xff0c;因为每个数至少可以作为一个单独的序列in…

DFS--

数字的全排列 #include <bits/stdc.h> using namespace std;//最大的排列数目 const int N10; int n; //存储排列的路径 int path[N]; //标记数字是否已经被使用 bool st[N];void dfs(int u){//到达递归边界&#xff0c;输出一个排列if(un){//输出循环for(int i0; i<…

栈与队列及其基础应用

一.栈 1.栈的定义 栈是一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out&#xff09;的原则。其结构可以参考羽毛…

openEuler-22.03-LTS-SP3 编译安装 Greenplum-db 6.20.0

openEuler-22.03-LTS-SP3 编译安装 Greenplum-db 6.20.0 1、配置 yum 华为源2、安装依赖3、源码安装 openssl 1.0.1u3.1、openssl 1.1.1 降级到 openssl 1.0.1 4、源码安装 python 2.75、使用 pip3 安装 Python 相关依赖6、编译安装 Greenplum-db 6.20.06.1、修改配置6.2、基于…

机器学习02——概要

一、简介 机器学习是一门在没有明确编程的情况下让计算机学习的科学。 监督学习是有目标的&#xff0c;输入数据对应明确的输出&#xff1b;无监督学习则是“探索”型的&#xff0c;模型的目标是从数据中发现潜在的模式或结构&#xff0c;而不需要预先知道标签。 二、机器学…

swift-08-属性、汇编分析inout本质

一、Swift中跟实例相关的属性可以分为2大类 1.1 存储属性&#xff08; Stored Property&#xff09; 类似于成员变量这个概念 存储在实例的内存中 结构体、类可以定义存储属性 枚举不可以定义存储属性&#xff08;因为枚举只存储关联值和case&#xff09; 1.2 计算属性&am…

【HarmonyOS Next之旅】DevEco Studio使用指南(十二)

目录 1 -> Code Linter代码检查 2 -> 配置代码检查规则 3 -> 查看/处理代码检查结果 1 -> Code Linter代码检查 Code Linter针对ArkTS/TS代码进行最佳实践/编程规范方面的检查。 可根据扫描结果中告警提示手工修复代码缺陷&#xff0c;或者执行一键式自动修复…

前端vue项目打包成桌面端exe应用

主要 使用 Electron将 vue项目打包为 exe 1.首先下载Electron git clone https://github.com/electron/electron-quick-start cd electron-quick-start npm install安装完依赖之后 npm start运行成功 注意&#xff1a;如果你的项目使用了VueRouter&#xff0c;那么切记&…