Android-藍芽聊天demo

petterchx發表於2021-09-09

Android 中將藍芽分為傳統藍芽低功耗藍芽(Bluetooth low energy)兩種。後者的優勢在於快速搜尋,快速連線,超低功耗保持連線和資料傳輸,同時低功耗帶來的缺點是資料傳輸速率低,所以多用在可穿戴式裝置。

在這裡我們主要介紹使用傳統藍芽來實現一個聊天的資料傳輸 demo。以下內容基本都是基於官方文件的二次闡述,以及一些疑惑的查詢到的解答,最後在 demo 裡面有對藍芽的相關操作進行了封裝。先貼個圖看看效果吧:


圖片描述

藍芽.png

基礎知識

BluetoothAdapter: 本地藍芽介面卡,我們在發現裝置,配對的時候都得用上它。

BluetoothDevice: 遠端藍芽裝置,就是代表著你可以連線的一個裝置,裡面儲存名字,MAC地址等資訊。

BluetoothSocket 和 BluetoothServerSocket: 藍芽套接字,和 TCP 的 Socket 相似。一臺裝置開啟一個 ServerSocket 並監聽,另一臺裝置開啟 Socket 進行連線,以此實現一個端對端的連線和資料傳輸。

UUID: 唯一識別符。它被用於唯一標識應用的藍芽服務(不是表示藍芽裝置)。

Q1:為什麼網上的大多數例子都是使用 00001101-0000-1000-8000-00805F9B34FB 這個UUID?
A1:這是因為一個藍芽裝置裡面可以提供諸多服務,如A2DP(藍芽音訊傳輸)、HEADFREE(擴音)、SPP(串列埠通訊) 等等。而上面的字串碼就是 SPP 的 UUID,基本藍芽板上預設就是這個值,我們可以透過UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")來將字串轉成 UUID。
在連線藍芽串列埠板我們往往就會使用上面的UUID,但是如果 Android 端對端的話,建議自己自己設定 UUID,這樣別人的 UUID 就連不上了。

實現一個藍芽聊天demo

要實現一個藍芽聊天demo,首先我們有兩臺有藍芽功能的裝置,這裡我用了兩臺手機。按照流程一般來說要開啟藍芽-搜尋裝置-配對裝置-連線-通訊。如此就能實現一個基本的藍芽通訊。

第一步:許可權

在 Android 中沒有許可權寸步難行。要使用藍芽,還需要宣告相應的許可權。

<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 是基本的許可權,用於你的藍芽連線,資料傳輸等。

BLUETOOTH_ADMIN 一般應只用於發現本地藍芽裝置。

除非該應用是將要應使用者請求修改藍芽設定的“超級管理員”,否則不應使用此許可權所授予的其他能力。

另:如果要使用 BLUETOOTH_ADMIN 許可權,則還必須擁有 BLUETOOTH 許可權。

此外會發現我這裡比官方文件還多了個 ACCESS_COARSE_LOCATION,這是因為我在實測過程中,我的測試機Android 8.0 系統中,藍芽掃描沒有掃描出資訊,但是系統是有的。在網上一番尋找之後發現在 Android 6.0 之後還需要一個模糊定位的許可權,否則掃描功能無效。

:為給使用者提供更嚴格的資料保護,從此版本(6.0)開始,對於使用 WLAN API 和 Bluetooth API 的應用,Android 移除了對裝置本地硬體識別符號的程式設計訪問權。WifiInfo.getMacAddress()方法和 BluetoothAdapter.getAddress() 方法現在會返回常量值 02:00:00:00:00:00
現在,要透過藍芽和 WLAN 掃描訪問附近外部裝置的硬體識別符號,您的應用必須擁有 ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION 許可權。

關於動態許可權申請在此不作累述,小夥伴們可以自己去實現。

第二步:啟動藍芽

1、獲取 BluetoothAdapter

BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();if (mBluetoothAdapter == null) {    //TODO 裝置不支援藍芽,阻斷使用者操作}

2、啟動藍芽

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

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

第三步:查詢裝置

1、查詢已配對裝置

Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();// If there are paired devicesif (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、查詢未知裝置

mBlueAdapter.startDiscovery()

查詢未知裝置只需要呼叫 startDiscovery() 即可,這是一個非同步操作,系統一般會在後臺程式進行一個 12 秒的查詢掃描。查詢出來的資訊我們需要在廣播中進行監聽才可得知。

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);//同時別忘了銷燬時登出廣播

第四步:配對連線

在這裡我們往往需要一臺裝置做伺服器端一臺做客戶端,實際上就是 app 開啟了一個伺服器執行緒讓藍芽的 socket 可以連線。連線完成後再使用 I/O Stream 進行資料互動。

1、伺服器執行緒

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

Q2:listenUsingRfcommWithServiceRecord()listenUsingInsecureRfcommWithServiceRecord() 有什麼區別?
A2:從名字來看似乎是安全不安全的區別,但是實際上我並沒有找到相關資料佐證。也有文章描述客戶端的 socket 建立createRfcommSocketToServiceRecord 是安卓2.3系統及以下用的,新的安卓要用 createInsecureRfcommSocketToServiceRecord,所以對應著伺服器端也用Insercure吧。

伺服器監聽中,由於 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、客戶端連線

客戶端連線和服務端連線相似。當然首先你要獲取到要配對的裝置 BluetoothDevice,然後獲取 BluetoothSocket ,使用 mSocket.connect() 連線即可。他們的邏輯基本相同,在官方文件中也有相關的描述。

在這裡因為實際上我的需求是使用手機連線一個硬體裝置,所以我選擇封裝了一個藍芽工具類,把藍芽開啟連線等客戶端相關操作封裝到 BluetoothManager 中。其中 ConnectThread 和 ReadThread 抽成兩個Runnable 放線上程池中處理。當 socket 連線成功後獲取到 IO 流來進行讀寫操作。讀操作因為屬於阻塞操作放在子執行緒。程式碼這裡就不貼了,文末有此 demo 的地址。有興趣的也可以自己去實現一下。

第五步:其他

剩下的就是佈局和互動邏輯的實現,這裡就不在一一闡述了。

總結

藍芽的相關操作感覺和 Socket 非常地相似,都是進行端對端繫結,然後進行資料傳輸。所以同理也應該會存在類似 Socket 的各種問題,比如說丟包,斷開連線需要心跳檢測,重連機制等等。這個demo只是對API進行了一定程度的整合,還存有不少的問題。



作者:白帽子耗子
連結:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4606/viewspace-2821607/,如需轉載,請註明出處,否則將追究法律責任。

相關文章