Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android BLE藍牙通訊學習

Android BLE藍牙通訊學習

編輯:關於Android編程

在app應用的開發過程中,一般和藍牙接觸的不多,但是隨著智能穿戴設備的發展,穿戴設備和手機關聯的app越來越多,之前也是沒怎麼接觸過這一塊的東西,正好最近需要做一個和藍牙有關的app,所以研究學習下,把應用的東西總結一下。項目源碼已經上傳github。

介紹

BLE是Bluetooth Low Energy的縮寫,又叫藍牙4.0,區別於藍牙3.0和之前的技術。BLE前身是NOKIA開發的Wibree技術,主要用於實現移動智能終端與周邊配件之間的持續連接,是功耗極低的短距離無線通信技術,並且有效傳輸距離被提升到了100米以上,同時只需要一顆紐扣電池就可以工作數年之久。

BLE是在藍牙技術的基礎上發展起來的,既同於藍牙,又區別於傳統藍牙。BLE設備分單模和雙模兩種,雙模簡稱BR,商標為Bluetooth Smart Ready,單模簡稱BLE或者LE,商標為Bluetooth Smart。Android是在4.3後才支持BLE,這可以解釋不是所有藍牙手機都支持BLE,而且支持BLE的藍牙手機一般是雙模的。

BLE和普通藍牙之間是有區別的:

優點:

功耗更低

連接速度更快

缺點:

每次發送的數據比較小

Android和BLE

一些概念

ATT

ATT(Attribute Protocol)協議是基礎協議,ATT針對BLE設備進行了特別的優化,它的基礎是屬性,使用一個UUID來定義屬性的類型。

GATT

GATT(Generic Attribute Profile)是所有BLE頂層協議的基礎,它定義了怎麼把一堆ATT屬性分組成為有意義的服務。

services

服務,基礎是UUID的值為0x2800的屬性。所有跟在這個屬性後面的屬性都屬於這個屬性定義的服務,直到另一個0x2800屬性出現。一個BLE設備可以有多個服務。

characteristics

特征,每一個服務都可以包含有多個特征,特征存儲了有用的值以及權限。其中藍牙模塊和app進行通訊主要是通過它來進行。

descriptor

特征的描述,又叫描述符,在一個特征中會有多個描述符。GATT協議已經定義了大多數的標准描述符,這其中有一個特別重要的描述符是:client characteristic configuration其UUID是0x2902,具有一個16bit的可讀寫值。被用來定義通知和暗示,通過設置可以能夠讓設備發送通知,並且被主機端接收到。

Notification

通知,BLE模塊向空中發送消息,可以被主機端的藍牙模塊接收到。包含在characteristics中,但是需要權限打開。

BLE透傳

透傳模式下,所有的串口數據都被看做用戶數據,模塊會將這些數據通過藍牙發送給主機,即是藍牙模塊和手機或者其他控制設備之間的通訊(其實還有另外一種命令模式,但是透傳模式更快速和簡單。),透傳模式即是通過services和characteristics進行。一般廠家或者工程師會給出相應的說明,例如下圖:

BLE透傳說明<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjxzdHJvbmc+vcfJq7rN1rDU8Dwvc3Ryb25nPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9wPg0KPHA+QW5kcm9pZMnosbi6zUJMRcnosbi9u7ul09DBvdfpvcfJq6O6PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvcD4NCjxwPtbQ0MTJ6LG4KENlbnRyYWwpus3N4s6nyeixuChQZXJpcGhlcnkpoaPN4s6nyeixuMrHyv2+3czhuanV36Os1tDR68rHyv2+3cq508MvtKbA7dXfoaPSu7j21tDR68nosbi/ydLUzazKscGsvdO24Lj2zeLOp8nosbijrLWrysfSu7j2zeLOp8nosbjNrMqx1rvE3MGsvdPSu7j21tDR68nosbihozwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvY29kZT48L2NvZGU+PC9jb2RlPjwvcD4NCjxoMiBpZD0="android-使用">Android 使用

准備權限


准備BLE

獲取BluetoothAdapter:
        BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        bluetoothAdapter = bluetoothManager.getAdapter();
        //打開藍牙 方法一
        if (!bluetoothAdapter.isEnabled()) {
            bluetoothAdapter.enable();
        }
        //方法二 推薦
        Intent enabler = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(enabler, REQUEST_ENABLE);
判斷是否支持BLE:
    private boolean checkBluetooth() {
        if (!getPackageManager().hasSystemFeature(
                PackageManager.FEATURE_BLUETOOTH_LE)) {
            return false;
        }
        return true;
    }
開始掃描設備:
bluetoothAdapter.startLeScan(mLeScanCallback);

BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
        @Override
        public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    /*
                    顯示了一個列表,點擊進入具體設備頁面 BLEDeviceTestActivity               
                    並且將設備的名稱和地址傳遞過去
                     intent.putExtra(DeviceControlActivity.EXTRAS_DEVICE_NAME, device.getName());
                     intent.putExtra(DeviceControlActivity.EXTRAS_DEVICE_ADDRESS, device.getAddress());
                    */
                    adapter.add("name : " + device.getName() + "\n address : " + device.getAddress());
                    bluetoothDevices.add(device);
                }
            });
        }
    };

連接設備

在BLEDeviceTestActivity中獲取設備,並且連接設備:

//根據地址獲取設備
BluetoothDevice device = bluetoothAdapter.getRemoteDevice(addressStr);
//獲取鏈接 這個時候需要實現BluetoothGattCallback
BluetoothGatt bluetoothGatt = device.connectGatt(this, false, bluetoothGattCallback);

有關device.connectGatt(this, false, bluetoothGattCallback);方法,可以看到它是通過設備建立一個GATT鏈接,任何有關GATT鏈接的操作都將觸發回調。而BluetoothGattCallback會異步處理這些回調結果。

    /**
     * Connect to GATT Server hosted by this device. Caller acts as GATT client.
     * The callback is used to deliver results to Caller, such as connection status as well
     * as any further GATT client operations.
     * The method returns a BluetoothGatt instance. You can use BluetoothGatt to conduct
     * GATT client operations.
     * @param callback GATT callback handler that will receive asynchronous callbacks.
     * @param autoConnect Whether to directly connect to the remote device (false)
     *                    or to automatically connect as soon as the remote
     *                    device becomes available (true).
     * @throws IllegalArgumentException if callback is null
     */
    public BluetoothGatt connectGatt(Context context, boolean autoConnect,
                                     BluetoothGattCallback callback) {
        return (connectGatt(context, autoConnect,callback, TRANSPORT_AUTO));
    }

所以實現bluetoothGattCallback:

    BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
        /**
         * 返回鏈接狀態
         * @param gatt
         * @param status 鏈接或者斷開連接是否成功 {@link BluetoothGatt#GATT_SUCCESS}
         * @param newState 返回一個新的狀態{@link BluetoothProfile#STATE_DISCONNECTED} or
         *                                  {@link BluetoothProfile#STATE_CONNECTED}
         */
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);
        }

        /**
         *  獲取到鏈接設備的GATT服務時的回調
         * @param gatt
         * @param status 成功返回{@link BluetoothGatt#GATT_SUCCESS}
         */
        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);

        }

        /**
         * 讀特征的時候的回調
         * @param gatt
         * @param characteristic 從相關設備上面讀取到的特征值
         * @param status
         */
        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicRead(gatt, characteristic, status);
        }

        /**
         * 指定特征寫入操作的回調結果
         * @param gatt
         * @param characteristic
         * @param status
         */
        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicWrite(gatt, characteristic, status);
        }

        /**
         * 設備發出通知時會調用到該接口
         * @param gatt
         * @param characteristic
         */
        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicChanged(gatt, characteristic);
        }

        /**
         * 指定描述符的讀操作的回調
         * @param gatt
         * @param descriptor
         * @param status
         */
        @Override
        public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            super.onDescriptorRead(gatt, descriptor, status);
        }

        /**
         * 指定描述符的寫操作
         * @param gatt
         * @param descriptor
         * @param status
         */
        @Override
        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            super.onDescriptorWrite(gatt, descriptor, status);
        }

        /**
         * 當一個寫入事物完成時的回調
         * @param gatt
         * @param status
         */
        @Override
        public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
            super.onReliableWriteCompleted(gatt, status);

        }

        @Override
        public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
            super.onReadRemoteRssi(gatt, rssi, status);
        }

        @Override
        public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
            super.onMtuChanged(gatt, mtu, status);
        }
    };

可以看到BluetoothGattCallback有很多方法,根據名字不難明白其意思,接著根據藍牙的使用過程來一個一個的分析就能清晰明了其中功能。

因為我們前面進行了鏈接,所以首先觸發的回調是onConnectionStateChange()方法:


         /**
         * 返回鏈接狀態
         * @param gatt
         * @param status 鏈接或者斷開連接是否成功 {@link BluetoothGatt#GATT_SUCCESS}即表示操作是否成功
         * @param newState 返回一個新的狀態{@link BluetoothProfile#STATE_DISCONNECTED} or  即表示當前的狀態
         *                                  {@link BluetoothProfile#STATE_CONNECTED}

         因此使用newState參數來做判斷
         */
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                //連接成功  因為是異步調用的 所以刷新UI的操作要放在主線程中,當然也可以使用hanlder  Eventbus等 隨便
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        connectTv.setText("連接成功");
                    }
                });
                //鏈接成功
                gatt.discoverServices();
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                //斷開連接
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        connectTv.setText("連接斷開");
                    }
                });
            }
        }

在上面的代碼中發現當連接成功後調用了gatt.discoverServices();方法,這個方法是用來發現遠程設備提供的服務,以及它們包含的特征特性和描述符等等。

因為根據前面的介紹可以知道,要想app和藍牙模塊進行通訊,需要通過這些服務和特征等。這個方法會觸發回調onServicesDiscovered():

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);
            if (BluetoothGatt.GATT_SUCCESS == status) {
                gatt.getServices();

                /*
一個GATT服務表現為一個 BluetoothGattService 對象,我們需要通過適當的UUID從 BluetoothGatt 實例中獲得;
一個GATT特征表示為一個 BluetoothGattCharacteristic  對象,我們可以通過適當的UUID從 BluetoothGattService 中得到;
相當於一個數據類型,它包括一個value和0~n個value的描述(BluetoothGattDescriptor)
一個GATT描述符表現為一個 BluetoothGattDescriptor 對象,我們可以通過適當的UUID從BluetoothGattCharacteristic  對象中獲得:
描述符,對Characteristic的描述,包括范圍、計量單位等
                 */

                gattCharacteristicList.clear();
                String uuid = null;
                ArrayList> gattServiceData = new ArrayList>();
                ArrayList>> gattCharacteristicData = new ArrayList>>();

                //獲取服務
                for (BluetoothGattService gattService : gatt.getServices()) {
                    HashMap currentServiceData = new HashMap();
                    uuid = gattService.getUuid().toString();
                    currentServiceData.put("name",
                            SampleGattAttributes.lookup(uuid, "未知服務"));
                    currentServiceData.put("uuid", uuid);
                    gattServiceData.add(currentServiceData);

                    ArrayList> gattCharacteristicGroupData = new ArrayList>();
                    List gattCharacteristics = gattService
                            .getCharacteristics();
                    ArrayList charas = new ArrayList();

                    // 獲取每個服務中包含的特征
                    for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
                        charas.add(gattCharacteristic);
                        HashMap currentCharaData = new HashMap();
                        uuid = gattCharacteristic.getUuid().toString();
                        currentCharaData.put("name",
                                SampleGattAttributes.lookup(uuid, "未知特征"));
                        currentCharaData.put("uuid", uuid);

                        //當某個特征的描述符UUID為0000ff02-0000-1000-8000-00805f9b34fb時 使能BLE透傳模塊通知功能
                        if (uuid.equals("0000ff02-0000-1000-8000-00805f9b34fb")) {
                            setCharacteristicNotification(gattCharacteristic, true);
                        }
                        gattCharacteristicGroupData.add(currentCharaData);
                    }   
                    gattCharacteristicList.add(charas);
                    gattCharacteristicData.add(gattCharacteristicGroupData);
                }

                // 用一個可折疊的列表來展示 這些服務和 特征 
                final SimpleExpandableListAdapter gattServiceAdapter = new SimpleExpandableListAdapter(
                        BLEDeviceTestActivity.this, gattServiceData,
                        android.R.layout.simple_expandable_list_item_2, new String[]{
                        "name", "uuid"}, new int[]{android.R.id.text1,
                        android.R.id.text2}, gattCharacteristicData,
                        android.R.layout.simple_expandable_list_item_2, new String[]{
                        "name", "uuid"}, new int[]{android.R.id.text1,
                        android.R.id.text2});
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        listView.setAdapter(gattServiceAdapter);
                    }
                });
            }
        }

上面一大堆代碼,就是把獲取到的服務和特征用一個列表展示出來並不是關鍵,關鍵代碼在:

 if (uuid.equals("0000ff02-0000-1000-8000-00805f9b34fb")) {
                            setCharacteristicNotification(gattCharacteristic, true);
                        }

setCharacteristicNotification()方法如下是啟用一個指定特征的通知權限。這裡被小小的坑了一下,本來以為bluetoothGatt.setCharacteristicNotification(characteristic, enabled);

設置true就夠了的,但是調試的時候發現不能夠接收到通知,查閱了資料才知道還需要給描述符設置通知權限啟用才行,但是具體哪個描述符就不知道了,最後還是根據藍牙模塊廠家發來的demo反編譯,查看了其源碼才知道相應的UUID。不過通過上面的介紹發現此UUID正是0x2902開頭的。由此我猜測應該這個描述符應該是GATT協議當中已經給定義好的一個描述符。


    public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enabled) {
        //僅僅有這一句是不夠的
        bluetoothGatt.setCharacteristicNotification(characteristic, enabled);
        //需要為指定特征的特定的描述符設置啟用才行
        BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID
                .fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));
        if (descriptor != null) {
            System.out.println("write descriptor");
            descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
            bluetoothGatt.writeDescriptor(descriptor);
        }
    }

當設置了通知以後,如果BLE設備通過通知的方式發送數據的話,app端接到通知會觸發onCharacteristicChanged()方法,此方法就可以用來接收BLE模塊通過廣播形式給返回的:

        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicChanged(gatt, characteristic);
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(BLEDeviceTestActivity.this, "接收到數據", Toast.LENGTH_SHORT).show();
                }
            });
            //以字節碼數組的形式接收到數據
            final byte[] data = characteristic.getValue();
            if (data != null && data.length > 0) {
                final StringBuilder stringBuilder = new StringBuilder(
                        data.length);
                StringBuffer test = new StringBuffer();
                for (byte byteChar : data) {
                    test.append(byteChar);
                    stringBuilder.append(String.format("%02X ", byteChar));//以兩位16進制輸出 不足的補0
                }
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        //數據展示
                        dataTv.setText(new String(data) + "\n"
                                + stringBuilder.toString());
                    }
                });
            }
        }

根據前面的介紹知道藍牙的透傳是通過characteristics進行的,所以當然還有讀和寫的操作,在進行操作之前需要先對服務的UUID進行判斷

可以根據characteristic.getUuid()來得到UUID,或者在知道UUID的情況下,主動獲取對應的特征進行操作。

進行讀操作如下,觸發回調函數onCharacteristicRead():

        //對相應的特征進行讀操作
        bluetoothGatt.readCharacteristic(characteristic);

        //讀取成功觸發回調函數 
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicRead(gatt, characteristic, status);

            //讀取到的數據存在characteristic當中,可以通過characteristic.getValue();函數取出。然後再進行解析操作。
            if (BluetoothGatt.GATT_SUCCESS == status) {
                final byte[] data = characteristic.getValue();
                ...
                //和通知一樣也是通過字節碼的形式傳遞數據 這裡省略不寫
            }

        }

同樣既然有了讀,也可以進行寫的操作,寫操作如下,會觸發回調函數onCharacteristicWrite():

        //這裡寫入需要是字節碼數組的形式來進行
        characteristic.setValue(byte[] value);
        bluetoothGatt.writeCharacteristic(characteristic);

        //這裡沒什麼好說的,就是判斷寫入是否成功
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicWrite(gatt, characteristic, status);
            if (BluetoothGatt.GATT_SUCCESS == status) {
                Log.d("BLEDeviceTestActivity", "寫入成功");
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(BLEDeviceTestActivity.this, "寫入成功", Toast.LENGTH_SHORT).show();
                    }
                });
            }

如果寫入成功,並且透傳模塊有應答,一般的應答方式是通過通知的方式進行的。所以一般在項目中在鏈接成功以後就要開啟通知的權限,省的後面寫入的時候忘記。至此,手機和BLE模塊之間的通訊基本完成,其他的寫入描述符這些回調過程大同小異。

當然,這只是簡單的完成了通訊,真正使用的時候由於數據包大小的限制還需要分包傳輸,接受等等等等很多需要完善的地方。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved