Android GATT 連線過程原始碼分析
Android GATT 連線過程原始碼分析
低功耗藍芽(BLE)裝置的通訊基本協議是 GATT, 要操作 BLE 裝置,第一步就是要連線裝置,其實就是連線 BLE 裝置上的 GATT service。
結合上一篇文章,我這裡結合原始碼,分析一下 GATT 連線的流程,以及各個模組是怎麼相互互動的。注意本文依據的是 Android 4.4 的原始碼。
應用框架層
首先,一般應用層都是通過呼叫如下方法,來建立一個 GATT 連線的:
|
這裡呼叫了方法 connectGatt()
,我們來看一下原始碼,程式碼在 /frameworks/base/core/java/android/bluetooth/BluetoothDevice.java
:
|
這裡通過 BluetoothAdapter
獲取 managerService
,這是通過 Binder
機制繫結的一個遠端藍芽管理服務,進而獲得 iGatt
,同樣,這也是一個遠端的 Binder 物件,這是一個非常關鍵的物件,後面會詳細講。然後呼叫了 BluetoothGatt
的 connect()
方法,需要注意這裡有一個引數 autoConnect
, 如果為 false
,則表示直接連線,true
表示自動連線,意思是等到裝置可用,則會自動連線上。
接下來看 gatt.connect()
的實現,程式碼在/frameworks/base/core/java/android/bluetooth/BluetoothGatt.java
:
|
這裡面關鍵的一句是 registerApp(callback)
,這是向底層註冊 App,底層就知道有 App 在使用藍芽,有藍芽訊息的時候,就通過回撥通知上層的 App。BLE 幾乎所有操作都是通過非同步回撥實現的,就是通過這個你自定義的 BluetoothGattCallback
來通知你的應用的。接下來我們繼續看 registerApp()
:
|
可以看到,這裡呼叫了 mService.registerClient()
,這裡的 mService
就是第一步建立的 BluetoothGatt
物件的時候傳入的 IBluetoothGatt
型別的 Binder
物件。對於這個函式的名字為什麼叫 registerClient
,這是因為,在 Binder 機制中,被繫結的 Service 作為稱為服務端,發起繫結的一方是客戶端。
在繼續往下看 registerClient()
這個函式之前,我們回憶一下,我們的目標是連線 BLE 裝置,到這一步了,還沒有看到連線動作的蹤影。這是怎麼回事?前面我們說過,藍芽幾乎所有的操作都是依靠回撥實現,我們先來看一下這裡的 mBluetoothGatt
的實現,看原始碼中,這個回撥物件非常大,包含所有的 Gatt 回撥動作,我們這裡主要看 onClientRegistered()
方法:
|
這個回撥方法有兩個引數 status
和 clientIf
,前者很好理解,就是表示註冊客戶端是否成功。clientIf
表示從底層返回的一個 id,用來唯一標示這個客戶端,接下來的所有客戶端的操作請求,都需要帶上這個 id。
這個回撥方法中做的事情比較清晰,特別注意到 mService.clientConnect(...)
,這裡開始呼叫 Service 介面開始發起連線了。
從程式碼中可以看到,mService
是一個很關鍵的物件,但是這個物件是從哪裡來的呢?
應用框架和藍芽服務的銜接: Binder
在第一段程式碼的分析中就提到了 iGatt
物件,從 BluetoothGatt
的建構函式可以看出,其實 mService = iGatt
,iGatt
是 IBluetoothGatt
介面的 Binder。
我們看一下 BluetoothAdapter
是怎麼獲得的,BluetoothAdapter.getDefaultAdapter()
:
|
這裡是一個單例模式,通過系統API ServiceManager.getService()
獲得的,這裡大致邏輯就是,在系統那個啟動的時候,Android 會啟動一些系統服務並通過 ServiceManager
管理,具體我就不往下深究了,可以具體看一下老羅的這篇文章。這裡直接給出結論,這裡 Binder 物件對應的服務是 BluetoothManagerService
,程式碼在 /frameworks/base/services/java/com/android/server/BluetoothManagerService.java
。
我們看一下怎麼從 BluetoothManagerService
中獲取到 IBluetoothGatt
的 Binder 的。注意到 BluetoothManagerService
中有一個方法 bluetoothStateChangeHandler()
,衝方法名就大概可以知道這個方法是在藍芽狀態變化的時候,做一些處理的。跟蹤以下這個函式的呼叫的地方,就能驗證我們的猜想是對的。這一塊和本文的關係不大,我們現在來看一下 bluetoothStateChangeHandler()
的具體實現:
|
看到 if (isUp)
這個分支中,會繫結到以 IBluetoothGatt 的類名為 Action Name 的服務,也就是 action="android.bluetooth.IBluetoothGatt"
。我們在 /packages/apps/Bluetooth
這個 App 的 AndroidManifest.xml
中找到如下的宣告:
|
我們找到了,原來是繫結到了 com.android.bluetooth.gatt.GattService
上了。如果繫結成功,會回撥 mConnection
的 onServiceConnected()
,其實現如下:
|
如果繫結的類名是 GattService
,就會發送MESSAGE_BLUETOOTH_SERVICE_CONNECTED
訊息給 mHandler
,訊息的第一個引數為 SERVICE_IBLUETOOTHGATT
,我們接下來看 mHandler 怎麼處理的:
|
最終獲得 IBluetoothGatt
的 Binder,並賦值給 mBluetoothGatt
,最後通過如下介面,返回給前面的 BluetoothGatt
。
至此,通過 Binder 機制,完成了應用框架 API 到 Service 的繫結。別忘了我們的目標:分析BLE連線的流程。通過前面的程式碼分析我們知道,連線的時候,先呼叫了 ‘mService.registerClient()’,然後在註冊成功後,呼叫了 mService.clientConnect()
真正發起連線。我們知道了,這個 mService
實際上就是 com.android.bluetooth.gatt.GattService
。我們接下來分析這個 Service,也就到了藍芽服務層了。
藍芽服務
藍芽服務的程式碼在 packages/app/Bluetooth
,編譯以後成 Bluetooth.apk,安裝在 /system/app/
目錄下面,GattService
執行在 com.android.bluetooth
程序中。我們接著來看 Binder
的 registerClient()
介面,這個 Binder 是 GattService
的一個內部類:
|
可以看到,實際上這裡還是呼叫了 GattService
的 registerClient
方法:
|
這裡首先是把 uuid
以及對應的 callback
儲存到一個 mClientMap
中去,這裡從名字上我們就能大概清楚這裡的作用,這裡的 uuid 是客戶端的唯一標示, uuid 對應了客戶端的回撥函式 callback
。接下來,呼叫了 gattClientRegisterAppNative()
介面向底層協議棧註冊客戶端,看一下函式定義:
|
這裡可以看出,實際上是客戶端的標示 – UUID 註冊到底層去,UUID 是 128 bit, 正好用兩個 long
型的引數表示。這個函式是 JNI 的申明,具體的實現就在對應的 C/C++ 程式碼中。
藍芽服務和 HAL 的呼叫:JNI
上面的 gattClientRegisterAppNative()
對應的 JNI 的程式碼在哪裡呢?通過檢視 AndroidManifest.xml
,我們知道 Bluetooth 的自定義 Application 是 AdapterApp
,裡面有這樣的程式碼:
|
這裡是載入了 libbluetooth_jni.so
動態庫。我們再看 jni 目錄的 Android.mk
,這裡正好是生成 libbluetooth_jni
的編譯指令碼。這樣我們就知道了對應的 C/C++ 程式碼在 com_android_bluetooth_gatt.cpp
:
|
這是註冊 JNI 函式的標準方法,關於 JNI 的詳細語法,可以參考這裡。可以找到對應的函式實現如下:
|
這裡呼叫了 sGattIf
的 client
的 register_client()
方法,這裡還是把客戶端的標示 UUID 傳遞下去。這裡的 sGattIf
是什麼呢?
|
它是一個 btgatt_interface_t
型別的變數,sGattIf
的初始化在 initializeNative()
中,這個函式在 GattService.start()
方法中被呼叫了,這個函式定義如下:
|
注意這裡首先通過 getBluetoothInterface()
獲得整個底層的藍芽介面。我們重點來關注以下 sGattIf
是怎麼來的?
看到這裡呼叫了 btIf->get_profile_interface(BT_PROFILE_GATT_ID))
來獲取 sGattIf
例項。
現在來看 btgatt_interface_t
是在哪裡定義的,我們看一下這個檔案 include 的標頭檔案中,最有可能就是在 #include "hardware/bt_gatt.h"
,這就是 HAL 介面定義的地方。
硬體抽象層 HAL
HAL 標頭檔案都放在 hardware/libhardware/include/hardware/
,我們在這裡找到了 bt_gatt.h
,並找到了 btgatt_interface_t
的定義如下:
|
可以看到 btgatt_interface_t
有一個成員 client
,型別是 btgatt_client_interface_t
。
我們再來看 btgatt_client_interface_t
的定義,在 bt_gatt_client.h
中:
|
在這裡我們看到了 register_client
這個函式指標。這個結構體的定義很長,我這裡只截取了本文相關的內容。這裡定義了所有 GATT
客戶端操作相關的介面,例如連線、掃描、獲取 Gatt Service、讀寫 Gatt characteristic/descriptor 等等。
但是這個結構體的具體實現在什麼地方呢?我們的直覺應該就在 HAL 的實現 BlueDroid 模組了。
藍芽協議棧:BlueDroid
BlueDroid 的程式碼在 external/bluetooth/bluedroid
。我們在這個目錄下 grep 一下:
|
我們很容易找到 btgatt_interface_t
的實現:
|
然後在 btif/src/btif_gatt_client.c
,找到 btgattClientInterface
具體的實現如下:
|
看到這裡定義了一個 btgatt_client_interface_t
的例項 btgattClientInterface
,內部都是函式指標,所有的函式的實現都這這個檔案中能找到。其實這個變數就是前面的程式碼中提到的 sGattIf->client
,我們下面來看看這是是怎麼關聯上的。
在 btif/src/btif_gatt.c
中又如下定義:
|
|
從程式碼中可以看出,btgattClientInterface
賦值給了 btgattInterface
的 client
成員(還記得前面的 btgatt_interface_t
的定義吧)。難道這裡的 btgattInterface
就是 JNI 中的 sGattIf
嗎?
確實是這樣的,還記的前面的說的 sGattIf
的初始化,呼叫瞭如下介面:
|
我們來看一下 get_profile_interface()
的定義,在 btif/src/bluetooth.c
中:
|
看到這裡呼叫了 btif_gatt_get_interface()
,實際上就是把 btgattInterface
返回賦值給了 sGattIf
。
到這裡,變數之間的相互關係和初始化就都完全清楚了。還是不要忘記我們的目標:連線 BLE 裝置。前面我們分析到了,發起 BLE 裝置連線,首先是向底層協議棧註冊客戶端,也就是呼叫了 sGattIf->client->register_client(&uuid);
。
現在我們知道了 register_client()
的實現就在 btif/src/btif_gatt_client.c
,對應的實現如下:
|
這裡的 btif_transfer_context()
是一個工具方法,程式碼如下:
|
這裡面呼叫了一個很重要的方法 btif_sendmsg(...)
,實際上是呼叫了 GKI_send_msg(...)
,接著往下看程式碼就會發現,實際上就是傳送一個 Event,讓對應的 handler 函式去處理。
據我的理解,其實就是一個類似於 Android framework 中的 Handler
,你可以提交你的 Task 到指定的執行緒中去執行。
這裡 btif_transfer_context(...)
的作用就是把程式碼執行的上下文(context)轉為藍芽協議棧,這部分的程式碼和本文關係不大,這裡就不深入討論了。
這裡的 btgattc_handle_event
相當於我們要執行的 Task, 後面就是這個 Task 執行需要的一些引數。我們接下來看 btgattc_handle_event
的實現:
|
可以看到,這個函式的引數,都是我們上面那個 btif_transfer_context(...)
傳遞進來的,這裡 event = BTIF_GATTC_REGISTER_APP
,看一下對應的 switch
分支程式碼。btif_to_bta_uuid()
非常簡單,這是把 UUID 的轉換為 BTA 層 UUID 格式。然後真正做事的是 BTA_GATTC_AppRegister(...)
:
|
這裡首先判斷 BTA_ID_GATTC
事件處理器是否註冊了,如果沒有註冊就註冊一個 bta_gattc_reg
。這裡表示 bta_gattc_reg
所有的 GATT 客戶端事件的處理器。
可見,這個 bta_gattc_reg
是非常重要的,我們來看它的定義:
|
這是一個結構體,其定義我也貼在上面了,成員型別都是函式指標,其函式的定義我在後面會講。
我們先往下看,這裡的程式碼是不是看起來有些面熟,其實和 btif_transfer_context()
的邏輯非常類似,這裡也是傳送一個 event 給 handler 去處理。
雖然這裡呼叫的是 bta_sys_sendmsg(...)
,實際上它的實現就是呼叫 GKI_send_msg(...)
。
分析方法類似,我們這裡的 event 是 BTA_GATTC_API_REG_EVT
,這個事件的處理函式就是上面的 bta_gattc_reg
的 bta_gattc_hdl_event
:
|
到這裡,我們終於看到了最終的真正執行註冊客戶端的函式 bta_gattc_register(...)
了:
|
主要流程都在程式碼的註釋中解釋了,遍歷客戶端列表,向 GATT statck 註冊客戶端,註冊成功後傳送 BTA_GATTC_INT_START_IF_EVT
事件。
因為這裡和前面一樣,同樣是呼叫了 bta_sys_sendmsg(...)
,所以處理函式是 bta_gattc_hdl_event(...)
,我們再來看這個分支:
|
這裡呼叫了 bta_gattc_start_if(...)
:
|
最終呼叫了 GATT_StartIf(...)
:
|
這個函式的主要作用是,呼叫剛註冊的客戶端 gatt_if
的連接回調函式,上報所有的裝置的連線狀態。
我們接著分析 bta_gattc_register(...)
函式,重點是這一行:
|
這裡是看起來呼叫了一個回撥函式,這裡的 (*p_data->api_reg.p_cback)
是誰,我們這裡回溯以下。
回到 bta_gattc_hdl_event(...)
,這個 p_data
是這裡傳進來的 tBTA_GATTC_DATA
型別的資料,它是一個聯合體(union):
|
其中 (p_data->api_reg)
成員是一個 tBTA_GATTC_API_REG
,這個型別我們在 BTA_GATTC_AppRegister(...)
函式中見過。
其實這個值就是我們在這裡建立的,我們看一下 p_cback
是誰,我們在往回找到 btgattc_handle_event(...)
函式,發現就是 bta_gattc_cback
:其定義如下:
|
這裡又是一個通過 “handler” 機制實現執行上下文的切換,這裡來看一下事件處理函式 btif_gattc_upstreams_evt
:
|
因為上面傳遞進來的 event 是 BTA_GATTC_REG_EVT
,我們就來看這個 event 的處理程式碼。bta_to_btif_uuid()
和上面的 btif_to_bta_uuid()
相反,把 BTA 的 UUID 轉換為 BTIF 的格式。
然後,一個巨集 HAL_CBACK
,其定義如下:
|
這裡展開一下,並替換其中實際的變數:
|
可以看到這裡其實就是一個執行回撥函式的巨集。
現在問題是 bt_gatt_callbacks
是從誰?我們可以看到是在這裡初始化的:
|
也就是初始化的時候,通過傳遞進來的。在分析 BlueDroid 的最開始我們已經看到了 btgattInterface
的初始化了,這裡的 btif_gatt_init
函式就賦值給了它的 init
成員。
通過我們前面的分析,btgattInterface
實際上就是 JNI 層的 sGattIf
物件。我們回到 JNI 層的程式碼:
|
這裡呼叫了 sGattIf->init(...)
方法,實際上就是前面的 btif_gatt_init(...)
函式。看到這裡傳入的 sGattCallbacks
就是我們在 BlueDroid 層尋找的 bt_gatt_callbacks
。
我們來看 sGattCallbacks
的定義:
|
通過前面的分析,我們應該能很快知道 btgatt_callbacks_t
應該在 HAL 中定義的,然後 sGattClientCallbacks
就是對應的 client
成員。
在來看 sGattClientCallbacks
的定義:
|
這裡的 btgattc_register_app_cb
對應的就是 btgatt_client_callbacks_t
的 register_client_cb
成員。所以我們再來看一下那個展開的巨集,這裡再貼一下:
|
實際上就是呼叫了 JNI 中定義的 btgattc_register_app_cb
函式:
|
|
這裡通過 JNI 呼叫了函式 id 為 method_onClientRegistered
函式,
|
這裡就對應了 Java class 中的 onClientRegistered(...)
方法,這裡 clazz
是誰呢?
|
我們在 GattService.java
中看到:
|
可見,上面的 clazz
就是 GattService
,onClientRegistered
就是 GattService 的成員方法。我們在 GattService 類中找到其實現如下:
|
哈哈,終於回到我們熟悉的 Java 層程式碼。還記得我們前面的在分析呼叫 registerClient(...)
的時候,把上層客戶端與 uuid 對應起來,儲存在 mClientMap 中。
這裡通過 uuid 找到對應的客戶端App,然後呼叫對應的回撥函式 app.callback.onClientRegistered(status, clientIf);
。
如果你還記得前面的分析的話,應該知道這個 callback
就是 BluetoothGatt
在呼叫 registerApp(...)
的時候傳遞的 mBluetoothGattCallback
,所以這裡就呼叫了 mBluetoothGattCallback.onClientRegistered(...)
方法。
這個 onClientRegistered()
回撥我們在之前就提到過,如果註冊客戶端完成,就會回撥這裡。如果成功,就會發起真正的連線請求(見第 1 節)。到這裡,其實就回調了 Android framework 層了。
如果這裡註冊客戶端成功了,回撥的 status
就是 GATT_SUCCESS
,在 BluetoothGatt
中就會發起連線請求 mService.clientConnect(mClientIf, mDevice.getAddress(), !mAutoConnect);
。具體的流程和上面整個分析類似。
有了上面的整個分析經驗,這次應該就駕輕就熟了。所以我這裡也不再往下繼續說了。
總結
結合文章開始的那個圖,回顧一下程式碼整個呼叫過程,非常清楚印證流程:framework -> Bluetooth service -> JNI -> HAL -> BlueDroid。
整個分析下來,幾乎貫穿了整個 Android 系統,儘管還有很多細節沒有展開去了解,但是脈絡還是很清晰的。
第一次寫分析原始碼的文章,不知不覺就寫了這麼長(其實大部分是程式碼),花費了很多精力,也不知道說清楚了沒有。對我自己來說,還是收穫不少。