譯:Android藍(lán)牙開發(fā)

藍(lán)牙

注:本文翻譯自https://developer.android.com/guide/topics/connectivity/bluetooth.html

Android平臺(tái)提供了對(duì)藍(lán)牙網(wǎng)絡(luò)棧的支持,藍(lán)牙網(wǎng)絡(luò)??梢宰屢慌_(tái)設(shè)備與另一臺(tái)藍(lán)牙設(shè)備之間實(shí)現(xiàn)無線數(shù)據(jù)交換。Android平臺(tái)通過Android藍(lán)牙API提供對(duì)藍(lán)牙的訪問能力。這些API能讓這些API與其他藍(lán)牙設(shè)備無線連接,具有通過點(diǎn)對(duì)點(diǎn)和多點(diǎn)無線特性。
通過藍(lán)牙API,Android應(yīng)用可是做下面的操作:

  • 掃描其他的藍(lán)牙設(shè)備
  • 為已經(jīng)匹配的藍(lán)牙設(shè)配查詢本地藍(lán)牙適配器。
  • 發(fā)布RFCOMM通道
  • 通過服務(wù)查詢鏈接到其他設(shè)備
  • 傳輸數(shù)據(jù)給其他設(shè)備
  • 管理多個(gè)鏈接

本文描述如何使用Classic Bluetooth,Classic Bluetooth是針對(duì)較多對(duì)較多耗電敏感的操作例如流媒體和通信正確的選擇。對(duì)只需低耗電的設(shè)備,Android 4.3 (API 18版)引入用于支持藍(lán)牙低功耗的API。參見 Bluetooth Low Energy了解更多內(nèi)容。

基礎(chǔ)

本文檔描述了如何使用Android藍(lán)牙APIs來完成使用藍(lán)牙進(jìn)行通信所需要的四個(gè)主要任務(wù):設(shè)置藍(lán)牙,檢索周圍匹配的或者可用的設(shè)備,連接設(shè)備以及設(shè)備間傳輸數(shù)據(jù)。所有藍(lán)牙APIs在android.bluetooth 包中。

下面是對(duì)創(chuàng)建藍(lán)牙連接所要用到的類和接口的一個(gè)總結(jié)。

  • BluetoothAdapter
    表示本地藍(lán)牙適配器(藍(lán)牙無線電廣播)。BluetoothAdapter是所有藍(lán)牙交互的入口點(diǎn)。你能夠通過它發(fā)現(xiàn)其它藍(lán)牙設(shè)備,查詢一系列已經(jīng)匹配的設(shè)備,使用已知的MAC地址實(shí)例化一個(gè) BluetoothDevice,創(chuàng)建 BluetoothServerSocket 監(jiān)聽來自其他設(shè)備的通信。

  • BluetoothDevice
    表示遠(yuǎn)程藍(lán)牙設(shè)備。用這個(gè)類通過 BluetoothSocket能夠請(qǐng)求同遠(yuǎn)程設(shè)備的鏈接,或者查詢?cè)O(shè)備的名字、地址、類和綁定狀態(tài)。

  • BluetoothSocket
    表示藍(lán)牙套接字通信(類似于TCP Socket)的接口。這是允許應(yīng)用通過InputStream和OutputStream與其他藍(lán)牙設(shè)備進(jìn)行數(shù)據(jù)交換的連接點(diǎn)。

  • BluetoothServerSocket
    表示用于監(jiān)聽即將到來的請(qǐng)求的對(duì)外公開套接字(類似于TCP ServerSocket)。為了連接兩個(gè)安卓設(shè)備,一個(gè)設(shè)備必須用這個(gè)類開放一個(gè)服務(wù)端的套接字。當(dāng)遠(yuǎn)程的藍(lán)牙設(shè)備向該設(shè)備發(fā)起連接請(qǐng)求時(shí),在連接建立的時(shí)候BluetoothServerSocket會(huì)返回一個(gè)BluetoothSocket。

  • BluetoothClass
    描述藍(lán)牙設(shè)備的普遍的特征和功能。這是一個(gè)只讀的屬性集合,它定義了設(shè)備的主要和次要的設(shè)備類及服務(wù)。然而,它并沒有可靠地描述所有的藍(lán)牙配置及設(shè)備所支持的服務(wù),但是作為設(shè)備類型的提示是很有用的。

  • BluetoothProfile
    表示藍(lán)牙配置文件的接口。一個(gè)藍(lán)牙配置文件是一個(gè)無線接口規(guī)格說明書,用于基于藍(lán)牙設(shè)備間的通信。一個(gè)示例就是免提裝置配置文件,想了解更多有關(guān)配置文件的內(nèi)容,參見Working with Profiles

  • BluetoothHeadset
    它為同移動(dòng)手機(jī)一起使用的藍(lán)牙耳機(jī)提供支持。這包括藍(lán)牙耳機(jī)和免提裝置配置文件。

  • BluetoothA2dp
    定義了通過藍(lán)牙連接能夠從一個(gè)設(shè)備傳輸多么高質(zhì)量的音頻到另外一個(gè)設(shè)備上。"A2DP"表示高級(jí)音頻發(fā)布配置文件(Advanced Audio Distribution Profile)。

  • BluetoothHealth
    表示能夠控制藍(lán)牙服務(wù)的醫(yī)療設(shè)備配置文件代理。

  • BluetoothHealthCallback
    用于實(shí)現(xiàn)BluetoothHealth 回調(diào)的抽象類。你必須繼承這個(gè)類并且實(shí)現(xiàn)其中的回調(diào)方法來接收與應(yīng)用注冊(cè)狀態(tài)和藍(lán)牙通道狀態(tài)有關(guān)的的變化信息。

  • BluetoothHealthAppConfiguration
    表示藍(lán)牙醫(yī)療第三方應(yīng)用所注冊(cè)的用來與遠(yuǎn)程藍(lán)牙醫(yī)療設(shè)備通信的配置信息。

  • BluetoothProfile.ServiceListener
    用于通知 BluetoothProfile跨進(jìn)程通信的客戶端已經(jīng)連接或者從服務(wù)端斷開鏈接的接口(也就是說,內(nèi)部服務(wù)運(yùn)行了一個(gè)配置文件)。

藍(lán)牙權(quán)限

為了在你的應(yīng)用中使用藍(lán)牙特性,你必須聲明藍(lán)牙權(quán)限 BLUETOOTH。你需要這個(gè)權(quán)限來執(zhí)行任何藍(lán)牙通信,例如請(qǐng)求連接,接受連接以及傳輸數(shù)據(jù)。

如果你希望你的應(yīng)用初始化設(shè)備感應(yīng)或者操作藍(lán)牙設(shè)置,你也必須聲明 BLUETOOTH_ADMIN 權(quán)限。大多數(shù)應(yīng)用為了具有感應(yīng)周圍藍(lán)牙設(shè)備的能力,需要單獨(dú)申明這個(gè)權(quán)限。除非這個(gè)應(yīng)用是一個(gè)能夠根據(jù)用戶請(qǐng)求修改藍(lán)牙設(shè)置的超級(jí)管理員,該權(quán)限所賦予的其他功能不應(yīng)被當(dāng)使用。注意:如果使用 BLUETOOTH_ADMIN 權(quán)限,你仍須申明 BLUETOOTH權(quán)限。

在你的manifest文件中聲明藍(lán)牙權(quán)限。例如:

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

參見 <uses-permission>了解更多有關(guān)申明配置權(quán)限的信息。

設(shè)置藍(lán)牙

在應(yīng)用能夠通過藍(lán)牙通信之前,你需要校驗(yàn)設(shè)備是否支持藍(lán)牙,如果支持,請(qǐng)確保藍(lán)牙是出于開啟狀態(tài)。如果不支持藍(lán)牙,你應(yīng)當(dāng)優(yōu)雅地禁止掉任何藍(lán)牙功能。如果支持藍(lán)牙但是沒有打開,你可以在不離開應(yīng)用的情況下請(qǐng)求應(yīng)用開啟藍(lán)牙,這個(gè)設(shè)置通過使用BluetoothAdapter在兩步內(nèi)完成。

  1. 獲取 BluetoothAdapter
    所有的藍(lán)牙Activity都需要BluetoothAdapter 。為了得到BluetoothAdapter,調(diào)用靜態(tài)方法getDefaultAdapter() 。這個(gè)方法會(huì)返回一個(gè)BluetoothAdapter,他表示設(shè)備自身的藍(lán)牙適配器(藍(lán)牙無線電廣播)。整個(gè)系統(tǒng)只存在一個(gè)藍(lán)牙適配器,你的應(yīng)用通過它來交互。如果 getDefaultAdapter()返回為null則表示設(shè)備不支持藍(lán)牙,只能到此為止。例如:
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
   // Device does not support Bluetooth
}
  1. 打開藍(lán)牙

**圖1:** 開啟藍(lán)牙對(duì)話框
下一步,你需要確保藍(lán)牙已經(jīng)啟用。調(diào)用 isEnabled() 去檢查藍(lán)牙是否已經(jīng)啟用。如果方法返回為false,則藍(lán)牙未被啟用。要請(qǐng)求啟用藍(lán)牙,調(diào)用 [startActivityForResult()](https://developer.android.com/reference/android/app/Activity.html#startActivityForResult(android.content.Intent, int))并且攜帶action intent參數(shù) ACTION_REQUEST_ENABLE 。這會(huì)發(fā)起一個(gè)請(qǐng)求,通過系統(tǒng)設(shè)置來開啟藍(lán)牙(同時(shí)不停止你的應(yīng)用)。例如:

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

會(huì)顯示一個(gè)對(duì)話框請(qǐng)求用戶權(quán)限來啟用藍(lán)牙,如圖1所示。如果用戶點(diǎn)擊“yes”,則系統(tǒng)會(huì)開始啟用藍(lán)牙,并且一旦你的應(yīng)用處理完畢(或者失?。缑娼裹c(diǎn)將會(huì)回到你的應(yīng)用。

傳輸給 [startActivityForResult()](https://developer.android.com/reference/android/app/Activity.html#startActivityForResult(android.content.Intent, int))的常量REQUEST_ENABLE_BT是一個(gè)本地定義的整型(必須大于零),在[onActivityResult()](https://developer.android.com/reference/android/app/Activity.html#onActivityResult(int, int, android.content.Intent))實(shí)現(xiàn)中,系統(tǒng)將這個(gè)整型作為requestCode參數(shù)返回給你。

如果藍(lán)牙啟動(dòng)成功,在 [onActivityResult()](https://developer.android.com/reference/android/app/Activity.html#onActivityResult(int, int, android.content.Intent)) 回調(diào)中,你的Activity接收到RESULT_OK結(jié)果碼。如果藍(lán)牙由于某一個(gè)錯(cuò)誤沒有啟動(dòng),則返回碼為 RESULT_CANCELED。

此外,你的應(yīng)用同樣能也夠監(jiān)聽Intent廣播 ACTION_STATE_CHANGED,每當(dāng)藍(lán)牙狀態(tài)發(fā)生改變,系統(tǒng)就會(huì)發(fā)送此廣播。這個(gè)廣播包含額外的參數(shù) EXTRA_STATEEXTRA_PREVIOUS_STATE, 分別包含新的和舊的藍(lán)牙狀態(tài)。這些額外的參數(shù)可能的值有 STATE_TURNING_ON, STATE_ONSTATE_TURNING_OFF,以及 STATE_OFF。在你的應(yīng)用運(yùn)行的時(shí)候監(jiān)測(cè)藍(lán)牙狀態(tài)改變,這時(shí)偵聽廣播變得重要。

小貼士:開啟藍(lán)牙可見性會(huì)自動(dòng)開啟藍(lán)牙。如果打算在執(zhí)行藍(lán)牙Activity之前開啟設(shè)備的可見性,你可以跳過上面的兩部,參考下方內(nèi)容開啟可見性。

查找設(shè)備

使用 BluetoothAdapter,你能夠找到遠(yuǎn)程藍(lán)牙設(shè)備,不論是通過設(shè)備查找還是通過查詢已匹配設(shè)備列表。

設(shè)備感應(yīng)是一個(gè)掃描的過程,會(huì)查詢周圍已經(jīng)開啟藍(lán)牙的設(shè)備,然后請(qǐng)求每一個(gè)設(shè)備的相關(guān)信息(有時(shí)候會(huì)將這個(gè)過程稱之為感應(yīng)、檢索或者掃描),如果一臺(tái)設(shè)備可以被感應(yīng)到,它會(huì)通過共享一些諸如設(shè)備名稱,類型以及它的唯一MAC地址來響應(yīng)感應(yīng)請(qǐng)求。利用這些信息,執(zhí)行查找的設(shè)備能夠選擇去初始化連接到一個(gè)已經(jīng)感應(yīng)到的設(shè)備。

一旦與遠(yuǎn)程的設(shè)備第一次建立起了連接,匹配請(qǐng)求會(huì)自動(dòng)地呈現(xiàn)給用戶,當(dāng)一臺(tái)設(shè)備匹配成功,設(shè)備相關(guān)的基本信息會(huì)被保存起來并且可以利用藍(lán)牙API來讀取。使用已有的遠(yuǎn)程設(shè)備MAC地址可以在仍和時(shí)候建立連接而無需執(zhí)行感應(yīng)(假設(shè)設(shè)備實(shí)在感應(yīng)范圍內(nèi))

記住正在配對(duì)和正在連接之間是存在差別的。進(jìn)行匹配意味著兩個(gè)設(shè)備都知道各自的存在,擁有一個(gè)能夠用來授權(quán)的共享連接密鑰,并且能夠互相建立一個(gè)加密的連接。進(jìn)行連接意味著當(dāng)前設(shè)備間共享一個(gè)RFCOMM信道,并且能夠互相傳遞數(shù)據(jù)。
目前Android藍(lán)牙APIs在一個(gè)RFCOMM連接建立之前要求設(shè)備已經(jīng)配對(duì)。(當(dāng)你使用藍(lán)牙APIs初始化一個(gè)加密連接的時(shí)候,匹配是自動(dòng)被執(zhí)行的。)

下面的章節(jié)描述如何找到已經(jīng)匹配的設(shè)備,或者使用設(shè)備感應(yīng)感應(yīng)新的設(shè)備。

Note:Android平臺(tái)的設(shè)備默認(rèn)是不能夠被感應(yīng)到的。用戶可以通過系統(tǒng)設(shè)置讓設(shè)備在一個(gè)有限的時(shí)間內(nèi)能夠被感應(yīng)到。或者在不離開當(dāng)前應(yīng)用的情況下,能夠請(qǐng)求用戶使得設(shè)備能夠可以被感應(yīng),下面的內(nèi)容講如何讓設(shè)備被感應(yīng)到。

查詢已匹配的設(shè)備

在執(zhí)行設(shè)備感應(yīng)之前,查詢已匹配設(shè)備集合,來確認(rèn)期望設(shè)備可見是有價(jià)值的。要做到這一點(diǎn),調(diào)用getBondedDevices()。這會(huì)返回代表已匹配設(shè)備BluetoothDevice
的一個(gè)集合。例如,你能夠查詢所有已匹配設(shè)備,然后使用ArrayAdapter,將每個(gè)設(shè)備的名稱顯示給用戶。

為了初始化一個(gè)連接,只需要 BluetoothDevice 對(duì)象中的MAC地址參數(shù)。在這個(gè)例子中,它被保存為一個(gè)ArrayAdapter的一部分用于顯示給用戶。MAC地址可以在稍后為了初始化連接的時(shí)候再提取出來。你可以在 Connecting Devices章節(jié)中了解更多有關(guān)創(chuàng)建一個(gè)連接的內(nèi)容。

感應(yīng)設(shè)備

只需簡(jiǎn)單地調(diào)用startDiscovery()即可開始感應(yīng)設(shè)備。這個(gè)處理過程是異步的,并且方法會(huì)立即返回一個(gè)boolean值,用于指明感應(yīng)操作是否已經(jīng)成功啟動(dòng)。感應(yīng)過程通常包含一個(gè)大約十二秒的查詢掃描操作,緊接著的頁面掃描每個(gè)已找到的設(shè)備取出它的藍(lán)牙名稱。

為了接收關(guān)于每個(gè)感應(yīng)到的設(shè)備的信息,你的應(yīng)用必須為 ACTION_FOUND intent注冊(cè)一個(gè)BroadcastReceiver。對(duì)每個(gè)設(shè)備,系統(tǒng)將會(huì)廣播 ACTION_FOUND intent。這個(gè)intent攜帶額外的參數(shù) EXTRA_DEVICEEXTRA_CLASS,分別包含一個(gè)相應(yīng)的BluetoothDevice 和一個(gè) BluetoothClass,例如,下面演示了當(dāng)設(shè)備被發(fā)現(xiàn)的時(shí)候,你可以如何注冊(cè)對(duì)廣播的處理。

// 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

為了初始化一個(gè)連接,只需要來自 BluetoothDevice 的是MAC地址。在上面的例子中,將其保存為ArrayAdapter 的一部分展示給用戶。MAC地址可以在稍后為了初始化連接的時(shí)候再提取出來。你可以在Connecting Devices章節(jié)中了解更多有關(guān)創(chuàng)建一個(gè)連接的內(nèi)容

注意: 對(duì)于藍(lán)牙適配器,執(zhí)行設(shè)備查詢是一個(gè)重度操作,將會(huì)消耗很多它的資源。一旦你發(fā)現(xiàn)了一個(gè)要去連接的設(shè)備,在你試圖開始連接之前,確認(rèn)你總是使用cancelDiscovery()來停止查找。同樣,如果你已經(jīng)和一臺(tái)設(shè)備保持著一個(gè)連接,則執(zhí)行查找會(huì)很大程度上削減這個(gè)連接的可用帶寬,因此當(dāng)你已經(jīng)連接時(shí),你不應(yīng)當(dāng)執(zhí)行查找操作。

開啟可見性

如果你希望本地設(shè)備對(duì)其他設(shè)備是可見的,調(diào)用 [startActivityForResult(Intent, int)](https://developer.android.com/reference/android/app/Activity.html#startActivityForResult(android.content.Intent, int)),并且使用ACTION_REQUEST_DISCOVERABLE action Intent。這會(huì)通過系統(tǒng)設(shè)置發(fā)出一個(gè)開啟可見性模式的請(qǐng)求(同時(shí)不會(huì)停止你的應(yīng)用)。默認(rèn)的,這個(gè)設(shè)備會(huì)在120秒內(nèi)成為可被查找的。通過添加Intent EXTRA_DISCOVERABLE_DURATION extra數(shù)據(jù),你能夠定義一個(gè)不同的持續(xù)時(shí)間。一個(gè)app能夠設(shè)定的最大持續(xù)時(shí)間為3600秒,并且0意味著這個(gè)設(shè)備總是可以被查找到的。任何小于0并且大于3600的值都將自動(dòng)設(shè)置為120秒。例如,如下程序片段設(shè)置持續(xù)時(shí)間為300:

Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);

**圖 2:** 打開藍(lán)牙可見性對(duì)話框

會(huì)顯示一個(gè)對(duì)話框,請(qǐng)求開啟設(shè)備可見性的用戶權(quán)限,如圖2所示。如果用戶響應(yīng)“Yes”,則設(shè)備在給定的時(shí)間內(nèi)將會(huì)成為可被查找的。然后你的activity將會(huì)收到對(duì) [onActivityResult())](https://developer.android.com/reference/android/app/Activity.html#onActivityResult(int, int, android.content.Intent))回調(diào)方法的調(diào)用?;卣{(diào)方法返回的result code和設(shè)備可見性的持續(xù)時(shí)間是相等的。如果用戶響應(yīng)“No.”或者如果發(fā)生了一個(gè)錯(cuò)誤,則這個(gè)result code將會(huì)是 RESULT_CANCELED。

注意:如果在此設(shè)備上沒有打開藍(lán)牙,則啟用設(shè)備可見性將會(huì)自動(dòng)啟用藍(lán)牙。

設(shè)備在分配的時(shí)間后臺(tái)里將會(huì)保持可見模式。當(dāng)可被查找模式發(fā)生改變的時(shí)候,如果你想得到提醒,你可以為 ACTION_SCAN_MODE_CHANGED intent注冊(cè)一個(gè)BroadcastReceiver。這會(huì)包含額外的參數(shù) EXTRA_SCAN_MODE以及 EXTRA_PREVIOUS_SCAN_MODE,它們分別告訴你新的和舊的掃描模式。它們各自可能的值有 SCAN_MODE_CONNECTABLE_DISCOVERABLE, SCAN_MODE_CONNECTABLE,或者 SCAN_MODE_NONE,這表明這個(gè)設(shè)備在可見模式,或者不在可見模式但是依舊能夠收到連接,或者不在可見模式并且不能夠收到連接。如果你將初始化到一個(gè)遠(yuǎn)程設(shè)備的連接,你不需要啟用設(shè)備的可見性。只有當(dāng)你希望你的應(yīng)用保持一個(gè)server socket的時(shí)候,你才需要啟用可見性,這個(gè)server socket用于接收新來到的連接,因?yàn)檫h(yuǎn)程設(shè)備在能夠初始化連接之前,它必須能夠感應(yīng)到這個(gè)設(shè)備。

連接設(shè)備

為了能夠在兩臺(tái)設(shè)備的應(yīng)用之間創(chuàng)建一個(gè)連接,你必須實(shí)現(xiàn)客戶端以及服務(wù)端的機(jī)制,因?yàn)橐慌_(tái)設(shè)備必須打開一個(gè)server socket,同時(shí)另外一個(gè)必須初始化連接(使用服務(wù)端設(shè)備的MAC地址來初始化一個(gè)連接)。當(dāng)兩臺(tái)設(shè)備在相同的RFCOMM信道中各有一個(gè)已連接的 BluetoothSocket 的時(shí)候,服務(wù)端和客戶端被認(rèn)為是互相連接的。此時(shí)每臺(tái)設(shè)備能夠獲得輸入和輸出流并且開始數(shù)據(jù)傳輸,本節(jié)將介紹如何在兩臺(tái)設(shè)備之間初始化連接。

服務(wù)端和客戶端各自以不同的方式獲得所需的BluetoothSocket 。當(dāng)一個(gè)來到的連接被接受的時(shí)候,服務(wù)端將會(huì)收到BluetoothSocket。當(dāng)客戶端打開一個(gè)連接服務(wù)端的RFCOMM信道的時(shí)候,它會(huì)收到它的BluetoothSocket。

一個(gè)技術(shù)實(shí)現(xiàn)就是自動(dòng)將每個(gè)設(shè)備準(zhǔn)備好作為一個(gè)服務(wù)端,因此每個(gè)設(shè)備都將打開一個(gè)server socket并且偵聽連接。然后每個(gè)設(shè)備都能夠初始化一個(gè)到另外一個(gè)設(shè)備的連接并且成為客戶端。另外一方面,一個(gè)設(shè)備能夠顯示地保持住這個(gè)連接并且根據(jù)需要打開一個(gè)server socket,另外一個(gè)設(shè)備能夠簡(jiǎn)單地初始化這個(gè)連接。

**圖 3:** 藍(lán)牙配對(duì)對(duì)話框

注意:如果兩臺(tái)設(shè)備之前沒有配對(duì)過,則在連接過程中,Android框架層會(huì)自動(dòng)給用戶顯示一個(gè)配對(duì)請(qǐng)求提示或者對(duì)話框,如圖3所示。因此當(dāng)試圖連接設(shè)備的時(shí)候,你的應(yīng)用不需要考慮,你的設(shè)備是否已經(jīng)配對(duì)過。在用戶成功配對(duì)之前,你的RFCOMM連接請(qǐng)求會(huì)一直阻塞,如果用戶拒絕配對(duì)的話則RFCOMM連接請(qǐng)求會(huì)失敗,這也有可能是配對(duì)失敗或者請(qǐng)求超時(shí)。

作為服務(wù)端來連接

當(dāng)你希望連接兩臺(tái)設(shè)備的時(shí)候,其中一臺(tái)必須通過保持一個(gè)打開的BluetoothServerSocket來作為服務(wù)器。Server socket的目的是在接受用于偵聽到來的連接請(qǐng)求,同時(shí)當(dāng)一臺(tái)接受請(qǐng)求的時(shí)候,提供一個(gè)已連接的 BluetoothServerSocket。當(dāng)從 BluetoothServerSocket獲得 BluetoothSocket 的時(shí)候,BluetoothServerSocket應(yīng)當(dāng)撤銷,除非你希望接受更多的連接。

關(guān)于UUID
通用唯一標(biāo)識(shí)符(UUID)是為字符串ID而生成的標(biāo)準(zhǔn)化128bit格式,用于唯一標(biāo)示信息。UUID的關(guān)鍵點(diǎn)是它足夠大,以至于你任意隨即選擇都不會(huì)產(chǎn)生沖突。在此情況中,它被用于標(biāo)識(shí)你應(yīng)用的藍(lán)牙服務(wù)。為了獲得一個(gè)你的應(yīng)用可以使用的UUID,你可以從眾多的UUID生成器中任意選擇一個(gè),然后通過fromString(String)實(shí)例化一個(gè)UUID。

下面是設(shè)置一個(gè)server socket以及獲得一個(gè)連接的基本處理流程:

  1. 調(diào)用 [listenUsingRfcommWithServiceRecord(String, UUID)](https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#listenUsingRfcommWithServiceRecord(java.lang.String, java.util.UUID))得到一個(gè) BluetoothServerSocket。
    生成的字符串是你設(shè)備的一個(gè)標(biāo)識(shí),系統(tǒng)會(huì)自動(dòng)將這個(gè)字符串寫入到位于設(shè)備上的新的服務(wù)查找協(xié)議(SDP)數(shù)據(jù)庫條目中(名字是任意的,可能就是你的應(yīng)用名字)。UUID也包含在SDP條目中,并且是允許連接到客戶端設(shè)備的基礎(chǔ)。即是,當(dāng)客戶端試圖去連接這個(gè)設(shè)備的時(shí)候,它會(huì)攜帶一個(gè)UUID,這個(gè)UUID能夠唯一識(shí)別它希望連接的服務(wù)。為了連接能夠被接受,這些UUID必須匹配(見下一步)。

  2. 通過調(diào)用accept()開始偵聽連接請(qǐng)求
    這是一個(gè)阻塞調(diào)用。當(dāng)有一個(gè)連接被接受或者發(fā)生一個(gè)異常的時(shí)候,調(diào)用才會(huì)返回。只有當(dāng)遠(yuǎn)程設(shè)備發(fā)送一個(gè)連接請(qǐng)求,并且?guī)в凶?cè)在監(jiān)聽服務(wù)socket中相匹配的UUID的時(shí)候,連接才會(huì)被接受。當(dāng)連接成功的時(shí)候, accept()會(huì)返回一個(gè)已連接的 BluetoothSocket。

  3. 調(diào)用 close(),除非你希望接收其他的連接。
    這個(gè)操作會(huì)釋放掉server socket以及所有它的資源,但是并不關(guān)閉 accept()返回的已連接的BluetoothSocket。不像TCP/IP,RFCOMM在同一時(shí)刻在每個(gè)信道中,僅僅允許一個(gè)已連接的客戶端,因此在大多數(shù)情況下,在接收一個(gè)已連接socket之后,立刻調(diào)用BluetoothServerSocketclose()是有意義的。

調(diào)用 accept() 不應(yīng)當(dāng)在主Activity UI線程中被執(zhí)行,因?yàn)樗且粋€(gè)阻塞調(diào)用,會(huì)阻止任何其他同應(yīng)用的交互。通常在你的應(yīng)用中,所有 BluetoothServerSocket或者 BluetoothSocket的操作都應(yīng)當(dāng)在一個(gè)新的線程中完成。要想終止一個(gè)像 accept()這樣的阻塞調(diào)用,調(diào)用來自另外一個(gè)線程中 BluetoothServerSocket(或者 BluetoothSocket)中的 close() 方法,此時(shí)阻塞調(diào)用會(huì)立即返回結(jié)果。注意所有在 BluetoothServerSocket或者 BluetoothSocket中的方法都是線程安全的。

示例

下面是用于接收來到連接請(qǐng)求的服務(wù)端組件做了簡(jiǎn)化的線程。

  
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) { }
    }
}

在本例中,僅希望有一個(gè)到來的連接,因此只要接收到連接,并且獲得 BluetoothSocket ,應(yīng)用程序就會(huì)發(fā)送獲得的 BluetoothSocket 到一個(gè)獨(dú)立的線程中,關(guān)閉 BluetoothServerSocket 并且終止循環(huán)。

注意到當(dāng)accept()返回 BluetoothSocket的時(shí)候,這個(gè)socket已經(jīng)連接上了,因此你應(yīng)當(dāng)調(diào)用 connect()
manageConnectedSocket()是應(yīng)用中虛構(gòu)的一個(gè)方法,會(huì)初始化用于傳輸數(shù)據(jù)的線程,這會(huì)在管理一個(gè)連接章節(jié)中討論。
只要你一完成監(jiān)聽到來連接操作,你就應(yīng)當(dāng)關(guān)閉 BluetoothServerSocket。在本例中,一旦獲得了 BluetoothSocket就調(diào)用 close()方法。你也可以在你的線程中提供一個(gè)公共方法,它能夠在你需要停止監(jiān)聽server socket的時(shí)候關(guān)閉私有屬性 BluetoothSocket 。

作為客戶端來連接

為了初始化一個(gè)到遠(yuǎn)程設(shè)備(保持者一個(gè)打開的server socket的設(shè)備)的連接,你必須首先獲得一個(gè)代表遠(yuǎn)程設(shè)備的 BluetoothDevice 對(duì)象(獲取 BluetoothDevice已經(jīng)在上面的 查詢?cè)O(shè)備章節(jié)中提到)。然后你必須使用BluetoothDevice去獲取一個(gè) BluetoothSocket 并且初始化這個(gè)連接。
這里是基本的操作流程:

  1. 使用 BluetoothDevice,通過調(diào)用 createRfcommSocketToServiceRecord(UUID)到一個(gè) BluetoothSocket.
    這將初始化一個(gè)連接到 BluetoothDeviceBluetoothSocket。當(dāng)服務(wù)端打開它的 BluetoothServerSocket(使用 [listenUsingRfcommWithServiceRecord(String, UUID)](https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#listenUsingRfcommWithServiceRecord(java.lang.String, java.util.UUID)),傳遞到這里的UUID必須和服務(wù)端設(shè)備所使用的UUID相匹配。使用同樣的UUID就是簡(jiǎn)單地將UUID字符串硬編碼進(jìn)你的應(yīng)用,并且在服務(wù)端和客戶端代碼中引用它。
  2. 調(diào)用 connect()初始化連接
    一旦調(diào)用這個(gè)接口,為了匹配UUID,系統(tǒng)會(huì)在遠(yuǎn)程設(shè)備上執(zhí)行一個(gè)SDP查詢操作。如果查詢成功,并且遠(yuǎn)程設(shè)備接收這個(gè)連接,則在連接期間它會(huì)共享使用RFCOMM信道,并且connect()調(diào)用也會(huì)返回。這個(gè)方法是一個(gè)阻塞調(diào)用。如果因?yàn)槿魏卧?,連接失敗或者connect() 方法超時(shí)(大約超過12秒),則它會(huì)拋出一個(gè)異常。
    因?yàn)?a target="_blank" rel="nofollow">connect() 是一個(gè)阻塞調(diào)用,這個(gè)連接處理應(yīng)當(dāng)總是在主activity線程之外的一個(gè)獨(dú)立線程中執(zhí)行。

Note:你應(yīng)當(dāng)總是確保,當(dāng)你調(diào)用connect()的時(shí)候,你的設(shè)備沒有在執(zhí)行設(shè)備查找操作。如果正在處理查找,則連接請(qǐng)求將會(huì)非常慢并且很有可能失敗。

示例

下面是一個(gè)線程中初始化一個(gè)藍(lán)牙連接的基本示例

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()是在建立連接之前調(diào)用的。你應(yīng)當(dāng)在連接之前總是執(zhí)行這個(gè)操作,并且不用檢查是否在運(yùn)行,調(diào)用cancelDiscovery()總是安全的(如果你確實(shí)想檢查,調(diào)用 isDiscovering())。
manageConnectedSocket()在應(yīng)用程序中是一個(gè)虛構(gòu)的方法,用于初始化傳輸數(shù)據(jù)的線程,這將在管理連接中討論。
當(dāng)你用完 BluetoothSocket,一定要調(diào)用 close()來完成清理工作。這么做會(huì)立即關(guān)閉掉已連接的socket并且清理所有中間資源。

管理連接

當(dāng)你成功連接兩個(gè)(或者更多)設(shè)備的時(shí)候,每個(gè)都擁有一個(gè)已連接的 BluetoothSocket。這便開始變得比較有趣,因?yàn)槟隳軌蛟谠O(shè)備之間共享數(shù)據(jù)。使用 BluetoothSocket,任意傳輸數(shù)據(jù)的通常處理是簡(jiǎn)單的:

  1. 通過socket,分別使用 getInputStream()getOutputStream()得到InputStreamOutputStream來處理傳輸。

  2. 使用 read(byte[])write(byte[])分別讀數(shù)據(jù)以及寫數(shù)據(jù)到流中。
    就醬!

當(dāng)然有一些實(shí)現(xiàn)細(xì)節(jié)需要考慮。首先,你應(yīng)當(dāng)使用一個(gè)專門的線程來處理所有流的讀寫。這是非常重要的,因?yàn)樗?read(byte[])以及write(byte[]) 方法都是阻塞調(diào)用。 read(byte[])會(huì)一直阻塞,直到存在某些東西從流中取讀,write(byte[])通常不會(huì)阻塞,但是如果遠(yuǎn)程設(shè)備沒有足夠快地調(diào)用 read(byte[]),就會(huì)阻塞流程控制,并且中間緩沖會(huì)溢出。因此,你線程中的主循環(huán)應(yīng)當(dāng)專門被用于從InputStream中讀數(shù)據(jù)。在線程中的一個(gè)獨(dú)立的公共方法,能夠被用于實(shí)例化到OutputStream中的寫操作。

示例

下面是一個(gè)連接管理的可能的范例:

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) { }
    }
}

構(gòu)造函數(shù)獲得所需的流并且一旦執(zhí)行,線程就會(huì)通過InputStream等待數(shù)據(jù)的到來。當(dāng) read(byte[])從流中返回字節(jié)的時(shí)候,使用來自父類中的成員Handler,數(shù)據(jù)被傳送到主Activity中。然后它會(huì)返回并且等待來自stream的更多的字節(jié)。

發(fā)送向外的數(shù)據(jù)就像調(diào)用主Activity的線程中write()方法一樣簡(jiǎn)單。然后這個(gè)方法簡(jiǎn)單地調(diào)用 write(byte[])發(fā)送數(shù)據(jù)到遠(yuǎn)程設(shè)備。

線程中的cancel()方法非常重要,因?yàn)橥ㄟ^關(guān)閉BluetoothSocket,連接能夠在任何時(shí)候被終止。當(dāng)你使用完藍(lán)牙連接之后,你應(yīng)當(dāng)總是調(diào)用這個(gè)方法。

一個(gè)作為使用Bluetooth APIs范例, 參見 Bluetooth Chat sample app

使用Profiles

從Android3.0開始,藍(lán)牙API支持使用藍(lán)牙Profiles。一個(gè)藍(lán)牙profile是一個(gè)無線接口規(guī)格說明,用于支持設(shè)備間基于藍(lán)牙的通信。一個(gè)例子就是Hands-Free profile。對(duì)于一個(gè)移動(dòng)電話連接到一個(gè)無線耳機(jī),兩個(gè)設(shè)備都必須支持Hands-Free profile。

通過實(shí)現(xiàn)接口 BluetoothProfile ,你能夠?qū)懩阕约旱念悂碇С忠粋€(gè)特別的藍(lán)牙Profile。Android藍(lán)牙API提供如下藍(lán)牙profiles的實(shí)現(xiàn):

  • 耳機(jī) 耳機(jī)配置文件對(duì)于移動(dòng)手機(jī)使用藍(lán)牙耳機(jī)提供支持。Android提供 BluetoothHeadset類,它是通過跨進(jìn)程通信(IPC)來控制藍(lán)牙耳機(jī)服務(wù)的代理。其中包括藍(lán)牙耳機(jī)以及Hands-Free(V1.5)profiles。 BluetoothHeadset類包含對(duì)AT命令的支持,想了解更多有關(guān)這部分的內(nèi)容,參見 制造商定制AT命令。

  • A2DP 高級(jí)音頻發(fā)布配置文件(Advanced Audio Distribution Profile profile 簡(jiǎn)稱A2DP)定義了通過一個(gè)藍(lán)牙連接將多高質(zhì)量的音頻能夠從一臺(tái)設(shè)備傳輸?shù)搅硗庖慌_(tái)。Android提供 BluetoothA2dp 代理類,通過跨進(jìn)程通信(IPC)控制藍(lán)牙A2DP。

  • 醫(yī)療設(shè)備 ** Android4.0(API 14)引入對(duì)藍(lán)牙醫(yī)療設(shè)備(Health Device Profile 簡(jiǎn)稱HDP)的支持。這允許你創(chuàng)建應(yīng)用,同支持藍(lán)牙的醫(yī)療設(shè)備進(jìn)行通信,例如心率監(jiān)測(cè),血壓,溫度計(jì),定標(biāo)器等等。對(duì)于支持的設(shè)備列表以及它們對(duì)應(yīng)設(shè)備數(shù)據(jù)特殊碼(device data specialization codes),參見www.bluetooth.org.中的Bluetooth Assigned Numbers*,注意這些值也在 ISO/IEEE 11073-20601 [7] 規(guī)格書中作為在命名規(guī)范附件(Nomenclature Codes Annex )中的MDC_DEV_SPEC_PROFILE_被引用。想了解更多內(nèi)容,參見 醫(yī)療設(shè)備配置文件

這里是使用配置文件的基本步驟:

  1. 得到默認(rèn)的adapter,如設(shè)置藍(lán)牙章節(jié)所述。

  2. 使用 [getProfileProxy()](https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getProfileProxy(android.content.Context, android.bluetooth.BluetoothProfile.ServiceListener, int)) 建立相關(guān)配置文件的配置文件代理對(duì)象的一個(gè)連接。在如下的示例中,配置文件代理對(duì)象是 BluetoothHeadset的一個(gè)實(shí)例。

  3. 建立一個(gè) BluetoothProfile.ServiceListener。當(dāng)BluetoothProfile IPC客戶端已經(jīng)連接到服務(wù)或者和服務(wù)斷開連接的時(shí)候,會(huì)得到listener的通知。

  4. 在 [onServiceConnected()](https://developer.android.com/reference/android/bluetooth/BluetoothProfile.ServiceListener.html#onServiceConnected(int, android.bluetooth.BluetoothProfile))中,獲得一個(gè)配置代理對(duì)象的句柄。

  5. 一旦你獲得配置代理對(duì)象,你能夠使用它來監(jiān)測(cè)連接的狀態(tài),并且執(zhí)行和那個(gè)配置文件相關(guān)的其他操作。
    例如,如下代碼片段顯示如何連接到一個(gè)BluetoothHeadset代理對(duì)象,通過它你能夠控制耳機(jī)配置文件:

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命令

從Android3.0開始,應(yīng)用能夠注冊(cè),接收耳機(jī)發(fā)送的,預(yù)先定義的,制造商預(yù)先定制的AT命令的系統(tǒng)廣播(例如Plantronics + XEVENT命令)。例如,一個(gè)應(yīng)用能夠接收支持已連接設(shè)備電池電量的廣播,并且提醒用戶采取其他需要的行動(dòng)。為ACTION_VENDER_SPECIFIC_
HEADSET_EVENT intent 創(chuàng)建一個(gè)廣播接收器,為耳機(jī)操作制造商定制的AT命令。

醫(yī)療設(shè)備配置文件(HDP)

Android 4.0(API 14版)引入對(duì)藍(lán)牙醫(yī)療設(shè)備配置文件(HDP)的支持。它能讓你創(chuàng)建這樣的應(yīng)用程序,它能夠使用藍(lán)牙和支持藍(lán)牙的醫(yī)療設(shè)備進(jìn)行通信,例如心率監(jiān)測(cè),血壓,溫度計(jì),定標(biāo)器等等。藍(lán)牙醫(yī)療API包括類 BluetoothHealthBluetoothHealthCallback, BluetoothHealthAppConfiguration,這些已經(jīng)在基礎(chǔ)部分描述過。

要使用醫(yī)療設(shè)備API,了解下面這些醫(yī)療設(shè)備配置文件的中的關(guān)鍵概念是很有幫助的。

| 概念 | 說明 |
|:----- :| ------ |
|Source | 定義在HDP中的角色。一個(gè)source代表一個(gè)醫(yī)療設(shè)備(體重計(jì),葡萄糖計(jì)量?jī)x,溫度計(jì)等等。),此設(shè)備將醫(yī)學(xué)數(shù)據(jù)傳輸?shù)嚼鏏ndroid手機(jī)或者平板的一個(gè)智能設(shè)備中。 |
| Sink | 定義在HDP中的角色。一個(gè)sink就是接收醫(yī)學(xué)數(shù)據(jù)的那個(gè)智能設(shè)備。在一個(gè)Android HDP應(yīng)用中,Sink通過一個(gè)BluetoothHealthAppConfiguration對(duì)象來體 |
| Registration | 指為一個(gè)特定的醫(yī)療設(shè)備注冊(cè)一個(gè)sink |
|Connection | 指在一個(gè)智能設(shè)備(例如一個(gè)Android手機(jī)或者平板)和一個(gè)醫(yī)療設(shè)備之間打開一個(gè)信道(channel ) |

創(chuàng)建一個(gè)HDP應(yīng)用

下面是創(chuàng)建一個(gè)Android HDP應(yīng)用所需的基本步驟:

  1. 獲得一個(gè) BluetoothHealth 代理對(duì)象的引用。
    類似于常規(guī)的耳機(jī)和A2DP配置文件設(shè)備,你必須用BluetoothProfile.ServiceListenerHEALTH配置文件類型來調(diào)用 [getProfileProxy()](https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#getProfileProxy(android.content.Context, android.bluetooth.BluetoothProfile.ServiceListener, int)),以便和這個(gè)配置代理對(duì)象建立一個(gè)連接。

  2. 創(chuàng)建一個(gè) BluetoothHealthCallback,并注冊(cè)一個(gè)應(yīng)用配置(BluetoothHealthAppConfiguration)作為一個(gè)醫(yī)療sink。

  3. 建立同醫(yī)療設(shè)備的連接。某些設(shè)備會(huì)初始化這個(gè)連接。對(duì)于那些設(shè)備不需要執(zhí)行這個(gè)步驟。

  4. 當(dāng)同一個(gè)醫(yī)療設(shè)備連接成功的時(shí)候,使用文件描述符( file descriptor)對(duì)醫(yī)療設(shè)備進(jìn)行讀寫。 接收到的數(shù)據(jù)需要使用一個(gè)實(shí)現(xiàn)了IEEE 11073-xxxxx規(guī)范升的醫(yī)療管理器進(jìn)行解析。

  5. 當(dāng)完成操作,關(guān)閉醫(yī)療信道并且注銷這個(gè)應(yīng)用。當(dāng)處于長(zhǎng)時(shí)間不運(yùn)行狀態(tài)的時(shí)候,信道也會(huì)關(guān)閉。

完整的實(shí)現(xiàn)步驟相關(guān)代碼,可以參考Android Bluetooth HDP (Health Device Profile).。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容