Android-藍牙聊天demo

官方文檔:https://developer.android.com/guide/topics/connectivity/bluetooth

Android 中將藍牙分為傳統(tǒng)藍牙低功耗藍牙(Bluetooth low energy)兩種。后者的優(yōu)勢在于快速搜索,快速連接,超低功耗保持連接和數(shù)據(jù)傳輸,同時低功耗帶來的缺點是數(shù)據(jù)傳輸速率低,所以多用在可穿戴式設(shè)備。

在這里我們主要介紹使用傳統(tǒng)藍牙來實現(xiàn)一個聊天的數(shù)據(jù)傳輸 demo。以下內(nèi)容基本都是基于官方文檔的二次闡述,以及一些疑惑的查找到的解答,最后在 demo 里面有對藍牙的相關(guān)操作進行了封裝。先貼個圖看看效果吧:


藍牙.png

基礎(chǔ)知識

BluetoothAdapter: 本地藍牙適配器,我們在發(fā)現(xiàn)設(shè)備,配對的時候都得用上它。

BluetoothDevice: 遠程藍牙設(shè)備,就是代表著你可以連接的一個設(shè)備,里面存儲名字,MAC地址等信息。

BluetoothSocket 和 BluetoothServerSocket: 藍牙套接字,和 TCP 的 Socket 相似。一臺設(shè)備開啟一個 ServerSocket 并監(jiān)聽,另一臺設(shè)備開啟 Socket 進行連接,以此實現(xiàn)一個端對端的連接和數(shù)據(jù)傳輸。

UUID: 唯一識別符。它被用于唯一標(biāo)識應(yīng)用的藍牙服務(wù)(不是表示藍牙設(shè)備)。

Q1:為什么網(wǎng)上的大多數(shù)例子都是使用 00001101-0000-1000-8000-00805F9B34FB 這個UUID?
A1:這是因為一個藍牙設(shè)備里面可以提供諸多服務(wù),如A2DP(藍牙音頻傳輸)、HEADFREE(免提)、SPP(串口通信) 等等。而上面的字符串碼就是 SPP 的 UUID,基本藍牙板上默認就是這個值,我們可以通過UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")來將字符串轉(zhuǎn)成 UUID。
在連接藍牙串口板我們往往就會使用上面的UUID,但是如果 Android 端對端的話,建議自己自己設(shè)定 UUID,這樣別人的 UUID 就連不上了。

實現(xiàn)一個藍牙聊天demo

要實現(xiàn)一個藍牙聊天demo,首先我們有兩臺有藍牙功能的設(shè)備,這里我用了兩臺手機。按照流程一般來說要開啟藍牙-搜索設(shè)備-配對設(shè)備-連接-通信。如此就能實現(xiàn)一個基本的藍牙通信。

第一步:權(quán)限

在 Android 中沒有權(quán)限寸步難行。要使用藍牙,還需要聲明相應(yīng)的權(quán)限。

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

BLUETOOTH 是基本的權(quán)限,用于你的藍牙連接,數(shù)據(jù)傳輸?shù)取?/p>

BLUETOOTH_ADMIN 一般應(yīng)只用于發(fā)現(xiàn)本地藍牙設(shè)備。

除非該應(yīng)用是將要應(yīng)用戶請求修改藍牙設(shè)置的“超級管理員”,否則不應(yīng)使用此權(quán)限所授予的其他能力。

另:如果要使用 BLUETOOTH_ADMIN 權(quán)限,則還必須擁有 BLUETOOTH 權(quán)限。

此外會發(fā)現(xiàn)我這里比官方文檔還多了個 ACCESS_COARSE_LOCATION,這是因為我在實測過程中,我的測試機Android 8.0 系統(tǒng)中,藍牙掃描沒有掃描出信息,但是系統(tǒng)是有的。在網(wǎng)上一番尋找之后發(fā)現(xiàn)在 Android 6.0 之后還需要一個模糊定位的權(quán)限,否則掃描功能無效。

google 文檔:為給用戶提供更嚴(yán)格的數(shù)據(jù)保護,從此版本(6.0)開始,對于使用 WLAN API 和 Bluetooth API 的應(yīng)用,Android 移除了對設(shè)備本地硬件標(biāo)識符的編程訪問權(quán)。WifiInfo.getMacAddress()方法和 BluetoothAdapter.getAddress() 方法現(xiàn)在會返回常量值 02:00:00:00:00:00。
現(xiàn)在,要通過藍牙和 WLAN 掃描訪問附近外部設(shè)備的硬件標(biāo)識符,您的應(yīng)用必須擁有 ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION 權(quán)限。

關(guān)于動態(tài)權(quán)限申請在此不作累述,小伙伴們可以自己去實現(xiàn)。

第二步:啟動藍牙

1、獲取 BluetoothAdapter

BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
    //TODO 設(shè)備不支持藍牙,阻斷用戶操作
}

2、啟動藍牙

if(!mBlueAdapter.isEnabled()){
    //請求藍牙
    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}

系統(tǒng)將會彈窗提示用戶是否開啟藍牙,用戶的選擇將在 onActivityResult() 中得到反饋。同意的時候收到 RESULT_OK,拒絕的時候收到 RESULT_CANCELED。

第三步:查找設(shè)備

1、查找已配對設(shè)備

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());
    }
}

2、查找未知設(shè)備

mBlueAdapter.startDiscovery()

查找未知設(shè)備只需要調(diào)用 startDiscovery() 即可,這是一個異步操作,系統(tǒng)一般會在后臺進程進行一個 12 秒的查詢掃描。查找出來的信息我們需要在廣播中進行監(jiān)聽才可得知。

private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (BluetoothDevice.ACTION_FOUND.equals(action)) {
            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
            mUnpaireList.add(device);
            mUnpaireAdapter.notifyDataSetChanged();
        }
    }
};
//注冊廣播
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mBtReceiver, filter);
//同時別忘了銷毀時注銷廣播

第四步:配對連接

在這里我們往往需要一臺設(shè)備做服務(wù)器端一臺做客戶端,實際上就是 app 開啟了一個服務(wù)器線程讓藍牙的 socket 可以連接。連接完成后再使用 I/O Stream 進行數(shù)據(jù)交互。

1、服務(wù)器線程

我們需要用 listenUsingInsecureRfcommWithServiceRecord(String,UUID) 獲取 BluetoothServerSocket。

Q2:listenUsingRfcommWithServiceRecord()listenUsingInsecureRfcommWithServiceRecord() 有什么區(qū)別?
A2:從名字來看似乎是安全不安全的區(qū)別,但是實際上我并沒有找到相關(guān)資料佐證。也有文章描述客戶端的 socket 創(chuàng)建createRfcommSocketToServiceRecord 是安卓2.3系統(tǒng)及以下用的,新的安卓要用 createInsecureRfcommSocketToServiceRecord,所以對應(yīng)著服務(wù)器端也用Insercure吧。

服務(wù)器監(jiān)聽中,由于 accept() 方法是阻塞的,所以需要子線程中處理。

private class AcceptThread extends Thread {
    private final BluetoothServerSocket mmServerSocket;

    public AcceptThread() {
        BluetoothServerSocket tmp = null;
        try {
            tmp = mBluetoothAdapter.listenUsingInsecureRfcommWithServiceRecord(NAME, MY_UUID);
        } catch (IOException e) { }
        mmServerSocket = tmp;
    }

    public void run() {
        BluetoothSocket socket = mmServerSocket.accept();
        mmServerSocket.clost();
        mInputStream = socket.getInputStrem();
        mOutputStream = socket.getOutputStream();
        byte[] buffer = new byte[1024];  
        int bytes;
        while (true) {
            try{
                //讀取buffer信息打印出來
                bytes = mInputStream.read(buffer);
                String s = new String(buffer, 0, bytes);
                sendHandlerMsg(s);
            } carch(IOException e){
                break;
            }
        }
    }
}

2、客戶端連接

客戶端連接和服務(wù)端連接相似。當(dāng)然首先你要獲取到要配對的設(shè)備 BluetoothDevice,然后獲取 BluetoothSocket ,使用 mSocket.connect() 連接即可。他們的邏輯基本相同,在官方文檔中也有相關(guān)的描述。

在這里因為實際上我的需求是使用手機連接一個硬件設(shè)備,所以我選擇封裝了一個藍牙工具類,把藍牙開啟連接等客戶端相關(guān)操作封裝到 BluetoothManager 中。其中 ConnectThread 和 ReadThread 抽成兩個Runnable 放在線程池中處理。當(dāng) socket 連接成功后獲取到 IO 流來進行讀寫操作。讀操作因為屬于阻塞操作放在子線程。代碼這里就不貼了,文末有此 demo 的地址。有興趣的也可以自己去實現(xiàn)一下。

第五步:其他

剩下的就是布局和交互邏輯的實現(xiàn),這里就不在一一闡述了。

總結(jié)

藍牙的相關(guān)操作感覺和 Socket 非常地相似,都是進行端對端綁定,然后進行數(shù)據(jù)傳輸。所以同理也應(yīng)該會存在類似 Socket 的各種問題,比如說丟包,斷開連接需要心跳檢測,重連機制等等。這個demo只是對API進行了一定程度的整合,還存有不少的問題。

github 地址

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

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

  • 最近項目使用藍牙,之前并沒有接觸,還是發(fā)現(xiàn)了很多坑,查閱了很多資料,說的迷迷糊糊,今天特查看官方文檔。 說下遇到的...
    King9527閱讀 1,924評論 0 1
  • Android平臺支持藍牙網(wǎng)絡(luò)協(xié)議棧,實現(xiàn)藍牙設(shè)備之間數(shù)據(jù)的無線傳輸。本文檔描述了怎樣利用android平臺提供的...
    Camming閱讀 3,485評論 0 3
  • 藍牙 注:本文翻譯自https://developer.android.com/guide/topics/conn...
    RxCode閱讀 9,018評論 11 99
  • 前言 最近在做Android藍牙這部分內(nèi)容,所以查閱了很多相關(guān)資料,在此總結(jié)一下。 基本概念 Bluetooth是...
    貓疏閱讀 15,167評論 7 113
  • Android 平臺包含藍牙網(wǎng)絡(luò)堆棧支持,憑借此項支持,設(shè)備能以無線方式與其他藍牙設(shè)備交換數(shù)據(jù)。應(yīng)用框架提供了通過...
    虎三呀閱讀 887評論 0 1

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