android development藍牙開發學習借鑑

Android 平臺包含藍牙網絡堆棧支持,憑藉此項支持,設備能以無線方式與其他藍牙設備交換數據。應用框架提供了通過 Android Bluetooth API 訪問藍牙功能的途徑。 這些 API 允許應用以無線方式連接到其他藍牙設備,從而實現點到點和多點無線功能。

使用 Bluetooth API,Android 應用可執行以下操作:

  • 掃描其他藍牙設備
  • 查詢本地藍牙適配器的配對藍牙設備
  • 建立 RFCOMM 通道
  • 通過服務發現連接到其他設備
  • 與其他設備進行雙向數據傳輸
  • 管理多個連接

本文將介紹如何使用傳統藍牙。傳統藍牙適用於電池使用強度較大的操作,例如 Android 設備之間的流式傳輸和通信等。 針對具有低功耗要求的藍牙設備,Android 4.3(API 級別 18)中引入了面向低功耗藍牙的 API 支持。 如需瞭解更多信息,請參閱低功耗藍牙

基礎知識


本文將介紹如何使用 Android Bluetooth API 來完成使用藍牙進行通信的四項主要任務:設置藍牙、查找局部區域內的配對設備或可用設備、連接設備,以及在設備之間傳輸數據。

android.bluetooth 包中提供了所有 Bluetooth API。 下面概要列出了創建藍牙連接所需的類和接口:

BluetoothAdapter
表示本地藍牙適配器(藍牙無線裝置)。 BluetoothAdapter 是所有藍牙交互的入口點。 利用它可以發現其他藍牙設備,查詢綁定(配對)設備的列表,使用已知的 MAC 地址實例化 BluetoothDevice,以及創建 BluetoothServerSocket 以偵聽來自其他設備的通信。
BluetoothDevice
表示遠程藍牙設備。利用它可以通過 BluetoothSocket 請求與某個遠程設備建立連接,或查詢有關該設備的信息,例如設備的名稱、地址、類和綁定狀態等。
BluetoothSocket
表示藍牙套接字接口(與 TCP Socket 相似)。這是允許應用通過 InputStream 和 OutputStream 與其他藍牙設備交換數據的連接點。
BluetoothServerSocket
表示用於偵聽傳入請求的開放服務器套接字(類似於 TCP ServerSocket)。 要連接兩臺 Android 設備,其中一臺設備必須使用此類開放一個服務器套接字。 當一臺遠程藍牙設備向此設備發出連接請求時, BluetoothServerSocket 將會在接受連接後返回已連接的 BluetoothSocket
BluetoothClass
描述藍牙設備的一般特徵和功能。 這是一組只讀屬性,用於定義設備的主要和次要設備類及其服務。 不過,它不能可靠地描述設備支持的所有藍牙配置文件和服務,而是適合作爲設備類型提示。
BluetoothProfile
表示藍牙配置文件的接口。 藍牙配置文件是適用於設備間藍牙通信的無線接口規範。 免提配置文件便是一個示例。 如需瞭解有關配置文件的詳細討論,請參閱使用配置文件
BluetoothHeadset
提供藍牙耳機支持,以便與手機配合使用。 其中包括藍牙耳機和免提(1.5 版)配置文件。
BluetoothA2dp
定義高質量音頻如何通過藍牙連接和流式傳輸,從一臺設備傳輸到另一臺設備。“A2DP”代表高級音頻分發配置文件。
BluetoothHealth
表示用於控制藍牙服務的健康設備配置文件代理。
BluetoothHealthCallback
用於實現 BluetoothHealth 回調的抽象類。您必須擴展此類並實現回調方法,以接收關於應用註冊狀態和藍牙通道狀態變化的更新內容。
BluetoothHealthAppConfiguration
表示第三方藍牙健康應用註冊的應用配置,以便與遠程藍牙健康設備通信。
BluetoothProfile.ServiceListener
BluetoothProfile IPC 客戶端連接到服務(即,運行特定配置文件的內部服務)或斷開服務連接時向其發送通知的接口。

藍牙權限


要在應用中使用藍牙功能,必須聲明藍牙權限 BLUETOOTH。您需要此權限才能執行任何藍牙通信,例如請求連接、接受連接和傳輸數據等。

如果您希望您的應用啓動設備發現或操作藍牙設置,則還必須聲明 BLUETOOTH_ADMIN 權限。 大多數應用需要此權限僅僅爲了能夠發現本地藍牙設備。 除非該應用是將要應用戶請求修改藍牙設置的“超級管理員”,否則不應使用此權限所授予的其他能力。 :如果要使用 BLUETOOTH_ADMIN 權限,則還必須擁有 BLUETOOTH 權限。

在您的應用清單文件中聲明藍牙權限。例如:

<manifest ... >
 
<uses-permission android:name="android.permission.BLUETOOTH" />
  ...
</manifest>

如需瞭解有關聲明應用權限的更多信息,請參閱 <uses-permission> 參考資料。

設置藍牙


圖 1:啓用藍牙對話框。

您需要驗證設備支持藍牙,並且如果支持確保將其啓用,然後您的應用才能通過藍牙進行通信。

如果不支持藍牙,則應正常停用任何藍牙功能。 如果支持藍牙但已停用此功能,則可以請求用戶在不離開應用的同時啓用藍牙。 可使用 BluetoothAdapter,分兩步完成此設置:

  1. 獲取 BluetoothAdapter

    所有藍牙 Activity 都需要 BluetoothAdapter。 要獲取 BluetoothAdapter,請調用靜態 getDefaultAdapter() 方法。這將返回一個表示設備自身的藍牙適配器(藍牙無線裝置)的 BluetoothAdapter。 整個系統有一個藍牙適配器,並且您的應用可使用此對象與之交互。 如果 getDefaultAdapter() 返回 null,則該設備不支持藍牙,您的操作到此爲止。 例如:

    BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if (mBluetoothAdapter == null) {
       
    // Device does not support Bluetooth
    }
  2. 啓用藍牙

    下一步,您需要確保已啓用藍牙。調用 isEnabled() 以檢查當前是否已啓用藍牙。 如果此方法返回 false,則表示藍牙處於停用狀態。要請求啓用藍牙,請使用 ACTION_REQUEST_ENABLE 操作 Intent 調用 startActivityForResult()。這將通過系統設置發出啓用藍牙的請求(無需停止您的應用)。 例如:

    if (!mBluetoothAdapter.isEnabled()) {
       
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult
    (enableBtIntent, REQUEST_ENABLE_BT);
    }

    如圖 1 所示,將顯示對話框,請求用戶允許啓用藍牙。如果用戶響應“Yes”,系統將開始啓用藍牙,並在該進程完成(或失敗)後將焦點返回到您的應用。

    傳遞給 startActivityForResult()REQUEST_ENABLE_BT 常量是在局部定義的整型(必須大於 0),系統會將其作爲 requestCode 參數傳遞迴您的 onActivityResult() 實現。

    如果成功啓用藍牙,您的 Activity 將會在 onActivityResult() 回調中收到 RESULT_OK 結果代碼。 如果由於某個錯誤(或用戶響應“No”)而沒有啓用藍牙,則結果代碼爲 RESULT_CANCELED

您的應用還可以選擇偵聽 ACTION_STATE_CHANGED 廣播 Intent,每當藍牙狀態發生變化時,系統都會廣播此 Intent。 此廣播包含額外字段 EXTRA_STATEEXTRA_PREVIOUS_STATE,二者分別包含新的和舊的藍牙狀態。 這些額外字段可能的值包括 STATE_TURNING_ONSTATE_ONSTATE_TURNING_OFFSTATE_OFF。偵聽此廣播適用於檢測在您的應用運行期間對藍牙狀態所做的更改。

提示:啓用可檢測性將會自動啓用藍牙。 如果您計劃在執行藍牙 Activity 之前一直啓用設備的可檢測性,則可以跳過上述步驟 2。 閱讀下面的啓用可檢測性

查找設備


使用 BluetoothAdapter,您可以通過設備發現或通過查詢配對(綁定)設備的列表來查找遠程藍牙設備。

設備發現是一個掃描過程,它會搜索局部區域內已啓用藍牙功能的設備,然後請求一些關於各臺設備的信息(有時也被稱爲“發現”、“查詢”或“掃描”)。但局部區域內的藍牙設備僅在其當前已啓用可檢測性時纔會響應發現請求。 如果設備可檢測到,它將通過共享一些信息(例如設備名稱、類及其唯一 MAC 地址)來響應發現請求。 利用此信息,執行發現的設備可以選擇發起到被發現設備的連接。

在首次與遠程設備建立連接後,將會自動向用戶顯示配對請求。 設備完成配對後,將會保存關於該設備的基本信息(例如設備名稱、類和 MAC 地址),並且可使用 Bluetooth API 讀取這些信息。 利用遠程設備的已知 MAC 地址可隨時向其發起連接,而無需執行發現操作(假定該設備處於有效範圍內)。

請記住,被配對與被連接之間存在差別。被配對意味着兩臺設備知曉彼此的存在,具有可用於身份驗證的共享鏈路密鑰,並且能夠與彼此建立加密連接。 被連接意味着設備當前共享一個 RFCOMM 通道,並且能夠向彼此傳輸數據。 當前的 Android Bluetooth API 要求對設備進行配對,然後才能建立 RFCOMM 連接。 (在使用 Bluetooth API 發起加密連接時,會自動執行配對)。

以下幾節介紹如何查找已配對的設備,或使用設備發現來發現新設備。

:Android 設備默認處於不可檢測到狀態。 用戶可通過系統設置將設備設爲在有限的時間內處於可檢測到狀態,或者,應用可請求用戶在不離開應用的同時啓用可檢測性。 下面討論如何啓用可檢測性

查詢配對的設備

在執行設備發現之前,有必要查詢已配對的設備集,以瞭解所需的設備是否處於已知狀態。 爲此,請調用 getBondedDevices()。 這將返回表示已配對設備的一組 BluetoothDevice。 例如,您可以查詢所有已配對設備,然後使用 ArrayAdapter 向用戶顯示每臺設備的名稱:

Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
// If there are paired devices
if (pairedDevices.size() > 0) {
   
// Loop through paired devices
   
for (BluetoothDevice device : pairedDevices) {
       
// Add the name and address to an array adapter to show in a ListView
        mArrayAdapter
.add(device.getName() + "\n" + device.getAddress());
   
}
}

要發起連接,BluetoothDevice 對象僅僅需要提供 MAC 地址。 在此示例中,它將保存爲顯示給用戶的 ArrayAdapter 的一部分。 之後可提取該 MAC 地址,以便發起連接。 您可以在有關連接設備的部分詳細瞭解如何創建連接。

發現設備

要開始發現設備,只需調用 startDiscovery()。該進程爲異步進程,並且該方法會立即返回一個布爾值,指示是否已成功啓動發現操作。 發現進程通常包含約 12 秒鐘的查詢掃描,之後對每臺發現的設備進行頁面掃描,以檢索其藍牙名稱。

您的應用必須針對 ACTION_FOUND Intent 註冊一個 BroadcastReceiver,以便接收每臺發現的設備的相關信息。 針對每臺設備,系統將會廣播 ACTION_FOUND Intent。此 Intent 將攜帶額外字段 EXTRA_DEVICEEXTRA_CLASS,二者分別包含 BluetoothDeviceBluetoothClass。 例如,下面說明了在發現設備時如何註冊以處理廣播。

// Create a BroadcastReceiver for ACTION_FOUND
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
   
public void onReceive(Context context, Intent intent) {
       
String action = intent.getAction();
       
// When discovery finds a device
       
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
           
// Get the BluetoothDevice object from the Intent
           
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
           
// Add the name and address to an array adapter to show in a ListView
            mArrayAdapter
.add(device.getName() + "\n" + device.getAddress());
       
}
   
}
};
// Register the BroadcastReceiver
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver
(mReceiver, filter); // Don't forget to unregister during onDestroy

要發起連接,BluetoothDevice 對象僅僅需要提供 MAC 地址。 在此示例中,它將保存爲顯示給用戶的 ArrayAdapter 的一部分。 之後可提取該 MAC 地址,以便發起連接。 您可以在有關連接設備的部分詳細瞭解如何創建連接。

注意:執行設備發現對於藍牙適配器而言是一個非常繁重的操作過程,並且會消耗大量資源。 在找到要連接的設備後,確保始終使用 cancelDiscovery() 停止發現,然後再嘗試連接。 此外,如果您已經保持與某臺設備的連接,那麼執行發現操作可能會大幅減少可用於該連接的帶寬,因此不應該在處於連接狀態時執行發現操作。

啓用可檢測性

如果您希望將本地設備設爲可被其他設備檢測到,請使用 ACTION_REQUEST_DISCOVERABLE 操作 Intent 調用 startActivityForResult(Intent, int)。 這將通過系統設置發出啓用可檢測到模式的請求(無需停止您的應用)。 默認情況下,設備將變爲可檢測到並持續 120 秒鐘。 您可以通過添加 EXTRA_DISCOVERABLE_DURATION Intent Extra 來定義不同的持續時間。 應用可以設置的最大持續時間爲 3600 秒,值爲 0 則表示設備始終可檢測到。 任何小於 0 或大於 3600 的值都會自動設爲 120 秒。 例如,以下片段會將持續時間設爲 300 秒:

Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent
.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity
(discoverableIntent);
圖 2:啓用可檢測性對話框。

如圖 2 所示,將顯示對話框,請求用戶允許將設備設爲可檢測到。如果用戶響應“Yes”,則設備將變爲可檢測到並持續指定的時間量。 然後,您的 Activity 將會收到對 onActivityResult()) 回調的調用,其結果代碼等於設備可檢測到的持續時間。 如果用戶響應“No”或出現錯誤,結果代碼將爲 RESULT_CANCELED

:如果尚未在設備上啓用藍牙,則啓用設備可檢測性將會自動啓用藍牙。

設備將在分配的時間內以靜默方式保持可檢測到模式。如果您希望在可檢測到模式發生變化時收到通知,您可以針對 ACTION_SCAN_MODE_CHANGED Intent 註冊 BroadcastReceiver。 它將包含額外字段 EXTRA_SCAN_MODEEXTRA_PREVIOUS_SCAN_MODE,二者分別告知您新的和舊的掃描模式。 每個字段可能的值包括 SCAN_MODE_CONNECTABLE_DISCOVERABLESCAN_MODE_CONNECTABLESCAN_MODE_NONE,這些值分別指示設備處於可檢測到模式、未處於可檢測到模式但仍能接收連接,或未處於可檢測到模式並且無法接收連接。

如果您將要發起到遠程設備的連接,則無需啓用設備可檢測性。 僅當您希望您的應用託管將用於接受傳入連接的服務器套接字時,纔有必要啓用可檢測性,因爲遠程設備必須能夠發現該設備,然後才能發起連接。

連接設備


要在兩臺設備上的應用之間創建連接,必須同時實現服務器端和客戶端機制,因爲其中一臺設備必須開放服務器套接字,而另一臺設備必須發起連接(使用服務器設備的 MAC 地址發起連接)。 當服務器和客戶端在同一 RFCOMM 通道上分別擁有已連接的 BluetoothSocket 時,二者將被視爲彼此連接。 這種情況下,每臺設備都能獲得輸入和輸出流式傳輸,並且可以開始傳輸數據,在有關管理連接的部分將會討論這一主題。 本部分介紹如何在兩臺設備之間發起連接。

服務器設備和客戶端設備分別以不同的方法獲得需要的 BluetoothSocket。服務器將在傳入連接被接受時收到套接字。 客戶端將在其打開到服務器的 RFCOMM 通道時收到該套接字。

圖 3:藍牙配對對話框。

一種實現技術是自動將每臺設備準備爲一個服務器,從而使每臺設備開放一個服務器套接字並偵聽連接。然後任一設備可以發起與另一臺設備的連接,併成爲客戶端。 或者,其中一臺設備可顯式“託管”連接並按需開放一個服務器套接字,而另一臺設備則直接發起連接。

:如果兩臺設備之前尚未配對,則在連接過程中,Android 框架會自動向用戶顯示配對請求通知或對話框(如圖 3 所示)。因此,在嘗試連接設備時,您的應用無需擔心設備是否已配對。 您的 RFCOMM 連接嘗試將被阻塞,直至用戶成功完成配對或配對失敗(包括用戶拒絕配對、配對失敗或超時)。

連接爲服務器

當您需要連接兩臺設備時,其中一臺設備必須通過保持開放的 BluetoothServerSocket 來充當服務器。 服務器套接字的用途是偵聽傳入的連接請求,並在接受一個請求後提供已連接的 BluetoothSocket。 從 BluetoothServerSocket 獲取 BluetoothSocket 後,可以(並且應該)捨棄 BluetoothServerSocket,除非您需要接受更多連接。

以下是設置服務器套接字並接受連接的基本過程:

  1. 通過調用 listenUsingRfcommWithServiceRecord(String, UUID) 獲取 BluetoothServerSocket

    該字符串是您的服務的可識別名稱,系統會自動將其寫入到設備上的新服務發現協議 (SDP) 數據庫條目(可使用任意名稱,也可直接使用您的應用名稱)。 UUID 也包含在 SDP 條目中,並且將作爲與客戶端設備的連接協議的基礎。 也就是說,當客戶端嘗試連接此設備時,它會攜帶能夠唯一標識其想要連接的服務的 UUID。 兩個 UUID 必須匹配,在下一步中,連接纔會被接受。

  2. 通過調用 accept() 開始偵聽連接請求。

    這是一個阻塞調用。它將在連接被接受或發生異常時返回。 僅當遠程設備發送的連接請求中所包含的 UUID 與向此偵聽服務器套接字註冊的 UUID 相匹配時,連接纔會被接受。 操作成功後,accept() 將會返回已連接的 BluetoothSocket

  3. 除非您想要接受更多連接,否則請調用 close()

    這將釋放服務器套接字及其所有資源,但不會關閉 accept() 所返回的已連接的 BluetoothSocket。 與 TCP/IP 不同,RFCOMM 一次只允許每個通道有一個已連接的客戶端,因此大多數情況下,在接受已連接的套接字後立即在 BluetoothServerSocket 上調用 close() 是行得通的。

accept() 調用不應在主 Activity UI 線程中執行,因爲它是阻塞調用,並會阻止與應用的任何其他交互。 在您的應用所管理的新線程中使用 BluetoothServerSocketBluetoothSocket 完成所有工作,這通常是一種行之有效的做法。 要終止 accept() 等被阻塞的調用,請通過另一個線程在 BluetoothServerSocket(或 BluetoothSocket)上調用 close(),被阻塞的調用將會立即返回。 請注意,BluetoothServerSocketBluetoothSocket 中的所有方法都是線程安全的方法。

示例

以下是一個用於接受傳入連接的服務器組件的簡化線程:

private class AcceptThread extends Thread {
   
private final BluetoothServerSocket mmServerSocket;

   
public AcceptThread() {
       
// Use a temporary object that is later assigned to mmServerSocket,
       
// because mmServerSocket is final
       
BluetoothServerSocket tmp = null;
       
try {
           
// MY_UUID is the app's UUID string, also used by the client code
            tmp
= mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
       
} catch (IOException e) { }
        mmServerSocket
= tmp;
   
}

   
public void run() {
       
BluetoothSocket socket = null;
       
// Keep listening until exception occurs or a socket is returned
       
while (true) {
           
try {
                socket
= mmServerSocket.accept();
           
} catch (IOException e) {
               
break;
           
}
           
// If a connection was accepted
           
if (socket != null) {
               
// Do work to manage the connection (in a separate thread)
                manageConnectedSocket
(socket);
                mmServerSocket
.close();
               
break;
           
}
       
}
   
}

   
/** Will cancel the listening socket, and cause the thread to finish */
   
public void cancel() {
       
try {
            mmServerSocket
.close();
       
} catch (IOException e) { }
   
}
}

在此示例中,只需要一個傳入連接,因此在接受連接並獲取 BluetoothSocket 之後,應用會立即將獲取的 BluetoothSocket 發送到單獨的線程,關閉 BluetoothServerSocket 並中斷循環。

請注意,當 accept() 返回 BluetoothSocket 時,表示套接字已連接好,因此您不應該像在客戶端那樣調用 connect()

manageConnectedSocket() 是應用中的虛構方法,它將啓動用於傳輸數據的線程,在有關管理連接的部分將會討論這一主題。

在完成傳入連接的偵聽後,通常應立即關閉您的 BluetoothServerSocket。 在此示例中,獲取 BluetoothSocket 後立即調用 close()。 您也可能希望在您的線程中提供一個公共方法,以便在需要停止偵聽服務器套接字時關閉私有 BluetoothSocket

連接爲客戶端

要發起與遠程設備(保持開放的服務器套接字的設備)的連接,必須首先獲取表示該遠程設備的 BluetoothDevice 對象。(在前面有關查找設備的部分介紹瞭如何獲取 BluetoothDevice)。 然後必須使用 BluetoothDevice 來獲取 BluetoothSocket 併發起連接。

以下是基本過程:

  1. 使用 BluetoothDevice,通過調用 createRfcommSocketToServiceRecord(UUID) 獲取 BluetoothSocket

    這將初始化將要連接到 BluetoothDeviceBluetoothSocket。 此處傳遞的 UUID 必須與服務器設備在使用 listenUsingRfcommWithServiceRecord(String, UUID) 開放其 BluetoothServerSocket 時所用的 UUID 相匹配。 要使用相同的 UUID,只需將該 UUID 字符串以硬編碼方式編入應用,然後通過服務器代碼和客戶端代碼引用該字符串。

  2. 通過調用 connect() 發起連接。
  3. 執行此調用時,系統將會在遠程設備上執行 SDP 查找,以便匹配 UUID。 如果查找成功並且遠程設備接受了該連接,它將共享 RFCOMM 通道以便在連接期間使用,並且 connect() 將會返回。 此方法爲阻塞調用。 如果由於任何原因連接失敗或 connect() 方法超時(大約 12 秒之後),它將會引發異常。

    由於 connect() 爲阻塞調用,因此該連接過程應始終在主 Activity 線程以外的線程中執行。

    注:在調用 connect() 時,應始終確保設備未在執行設備發現。 如果正在進行發現操作,則會大幅降低連接嘗試的速度,並增加連接失敗的可能性。

示例

以下是發起藍牙連接的線程的基本示例:

private class ConnectThread extends Thread {
   
private final BluetoothSocket mmSocket;
   
private final BluetoothDevice mmDevice;

   
public ConnectThread(BluetoothDevice device) {
       
// Use a temporary object that is later assigned to mmSocket,
       
// because mmSocket is final
       
BluetoothSocket tmp = null;
        mmDevice
= device;

       
// Get a BluetoothSocket to connect with the given BluetoothDevice
       
try {
           
// MY_UUID is the app's UUID string, also used by the server code
            tmp
= device.createRfcommSocketToServiceRecord(MY_UUID);
       
} catch (IOException e) { }
        mmSocket
= tmp;
   
}

   
public void run() {
       
// Cancel discovery because it will slow down the connection
        mBluetoothAdapter
.cancelDiscovery();

       
try {
           
// Connect the device through the socket. This will block
           
// until it succeeds or throws an exception
            mmSocket
.connect();
       
} catch (IOException connectException) {
           
// Unable to connect; close the socket and get out
           
try {
                mmSocket
.close();
           
} catch (IOException closeException) { }
           
return;
       
}

       
// Do work to manage the connection (in a separate thread)
        manageConnectedSocket
(mmSocket);
   
}

   
/** Will cancel an in-progress connection, and close the socket */
   
public void cancel() {
       
try {
            mmSocket
.close();
       
} catch (IOException e) { }
   
}
}

請注意,在建立連接之前會調用 cancelDiscovery()。 在進行連接之前應始終執行此調用,而且調用時無需實際檢查其是否正在運行(但如果您確實想要執行檢查,請調用 isDiscovering())。

manageConnectedSocket() 是應用中的虛構方法,它將啓動用於傳輸數據的線程,在有關管理連接的部分將會討論這一主題。

在完成 BluetoothSocket 後,應始終調用 close() 以執行清理操作。這樣做會立即關閉已連接的套接字並清理所有內部資源。

管理連接


在成功連接兩臺(或更多臺)設備後,每臺設備都會有一個已連接的 BluetoothSocket。 這一點非常有趣,因爲這表示您可以在設備之間共享數據。 利用 BluetoothSocket,傳輸任意數據的一般過程非常簡單:

  1. 獲取 InputStreamOutputStream,二者分別通過套接字以及 getInputStream()getOutputStream() 來處理數據傳輸。
  2. 使用 read(byte[])write(byte[]) 讀取數據並寫入到流式傳輸。

就這麼簡單。

當然,還需要考慮實現細節。首要的是,應該爲所有流式傳輸讀取和寫入操作使用專門的線程。 這一點很重要,因爲 read(byte[])write(byte[]) 方法都是阻塞調用。read(byte[]) 將會阻塞,直至從流式傳輸中讀取內容。write(byte[]) 通常不會阻塞,但如果遠程設備沒有足夠快地調用 read(byte[]),並且中間緩衝區已滿,則其可能會保持阻塞狀態以實現流量控制。因此,線程中的主循環應專門用於讀取 InputStream。 可使用線程中單獨的公共方法來發起對 OutputStream 的寫入操作。

示例

以下是可能的顯示內容示例:

private class ConnectedThread extends Thread {
   
private final BluetoothSocket mmSocket;
   
private final InputStream mmInStream;
   
private final OutputStream mmOutStream;

   
public ConnectedThread(BluetoothSocket socket) {
        mmSocket
= socket;
       
InputStream tmpIn = null;
       
OutputStream tmpOut = null;

       
// Get the input and output streams, using temp objects because
       
// member streams are final
       
try {
            tmpIn
= socket.getInputStream();
            tmpOut
= socket.getOutputStream();
       
} catch (IOException e) { }

        mmInStream
= tmpIn;
        mmOutStream
= tmpOut;
   
}

   
public void run() {
       
byte[] buffer = new byte[1024];  // buffer store for the stream
       
int bytes; // bytes returned from read()

       
// Keep listening to the InputStream until an exception occurs
       
while (true) {
           
try {
               
// Read from the InputStream
                bytes
= mmInStream.read(buffer);
               
// Send the obtained bytes to the UI activity
                mHandler
.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
                       
.sendToTarget();
           
} catch (IOException e) {
               
break;
           
}
       
}
   
}

   
/* Call this from the main activity to send data to the remote device */
   
public void write(byte[] bytes) {
       
try {
            mmOutStream
.write(bytes);
       
} catch (IOException e) { }
   
}

   
/* Call this from the main activity to shutdown the connection */
   
public void cancel() {
       
try {
            mmSocket
.close();
       
} catch (IOException e) { }
   
}
}

構造函數獲取必要的流式傳輸,並且一旦執行,線程將會等待通過 InputStream 傳入的數據。 當 read(byte[]) 返回流式傳輸中的字節時,將使用來自父類的成員處理程序將數據發送到主 Activity。 然後該方法返回並等待流式傳輸提供更多字節。

發送傳出數據不外乎從主 Activity 調用線程的 write() 方法,並傳入要發送的字節。 然後,此方法直接調用 write(byte[]),將數據發送到遠程設備。

線程的 cancel() 方法很重要,它能通過關閉 BluetoothSocket 隨時終止連接。當您結束藍牙連接的使用時,應始終調用此方法。

有關使用 Bluetooth API 的演示,請參閱藍牙聊天示例應用

使用配置文件


從 Android 3.0 開始,Bluetooth API 便支持使用藍牙配置文件。 藍牙配置文件是適用於設備間藍牙通信的無線接口規範。 免提配置文件便是一個示例。 對於連接到無線耳機的手機,兩臺設備都必須支持免提配置文件。

您可以實現接口 BluetoothProfile,通過寫入自己的類來支持特定的藍牙配置文件。 Android Bluetooth API 提供了以下藍牙配置文件的實現:

  • 耳機。耳機配置文件提供了藍牙耳機支持,以便與手機配合使用。 Android 提供了 BluetoothHeadset 類,它是用於通過進程間通信 (IPC) 來控制藍牙耳機服務的代理。 這包括藍牙耳機和免提(1.5 版)配置文件。 BluetoothHeadset 類包含 AT 命令支持。 有關此主題的詳細討論,請參閱供應商特定的 AT 命令
  • A2DP。高級音頻分發配置文件 (A2DP) 定義了高質量音頻如何通過藍牙連接和流式傳輸,從一個設備傳輸到另一個設備。 Android 提供了 BluetoothA2dp 類,它是用於通過 IPC 來控制藍牙 A2DP 服務的代理。
  • 健康設備。Android 4.0(API 級別 14)引入了對藍牙健康設備配置文件 (HDP) 的支持。 這允許您創建應用,使用藍牙與支持藍牙功能的健康設備進行通信,例如心率監測儀、血糖儀、溫度計、檯秤等等。 有關支持的設備列表及其相應的設備數據專業化代碼,請參閱 www.bluetooth.org 上的藍牙分配編號。 請注意,這些值在 ISO/IEEE 11073-20601 [7] 規範的“命名法規附錄”中也被稱爲 MDC_DEV_SPEC_PROFILE_*。 有關 HDP 的詳細討論,請參閱健康設備配置文件

以下是使用配置文件的基本步驟:

  1. 獲取默認適配器(請參閱設置藍牙)。
  2. 使用 getProfileProxy() 建立到配置文件所關聯的配置文件代理對象的連接。在以下示例中,配置文件代理對象是一個 BluetoothHeadset 的實例。
  3. 設置 BluetoothProfile.ServiceListener。此偵聽程序會在 BluetoothProfile IPC 客戶端連接到服務或斷開服務連接時向其發送通知。
  4. onServiceConnected() 中,獲取配置文件代理對象的句柄。
  5. 獲得配置文件代理對象後,可以立即將其用於監視連接狀態和執行其他與該配置文件相關的操作。

例如,以下代碼片段顯示瞭如何連接到 BluetoothHeadset 代理對象,以便能夠控制耳機配置文件:

BluetoothHeadset mBluetoothHeadset;

// Get the default adapter
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

// Establish connection to the proxy.
mBluetoothAdapter
.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET);

private BluetoothProfile.ServiceListener mProfileListener = new BluetoothProfile.ServiceListener() {
   
public void onServiceConnected(int profile, BluetoothProfile proxy) {
       
if (profile == BluetoothProfile.HEADSET) {
            mBluetoothHeadset
= (BluetoothHeadset) proxy;
       
}
   
}
   
public void onServiceDisconnected(int profile) {
       
if (profile == BluetoothProfile.HEADSET) {
            mBluetoothHeadset
= null;
       
}
   
}
};

// ... call functions on mBluetoothHeadset

// Close proxy connection after use.
mBluetoothAdapter
.closeProfileProxy(mBluetoothHeadset);

供應商特定的 AT 命令

從 Android 3.0 開始,應用可以註冊接收耳機所發送的預定義的供應商特定 AT 命令的系統廣播(例如 Plantronics +XEVENT 命令)。 例如,應用可以接收指示所連接設備的電池電量的廣播,並根據需要通知用戶或採取其他操作。 爲 ACTION_VENDOR_SPECIFIC_HEADSET_EVENT intent 創建廣播接收器,以處理耳機的供應商特定 AT 命令。

健康設備配置文件

Android 4.0(API 級別 14)引入了對藍牙健康設備配置文件 (HDP) 的支持。 這允許您創建應用,使用藍牙與支持藍牙功能的健康設備進行通信,例如心率監測儀、血糖儀、溫度計、檯秤等等。 Bluetooth Health API 包括類 BluetoothHealthBluetoothHealthCallbackBluetoothHealthAppConfiguration,在基礎知識部分介紹了這些類。

在使用 Bluetooth Health API 時,瞭解以下關鍵 HDP 概念很有幫助:

概念 說明
源設備 在 HDP 中定義的角色。源設備是將醫療數據傳輸到 Android 手機或平板電腦等智能設備的健康設備(體重秤、血糖儀、溫度計等)。
彙集設備 在 HDP 中定義的角色。在 HDP 中,彙集設備是接收醫療數據的智能設備。 在 Android HDP 應用中,彙集設備表示爲 BluetoothHealthAppConfiguration 對象。
註冊 指的是註冊特定健康設備的彙集設備。
連接 指的是開放健康設備與 Android 手機或平板電腦等智能設備之間的通道。

創建 HDP 應用

以下是創建 Android HDP 應用所涉及的基本步驟:

  1. 獲取 BluetoothHealth 代理對象的引用。

    與常規耳機和 A2DP 配置文件設備相似,您必須使用 BluetoothProfile.ServiceListenerHEALTH 配置文件類型來調用 getProfileProxy(),以便與配置文件代理對象建立連接。

  2. 創建 BluetoothHealthCallback 並註冊充當健康彙集設備的應用配置 (BluetoothHealthAppConfiguration)。
  3. 建立到健康設備的連接。一些設備將會發起該連接。 對於這類設備,無需執行該步驟。
  4. 成功連接到健康設備後,使用文件描述符對健康設備執行讀/寫操作。

    接收的數據需要使用實現了 IEEE 11073-xxxxx 規範的健康管理器進行解釋。

  5. 完成後,關閉健康通道並取消註冊該應用。該通道在長期閒置時也會關閉。

有關描述上述步驟的完整代碼示例,請參閱藍牙 HDP(健康設備配置文件)

</div>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章