【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.pswp.cn/diannao/77316.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/77316.shtml
英文地址,請注明出處:http://en.pswp.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;那么切記&…