Android 藍(lán)牙相關(guān)開發(fā)

描述

最近公司有個(gè)項(xiàng)目,App從后臺(tái)獲取到數(shù)據(jù),App連接打印機(jī),將數(shù)據(jù)在打印機(jī)上打印。公司提供的測試設(shè)備是藍(lán)牙打印機(jī),為了完成項(xiàng)目,決定學(xué)習(xí)一下Android的藍(lán)牙通信機(jī)制,在此記錄全部過程

基于文檔
本文相關(guān)資料:
Android Bluetooth官方文檔:https://developer.android.com/guide/topics/connectivity/bluetooth?hl=zh-cn#EnablingDiscoverability

Android 藍(lán)牙知識(shí)

Android平臺(tái)包含藍(lán)牙網(wǎng)絡(luò)堆棧的支持,設(shè)備能以無線方式與其他藍(lán)牙設(shè)備交換數(shù)據(jù)。應(yīng)用框架提供了通過Android Bluetooth API訪問藍(lán)牙功能的途徑。
使用Bluetooth API,Android應(yīng)用可執(zhí)行以下操作:

  • 掃描其他藍(lán)夜設(shè)備
  • 查詢本地藍(lán)牙適配器的配對藍(lán)牙設(shè)備
  • 建立RFCOMM通道
  • 通過服務(wù)發(fā)現(xiàn)連接到其他設(shè)備
  • 與其他設(shè)備進(jìn)行雙向數(shù)據(jù)傳輸
  • 管理多個(gè)連接

基礎(chǔ)知識(shí)

Android中的 android.bluetooth 包中提供了所有Bluetooth API

BluetoothAdapter

表示本地藍(lán)牙適配器(藍(lán)牙無線裝置)。BluetoothAdapter是所有藍(lán)夜交互的入口點(diǎn)。利用本地藍(lán)牙適配器可以發(fā)現(xiàn)其他藍(lán)牙設(shè)備,查詢綁定(配對)設(shè)備的列表。使用已知的MAC地址實(shí)例化BluetoothDevice。以及創(chuàng)建BluetoothServerSocket 以偵聽來自其他設(shè)備的通信

BluetoothDevice

表示遠(yuǎn)程藍(lán)牙設(shè)備。利用它可以通過BluetoothSocket請求與某個(gè)遠(yuǎn)程設(shè)備建立連接,或查詢有關(guān)該設(shè)備的信息,例如設(shè)備的名稱、地址、類和綁定狀態(tài)等

BluetoothSocket

表示藍(lán)牙套接字接口,允許應(yīng)用通過InputStream和OutputStream與其他藍(lán)牙設(shè)備交換數(shù)據(jù)的連接點(diǎn)

BluetoothServerSocket

表示用于偵聽傳入請求的開放服務(wù)器套接字,要連接兩臺(tái)Android設(shè)備,其中一臺(tái)設(shè)備必須使用此類開放一個(gè)服務(wù)套接字。當(dāng)一臺(tái)遠(yuǎn)程藍(lán)牙設(shè)備向此設(shè)備發(fā)出連接請求是BluetoothServerSocket將會(huì)在接受連接后返回已經(jīng)連接的BluetoothSocket

BluetoothClass

描述藍(lán)牙設(shè)備的一般特征和功能。這是一組只讀屬性,用于定義設(shè)備的主要和次要設(shè)備類及其服務(wù)。不過它不能可靠的描述設(shè)備的所有藍(lán)牙配置文件和服務(wù),而是適合作為設(shè)備類型提示

BluetoothProfile

表示藍(lán)牙配置文件的接口。藍(lán)牙配置文件是適用于設(shè)備間藍(lán)牙通信的無線接口規(guī)范。

BluetoothHeadset

提供藍(lán)牙耳機(jī)支持

BluetoothA2dp

定義高質(zhì)量音頻如何通過藍(lán)牙連接和流式傳輸,從一臺(tái)設(shè)備傳輸?shù)搅硪慌_(tái)設(shè)備 “A2DP”代表高級(jí)音頻分發(fā)配置文件

BluetoothHealth

表示用于控制藍(lán)牙服務(wù)的健康設(shè)備配置文件袋里

BluetoothHealthCallback

用于實(shí)現(xiàn)BluetoothHealth回調(diào)的抽象類

BluetoothHealthAppConfiguration

表示第三方藍(lán)牙健康應(yīng)用注冊的應(yīng)用配置,以便于遠(yuǎn)程藍(lán)牙健康設(shè)備通信

BluetoothProfile.ServiceListener

在BluetoothProfile IPC客戶端連接到服務(wù)或斷開服務(wù)連接時(shí)向其發(fā)送通知的接口

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

要在應(yīng)用中使用藍(lán)牙功能,必須聲明藍(lán)牙權(quán)限BLUETOOTH,需要權(quán)限才能執(zhí)行任何藍(lán)牙通信
如果希望應(yīng)用啟動(dòng)設(shè)備發(fā)現(xiàn)或操作藍(lán)牙設(shè)置,則還必須聲明BLUETOOTH_ADMIN權(quán)限。如果要使用BLUETOOTH_ADMIN權(quán)限,則還必須擁有BLUETOOTH權(quán)限


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

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

在開發(fā)中使用藍(lán)牙時(shí),需要驗(yàn)證設(shè)備支持藍(lán)牙,如果支持藍(lán)牙還需要確保藍(lán)牙啟用。
如果設(shè)備不支持藍(lán)牙,那還開發(fā)個(gè)毛線
設(shè)置藍(lán)牙的步驟分為:

  1. 獲取BluetoothAdapter
    所有藍(lán)牙Activity都需要BluetoothAdapter。要獲取BluetoothAdapter 請調(diào)用靜態(tài)的getDefaultAdapter()方法,這個(gè)方法將返回一個(gè)表示設(shè)備自身的藍(lán)牙適配器(藍(lán)牙無線裝置)的BluetoothAdapter。整個(gè)系統(tǒng)有一個(gè)藍(lán)牙適配器,并且應(yīng)用可使用此對象與設(shè)備交互 如果getDefaultAdapter()返回null,則表示該設(shè)備不支持藍(lán)牙。
 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
        if (adapter == null) {
            // TODO:  設(shè)備不支持藍(lán)牙
            Log.d(AssistUtils.TAG, "device not support bluetooth");
        }
  1. 啟用藍(lán)牙
    需要確保啟用藍(lán)牙,調(diào)用isEnabled()以檢查當(dāng)前是否已經(jīng)啟用藍(lán)牙,如果此方法返回false,則表示藍(lán)牙處于停用狀態(tài)。在這個(gè)時(shí)候,需要我們使用intent來啟用藍(lán)牙,使用ACTION_REQUEST_ENABLE 操作Intent調(diào)用startActivityForResult()。將通過系統(tǒng)設(shè)置發(fā)出啟用藍(lán)牙的請求
 //判斷藍(lán)牙是否可用
        if (!bluetoothAdapter.isEnabled()) {
            Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(intent,REQUESTCODE_BLUETOOTH_ENABLECODE);
        }

調(diào)用后,將顯示如下圖的對話框,請求用戶允許啟用藍(lán)牙沒如果用戶響應(yīng)yes,并在該進(jìn)程完成后將焦點(diǎn)返回到您的應(yīng)用


藍(lán)牙用戶授權(quán)對話框

傳遞給startActivityForResult()的REQUESTCODE_BLUETOOTH_ENABLECODE常量是在局部定義的整型。系統(tǒng)會(huì)將其作為requestCode參數(shù)傳遞會(huì)onActivityResult()實(shí)現(xiàn)。
如果成功啟用藍(lán)牙,Activity將會(huì)在onActivityResult()回調(diào)中收到RESULT_OK結(jié)果代碼。如果由于某個(gè)錯(cuò)誤(用戶響應(yīng)no)而沒有啟用藍(lán)牙,則結(jié)果代碼為RESULT_CANCELED

除此外還可以選擇偵聽 [ACTION_STATE_CHANGED] 廣播 Intent,每當(dāng)藍(lán)牙狀態(tài)發(fā)生變化時(shí),系統(tǒng)都會(huì)廣播此 Intent。 此廣播包含額外字段 [EXTRA_STATE][EXTRA_PREVIOUS_STATE],二者分別包含新的和舊的藍(lán)牙狀態(tài)。 這些額外字段可能的值包括 [STATE_TURNING_ON]、[STATE_ON][STATE_TURNING_OFF][STATE_OFF]。偵聽此廣播適用于檢測在您的應(yīng)用運(yùn)行期間對藍(lán)牙狀態(tài)所做的更改。

/**
 * @author wxblack-mac
 * @DESCRIBE:自定義藍(lán)牙廣播接收者
 * @DATE 2018/11/28 11:04
 * GOOD LUCK
 */
public class PrinterBlueToothReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        int intExtra = intent.getIntExtra(EXTRA_STATE, -1);
        Log.d(AssistUtils.TAG, "onReceive: intExtra" + intExtra);
        if (intExtra == STATE_TURNING_ON) {
            Log.d(AssistUtils.TAG, "onReceive: 藍(lán)牙開啟");
        }
    }
}
  • 注冊廣播
 private void registerPrintReceiver() {
        printerBlueToothReceiver = new PrinterBlueToothReceiver();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
        registerReceiver(printerBlueToothReceiver, intentFilter);
    }

查找設(shè)備

使用BluetoothAdapter 可以通過設(shè)備發(fā)現(xiàn)或通過查詢配對設(shè)備的列表來查找遠(yuǎn)程的藍(lán)牙設(shè)備
設(shè)備發(fā)現(xiàn)是一個(gè)掃描過程,它會(huì)搜索局部區(qū)域內(nèi)已啟動(dòng)藍(lán)牙功能的設(shè)備,然后請求一些關(guān)于各臺(tái)設(shè)備的信息。但是局部區(qū)域內(nèi)的藍(lán)牙設(shè)備僅在其當(dāng)前已啟用可檢測性是才會(huì)響應(yīng)發(fā)現(xiàn)請求。如果設(shè)備可檢測到,設(shè)備將通過共享一些信息(例如設(shè)備名稱、類及其唯一MAC地址)來響應(yīng)發(fā)現(xiàn)請求。利用此信息,執(zhí)行發(fā)現(xiàn)的設(shè)備可以選擇發(fā)起到設(shè)備的連接
在當(dāng)首次與設(shè)備建立連接后,就會(huì)自動(dòng)向用戶顯示配對請求。設(shè)備完成配對后,將會(huì)保存關(guān)于該設(shè)備的基本信息。并且可以使用上面提到過的Bluetooth API讀取這些信息。利用遠(yuǎn)程設(shè)備的已知MAC地址可以隨時(shí)向其
發(fā)起連接,而無需執(zhí)行發(fā)現(xiàn)操作。
被配對與被連接之間是存在差別的,被配對意味著兩臺(tái)設(shè)備知曉彼此的存在,具有可用于身份驗(yàn)證的共享鏈路秘鑰,并且能夠與彼此簡歷加密連接。而被連接意味著設(shè)備當(dāng)前共享一個(gè)RFCOMM通道,并且能夠向彼此傳輸數(shù)據(jù)。當(dāng)前的Android Bluetooth API要求對設(shè)備進(jìn)行配對然后才能建立RFCOMM連接。

也就是說 使用本地藍(lán)牙適配器可以掃描發(fā)現(xiàn)藍(lán)夜設(shè)備,設(shè)備發(fā)現(xiàn)會(huì)掃描啟用藍(lán)牙功能的設(shè)備,然后可以請求啟用了藍(lán)牙發(fā)現(xiàn)設(shè)備的相關(guān)設(shè)備信息,如MAC地址、設(shè)備名稱等。在首次建立設(shè)備連接后,就會(huì)向用戶發(fā)起配對請求,完成配對后,會(huì)保存配對設(shè)備的相關(guān)信息。如果利用遠(yuǎn)程設(shè)備的 唯一的MAC地址時(shí),并且當(dāng)設(shè)備處于發(fā)現(xiàn)狀態(tài)時(shí),可以直接發(fā)起連接,而無需執(zhí)行發(fā)現(xiàn)掃描的操作

查詢配對設(shè)備

在執(zhí)行設(shè)備發(fā)現(xiàn)以前,可以查詢已經(jīng)配對的設(shè)備集,以了解所需的設(shè)備是否處于已知狀態(tài)??梢哉{(diào)用getBonderDevices()。這將返回標(biāo)識(shí)已配對設(shè)備的一組BluetoothDevice.

//獲取已經(jīng)配對的設(shè)備,返回一個(gè)集合
 /**
     * 查詢配對設(shè)備集
     */
    private void findPairDevices() {
        Set<BluetoothDevice> bondedDevices = bluetoothAdapter.getBondedDevices();
        if (bondedDevices.size() > 0) {
            for (BluetoothDevice bluetoothDevice : bondedDevices) {
                Log.d(TAG, "findPairDevices: " + bluetoothDevice.getName() + "\taddress:" + bluetoothDevice.getAddress());
            }
        }
    }

發(fā)現(xiàn)設(shè)備

要開始發(fā)現(xiàn)設(shè)備,需要調(diào)用startDiscovery()。該進(jìn)程為異步進(jìn)程,并且該方法會(huì)立即返回一個(gè)布爾值,表示是否已成功啟動(dòng)發(fā)現(xiàn)操作。啟動(dòng)的發(fā)現(xiàn)進(jìn)程一般包含12秒的查詢掃描,之后對每臺(tái)發(fā)現(xiàn)的設(shè)備進(jìn)行頁面掃描。
App必須針對ACTION_FOUND Intent注冊一個(gè)BroadcastReceiver,以便接收每臺(tái)發(fā)現(xiàn)的設(shè)備的相關(guān)信息。針對每臺(tái)設(shè)備,系統(tǒng)將會(huì)廣播ACTION_FOUNT 的Intent。這個(gè)返回的Intent將攜帶額外的字段 EXTRA_DEVICEEXTRA_CLASS。分別包含BluetoothDeviceBluetoothClass

 //獲取發(fā)現(xiàn)的設(shè)備實(shí)體
        findDeviceReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                    //獲取發(fā)現(xiàn)的設(shè)備實(shí)體
                    BluetoothDevice device = (BluetoothDevice) intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                    Log.d(TAG, "發(fā)現(xiàn)的設(shè)備" + device.getAddress() + "\tname:" + device.getName());
                }
            }
        };
        IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
        registerReceiver(findDeviceReceiver, filter);

執(zhí)行設(shè)備發(fā)現(xiàn)對于藍(lán)牙適配器來說是一個(gè)非常繁重的操作過程,并且會(huì)消耗大量的資源,找到要連接的設(shè)備后,確保使用cancelDiscovery()停止發(fā)現(xiàn),然后嘗試連接,如果已經(jīng)保持與某臺(tái)設(shè)備的連接,執(zhí)行發(fā)現(xiàn)操作可能會(huì)減少可用于該連接的帶寬,不應(yīng)該在處于連接狀態(tài)時(shí)執(zhí)行發(fā)現(xiàn)操作

啟用可檢測性

如果希望本地設(shè)備可以被其他設(shè)備檢測到,請使用ACTION_REQUEST_DISCOVERABLE操作Intent調(diào)用startActivityForResult(),浙江通過系統(tǒng)設(shè)置發(fā)出啟用可檢測到模式的請求。在默認(rèn)情況下,設(shè)備將變?yōu)榭蓹z測到并持續(xù)120秒鐘??梢酝ㄟ^添加EXTRA_DISCOVERABLE_DURATION Intent Extra來定義不同的持續(xù)時(shí)間。應(yīng)用可以設(shè)置的最大持續(xù)時(shí)間為3600秒,值為0則表示設(shè)備始終可檢測到 任何小于0或者大于3600的值都會(huì)自動(dòng)這只為120秒

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

在調(diào)用后,將顯示對話框,請求用戶允許將設(shè)備設(shè)置為可檢測到,如果用戶響應(yīng)yes,則設(shè)備將變?yōu)榭蓹z測到并持續(xù)指定的時(shí)間量。然后您的Activity將會(huì)收到對onActivityResult()回調(diào)的調(diào)用,其結(jié)果代碼等于設(shè)備可檢測到的持續(xù)時(shí)間。如果用戶選擇NO或者出現(xiàn)錯(cuò)誤,結(jié)果代碼將為 [RESULT_CANCELED]`

注:如果尚未在設(shè)備上啟動(dòng)藍(lán)牙,則啟用設(shè)備可檢測性將會(huì)自動(dòng)啟動(dòng)藍(lán)牙

設(shè)備將在分配的時(shí)間內(nèi)以靜默方式保持可檢測到模式。如果希望在可檢測到模式發(fā)生變化時(shí)收到通知,可以針對ACTION_SCAN_MODE_CHANGED intent注冊BroadcastReceiver。接收到的intent將包含額外字段EXTRA_SCAN_MODEEXTRA_PREVIOUS_SCAN_MODESCAN_MODE_NONE這些值分別指示設(shè)備處于可檢測到模式、未處于可檢測到模式但仍能接收連接,或未處于可檢測到模式并且無法接收連接

如果您將要發(fā)起到遠(yuǎn)程設(shè)備的連接,則無需啟用設(shè)備可檢測性。僅當(dāng)您希望您的應(yīng)用托管將用戶接受傳入連接的服務(wù)器套接字時(shí),才有必要啟用可檢測性。因?yàn)檫h(yuǎn)程設(shè)備必須能夠發(fā)現(xiàn)該設(shè)備,然后才能發(fā)起連接

連接設(shè)備

要在兩臺(tái)設(shè)備上的應(yīng)用之間創(chuàng)建連接,必須同時(shí)實(shí)現(xiàn)服務(wù)器端和客戶端機(jī)制,因?yàn)槠渲幸慌_(tái)設(shè)備必須開放服務(wù)器套接字,而另一臺(tái)設(shè)備必須發(fā)起連接。當(dāng)服務(wù)器和客戶端在同一RFCOMM通道上分別擁有已連接的BluetoothSocket時(shí),二者將被視為彼此連接。在這種情況下,每臺(tái)設(shè)備都能獲得輸入和輸出流式傳輸,并且可以開始傳輸數(shù)據(jù)。

服務(wù)器設(shè)備和客戶端設(shè)備分別以不同的方法獲得需要的BluetoothSocket。服務(wù)器將在傳入連接被接受時(shí)收到套接字。
客戶端將在其打開到服務(wù)器的RFCOMM通道時(shí)收到該套接字
一種實(shí)現(xiàn)技術(shù)是自動(dòng)將每臺(tái)設(shè)備準(zhǔn)備為一個(gè)服務(wù)器,從而使每臺(tái)設(shè)備開發(fā)一個(gè)服務(wù)器套接字并偵聽連接,然后任一設(shè)備可以發(fā)起與另一臺(tái)設(shè)備的連接,并成為客戶端。或者其中一臺(tái)設(shè)備可顯式“托管”連接并按需開放一個(gè)服務(wù)器套接字,而另一臺(tái)設(shè)備則直接發(fā)起連接

連接為服務(wù)器

當(dāng)您需要連接兩臺(tái)設(shè)備時(shí),其中一臺(tái)設(shè)備必須通過保持開放的BluetoothServerSocket來充當(dāng)服務(wù)器。服務(wù)器套接字的用途是偵聽傳入的連接請求,并在接受一個(gè)請求后提供已經(jīng)連接的BluetoothSocket。從BluetoothServerSocket獲取BluetoothSocket后,可以舍棄BluetoothServerSocket。除非需要更多的連接時(shí)可以不用舍棄

關(guān)于UUID

通用唯一標(biāo)識(shí)符 UUID 是用于唯一標(biāo)識(shí)信息的字符串ID的128位標(biāo)準(zhǔn)化格式。UUID的特點(diǎn)是其足夠龐大,因此,可以選擇任意隨機(jī)值而不會(huì)發(fā)生沖突??梢员挥糜谖ㄒ粯?biāo)識(shí)應(yīng)用的藍(lán)牙服務(wù)

設(shè)置服務(wù)器套接字并接受連接的基本過程:

1.通過調(diào)用listenUsingRfcommWithServiceRecord(String,UUID)獲取BluetoothServerSocket
該字符串是服務(wù)的可識(shí)別名稱,系統(tǒng)會(huì)自動(dòng)將其寫入到設(shè)備上的新服務(wù)發(fā)現(xiàn)協(xié)議(SDP)數(shù)據(jù)庫條目(可使用任意名稱)。UUID也包含在SDP條目中,并且將作為與客戶端設(shè)備的連接協(xié)議的基礎(chǔ)。當(dāng)客戶端嘗試連接此設(shè)備時(shí),它會(huì)攜帶能夠唯一標(biāo)識(shí)其想要連接的服務(wù)的UUID。兩個(gè)UUID必須匹配,在下一步中,連接才會(huì)被接受
2.通過調(diào)用accept()開始偵聽連接請求
阻塞調(diào)用,將在連接被接受或發(fā)生異常時(shí)返回。僅當(dāng)遠(yuǎn)程設(shè)備發(fā)送的連接請求中所包含的UUID與向此偵聽服務(wù)器套接字注冊的UUID相匹配時(shí),連接才會(huì)被接受。操作成功后,accept()將會(huì)返回已連接的BluetoothSocket
3.除非想要接受更多連接,否則請調(diào)用close()
這將釋放服務(wù)器套接字及其所有資源。但不會(huì)關(guān)閉accept()所返回的已連接的BluetoothSocket。與TCP/IP不同,RFCOMM一次只允許每個(gè)通道有一個(gè)已連接的客戶端。
accept()調(diào)用不應(yīng)在主Activity UI線程中執(zhí)行,因?yàn)槭亲枞{(diào)用會(huì)造成ANR。

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

連接為客戶端

要發(fā)起與遠(yuǎn)程設(shè)備的連接,必須先獲取標(biāo)識(shí)該遠(yuǎn)程設(shè)備的BluetoothDevice對象。然后,必須使用BluetoothDevice來獲取BluetoothSocket并發(fā)起連接

基本過程

  1. 使用BluetoothDevice,通過調(diào)用createRfcommScoketToServiceRecord(UUID)獲取BluetoothSocket
    這將初始化將要連接到BluetoothDeviceBluetoothSocket。此處傳遞的UUID必須與服務(wù)器設(shè)備在使用listenUsingRfcommWithServiceRecord(String,UUID)開放其BluetoothServerSocket所使用的UUID相匹配。
  2. 通過調(diào)用connect()發(fā)起連接
    執(zhí)行此調(diào)用時(shí),系統(tǒng)將會(huì)在遠(yuǎn)程設(shè)備上執(zhí)行SDP查找,以便匹配UUID。如果查找成功并且遠(yuǎn)程設(shè)備接受了該連接。它將共享RFCOMM通道以便在連接期間使用。并且connect()將會(huì)返回。此方法為阻塞調(diào)用,應(yīng)當(dāng)在主線程UI線程之外的線程執(zhí)行。
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) { }
    }
}

在建立連接之前會(huì)調(diào)用cancelDiscovery(),在進(jìn)行連接之前應(yīng)始終執(zhí)行此調(diào)用,而且調(diào)用時(shí)無需實(shí)際檢查其是否正在運(yùn)行(isDiscovering()進(jìn)行檢查)

管理連接

在成功連接兩臺(tái)(多臺(tái))設(shè)備之后,每臺(tái)設(shè)備都會(huì)有一個(gè)已連接的BluetoothSocket.使用BluetoothSocket傳輸數(shù)據(jù)的方式為:

  1. 獲取InputStream和OutputStream.二者分別通過套接字以及getInputStream()getOutoputStream()來處理傳輸數(shù)據(jù)
    2.使用read(buye[])write(byte[])讀取數(shù)據(jù)并寫入到流式傳輸
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ì)等待數(shù)據(jù)從輸入流中流出。當(dāng)read(byte[])返回字節(jié)時(shí),數(shù)據(jù)將通過父類的Handler被發(fā)送至Activity。之后再返回并等待更多的字節(jié)流。

發(fā)送數(shù)據(jù)則僅僅需要簡單地調(diào)用線程的write()方法即可。

線程中的cancel()方法很重要,因?yàn)檫B接可以隨時(shí)在任意時(shí)間通過BluetoothSocket終止。該方法在結(jié)束使用藍(lán)牙連接后,應(yīng)當(dāng)總是被調(diào)用。

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

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

  • Android平臺(tái)支持藍(lán)牙網(wǎng)絡(luò)協(xié)議棧,實(shí)現(xiàn)藍(lán)牙設(shè)備之間數(shù)據(jù)的無線傳輸。本文檔描述了怎樣利用android平臺(tái)提供的...
    Camming閱讀 3,482評論 0 3
  • 最近項(xiàng)目使用藍(lán)牙,之前并沒有接觸,還是發(fā)現(xiàn)了很多坑,查閱了很多資料,說的迷迷糊糊,今天特查看官方文檔。 說下遇到的...
    King9527閱讀 1,919評論 0 1
  • Android 平臺(tái)包含藍(lán)牙網(wǎng)絡(luò)堆棧支持,憑借此項(xiàng)支持,設(shè)備能以無線方式與其他藍(lán)牙設(shè)備交換數(shù)據(jù)。應(yīng)用框架提供了通過...
    虎三呀閱讀 881評論 0 1
  • 藍(lán)牙 注:本文翻譯自https://developer.android.com/guide/topics/conn...
    RxCode閱讀 9,006評論 11 99
  • 公司的項(xiàng)目最近需要用到藍(lán)牙開發(fā)的相關(guān)內(nèi)容,因此特地查閱了Google官方文檔的內(nèi)容并進(jìn)行二次整理,希望能對需要學(xué)習(xí)...
    Chuckiefan閱讀 32,711評論 44 123

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