Android 帶你從 0 實現基本的 BLE 開發

純手寫實現極簡方式 BLE 開發,並適度封裝。實現掃描、連接、發送、接收等功能

下面帶大家從 0 實現基本的 BLE 開發。文末提供 git 完整源碼。

權限

進行藍牙相關操作,需要使用到藍牙權限,在AndroidManifest.xml清單文件中添加相應權限

<uses-feature
    android:name="android.hardware.bluetooth_le"
    android:required="true" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

在 android6.0 以後,藍牙BLE還需要需要獲得位置權限

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

這個位置權限,需要動態申請

/**
     * 請求位置權限
     *
     * @param activity 上下文
     * @return boolean
     */
    public static boolean isGrantLocationPermission(Activity activity) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && activity.checkSelfPermission(
                Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            activity.requestPermissions(new String[]{
                    Manifest.permission.ACCESS_FINE_LOCATION,
                    Manifest.permission.ACCESS_COARSE_LOCATION,
            }, GROUP_LOCATION);

            return false;
        }
        return true;
    }

初始化藍牙 Adapter

final BluetoothManager bluetoothManager =
 (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
    if (bluetoothManager != null){
   	 mBluetoothAdapter = bluetoothManager.getAdapter();
    }

判斷藍牙是否可用或者是否開啓,如果藍牙關閉,那麼開啓藍牙

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

掃描

掃描過程中,需要對掃描結果進行回調,在 onLeScan()方法中對掃描的結果進行相關處理

private BluetoothDevice mDevice;
final BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
        Log.d("haha", "onLeScan:  " + device.getName() + " : " + rssi);
        String name = device.getName();
        if (name != null) {
            deviceName.setText(name);
            if (name.equals("test_ble")) {// 或者比較 mac 地址
                mDevice = device;
                mBluetoothAdapter.stopLeScan(mLeScanCallback);
            }
        }
    }
};

由於藍牙掃描耗時耗電,所以在進行掃描的時候,注意自定義一個合適的掃描時間,在實際的開發和項目應用過程中,自己選擇合適的時間。定義好藍牙掃描回調,開始掃描藍牙,掃描到想要的藍牙,就可以停止掃描

    private void scanLeDevice(final boolean enable) {
    if (enable) {
        // Stops scanning after a pre-defined scan period.
        // 預先定義停止藍牙掃描的時間(因爲藍牙掃描需要消耗較多的電量)

        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mScanning = false;
                mBluetoothAdapter.stopLeScan(mLeScanCallback);
            }
        }, 30000);
        mScanning = true;
        // 定義一個回調接口供掃描結束處理
        mBluetoothAdapter.startLeScan(mLeScanCallback);
    } else {
        mScanning = false;
        mBluetoothAdapter.stopLeScan(mLeScanCallback);
    }
}

通過獲取的 mBluetoothAdapter 調用 startLeScan() 傳入 mLeScanCallback 參數,即可進行藍牙掃描。

BluetoothGattCallback 實現

GATT 是用於發送和接收的通用規範, BLE 之間的文件數據傳輸基於 GATT,因此在進行連接之前,需要進行Gatt接口回調。

  private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                if (connectChangedListener != null) {
                    connectChangedListener.onConnected();
                }
                mConnectionState = STATE_CONNECTED;
                sendBroadcast(connectSuccess, "連接成功");
                mBluetoothGatt.discoverServices();
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED 
                       || status != BluetoothGatt.GATT_SUCCESS) {
                if (connectChangedListener != null) {
                    connectChangedListener.onDisconnected();
                    sendBroadcast(connectFail, "連接失敗");
                }
                mConnectionState = STATE_DISCONNECTED;
                // 重置
                gatt.close();
                close();
                // 重連操作 todo
                connect(MAC);
            }
            Log.d(TAG, "連接狀態:" + mConnectionState);
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                Log.d(TAG, "onServicesDiscovered: " + "發現服務 : status = " + status);
                //成功
                isServiceConnected = true;
                if (mBluetoothGatt != null) {
                    BluetoothGattService gattService = mBluetoothGatt.getService(serviceUuid);
                    BluetoothGattCharacteristic characteristic = gattService
                        .getCharacteristic(notifyUuid);
                    boolean b = mBluetoothGatt.setCharacteristicNotification(characteristic, true);
                    if (b) {
                        List<BluetoothGattDescriptor> descriptors = characteristic.getDescriptors();
                        for (BluetoothGattDescriptor descriptor : descriptors) {
                            boolean b1 = descriptor
                                .setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                            if (b1) {
                                mBluetoothGatt.writeDescriptor(descriptor);
                                Log.d(TAG, "描述 UUID :" + descriptor.getUuid().toString());
                            }
                        }
                        Log.d(TAG, "startRead: " + "監聽接收數據開始");
                    }
                }
            } else {
                Log.w(TAG, "onServicesDiscovered : 發現服務異常 status = " + status);
            }
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, 
                                            BluetoothGattCharacteristic characteristic) {
            Log.d(TAG, "onCharacteristicChanged: 接收數據成功,value =" + HexUtil
                  .bytesToHexString(characteristic.getValue()));
            // parseData(characteristic);
            sendBroadcast(notifyAction, HexUtil.bytesToHexString(characteristic.getValue()));
        }

        @Override
        public void onDescriptorWrite(BluetoothGatt gatt, 
                                      BluetoothGattDescriptor descriptor, int status) {
            super.onDescriptorWrite(gatt, descriptor, status);
            Log.d(TAG, "onDescriptorWrite: " + "設置成功");
        }
        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, 
                                          BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicWrite(gatt, characteristic, status);
            Log.d(TAG, "onCharacteristicWrite: " + "發送數據成功,value = " + HexUtil
                  .bytesToHexString(characteristic.getValue()));
            sendBroadcast(writeSuccessAction, HexUtil.bytesToHexString(characteristic.getValue()));
        }
    };

通過status對當前連接進行判斷,當status != BluetoothGatt.GATT_SUCCESS時,可以進行Gatt的重置操作,嘗試重連。當newState == BluetoothProfile.STATE_CONNECTED時,此時連接成功。

連接

    /**
     * Connects to the GATT server hosted on the Bluetooth LE device.
     *
     * @param address The device address of the destination device.
     */
    public void connect(final String address) {
        if (mBluetoothAdapter == null || address == null) {
            Log.w(TAG, " --------- BluetoothAdapter not initialized or unspecified address. --------- ");
            return;
        }

        final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
        if (device == null) {
            Log.w(TAG, " --------- Device not found.  Unable to connect. --------- ");
            return;
        }
        mBluetoothGatt = device.connectGatt(context, true, mGattCallback);
        Log.d(TAG, " --------- Trying to create a new connection. --------- ");
        mConnectionState = STATE_CONNECTING;
    }

發現服務

在連接成功後,可以通過 Gatt 進行 discoverServices()

if (newState == BluetoothProfile.STATE_CONNECTED) {
     if (connectChangedListener != null) {
         connectChangedListener.onConnected();
      }
      mConnectionState = STATE_CONNECTED;
      sendBroadcast(connectSuccess, "連接成功");
      mBluetoothGatt.discoverServices();
}

在 mGattCallback 回調添加 Servicest 的相關回調

  //發現服務回調。
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        Log.d("haha", "onServicesDiscovered: " + "發現服務 : " + status);
        if (status == BluetoothGatt.GATT_SUCCESS) {
                 //成功
            }
        }

讀寫開關

當返回的 status == BluetoothGatt.GATT_SUCCESS 時,進行讀寫以及通知相關的操作, 調用 writeDescriptor(),注意設置 setValue 爲 ENABLE_NOTIFICATION_VALUE,否則可能後續讀取不到數據。


        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                Log.d(TAG, "onServicesDiscovered: " + "發現服務 : status = " + status);
                //成功
                isServiceConnected = true;
                if (mBluetoothGatt != null) {
                    BluetoothGattService gattService = mBluetoothGatt.getService(serviceUuid);
                    BluetoothGattCharacteristic characteristic = gattService.getCharacteristic(notifyUuid);
                    boolean b = mBluetoothGatt.setCharacteristicNotification(characteristic, true);
                    if (b) {
                        List<BluetoothGattDescriptor> descriptors = characteristic.getDescriptors();
                        for (BluetoothGattDescriptor descriptor : descriptors) {
                            boolean b1 = descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                            if (b1) {
                                mBluetoothGatt.writeDescriptor(descriptor);
                                Log.d(TAG, "描述 UUID :" + descriptor.getUuid().toString());
                            }
                        }
                        Log.d(TAG, "startRead: " + "監聽接收數據開始");
                    }
                }
            } else {
                Log.w(TAG, "onServicesDiscovered : 發現服務異常 status = " + status);
            }
        }

設置成功,會在 onDescriptorWrite 方法進行回調,注意 服務 UUID, 特徵值UUID,通知 UUID,可以詢問公司固件端的開發人員,和開發人員配合修改。

     @Override
    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status{
        super.onDescriptorWrite(gatt, descriptor, status);
        Log.d(TAG, "onDescriptorWrite: " + "設置成功");
    }

發送數據


    /**
     * 發送數據
     *
     * @param value         指令
     * @param writeCallback 發送回調
     */
    public void writeBuffer(String value, OnWriteCallback writeCallback) {
        if ((mBluetoothGatt != null) && (isServiceConnected)) {
            BluetoothGattService gattService = mBluetoothGatt.getService(serviceUuid);
            BluetoothGattCharacteristic characteristic = gattService.getCharacteristic(writeUuid);
            characteristic.setValue(HexUtil.hexStringToBytes(value));
            characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
            boolean b = mBluetoothGatt.writeCharacteristic(characteristic);
            if (b) {
                if (writeCallback != null) {
                    writeCallback.onSuccess();
                }
            } else {
                sendBroadcast(writeFailedAction, "發送失敗");
            }
        }
    }

讀取數據

讀取數據在 onCharacteristicChanged 方法中,注意進制間的轉換。


        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, 
                                            BluetoothGattCharacteristic characteristic) {
            Log.d(TAG, "onCharacteristicChanged: 接收數據成功,value =" + HexUtil
                  .bytesToHexString(characteristic.getValue()));
            // parseData(characteristic);
            sendBroadcast(notifyAction, HexUtil.bytesToHexString(characteristic.getValue()));
        }

斷開操作

 /**
     * After using a given BLE device, the app must call this method to ensure resources are
     * released properly.
     */
    public void close() {
        if (mBluetoothGatt == null) {
            return;
        }
        mBluetoothGatt.disconnect();
        mBluetoothGatt.close();
        mBluetoothGatt = null;
    }

注意事項

  1. BLE 實現android 4.3 以後 google 才提供了支持,所以Ble項目只可運行在 API 18 以上的手機;Android 6.0 以上,Ble 服務還需要定位權限,並且需要動態申請,否則使用不了。不使用時,還應注意對藍牙進行關閉或斷開操作,由於 Ble 連接屬於獨佔操作,有設備連接上了,其它設備是無法進行任何操作的。
  2. 一定要進行讀寫開關操作,注意 descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE),否則可能讀取不到數據。

遇到的問題

Android BLE 藍牙onCharacteristicChanged 方法不調用
參考裏面的終結回答。本文用的就是這種方式。

源碼

SimpleBle

參考 & 感謝

  1. 一步一步實現Android低功耗藍牙(BLE)基本開發
  2. Android BLE低功耗藍牙開發極簡系列(一)之掃描與連接
  3. Android BLE低功耗藍牙開發極簡系列(二)之讀寫操作
發佈了331 篇原創文章 · 獲贊 168 · 訪問量 95萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章