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 線程中執行嗎?
- 答案