Android BLE 蓝牙低功耗

under Android  tag     Published on May 19th , 2021 at 05:11 pm

官方文档
Android BLE 蓝牙开发入门
BLE 设备:例如近程传感器、心率监测仪和健身设备


BLE 蓝牙低功耗开发

1、申请权限

AndroidManifest.xml 中静态添加权限

    <!--  允许程序连接配对过的蓝牙设备  -->
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <!--  允许程序进行发现和配对新的蓝牙设备  -->
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <!--  允许程序通过WiFi或移动基站的方式获取用户错略的经纬度信息  -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <!--  允许程序通过GPS芯片接收卫星的定位信息  -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-feature
        android:name="android.hardware.bluetooth_le"
        android:required="true" />

java 文件中动态添加。在所有操作进行之前添加

    private static int REQUEST_CODE_PERMISSIONS = 77;
    // 声明权限变量
    private String[] permissions = {
            Manifest.permission.BLUETOOTH,
            Manifest.permission.BLUETOOTH_ADMIN,
            Manifest.permission.ACCESS_COARSE_LOCATION,
            Manifest.permission.ACCESS_FINE_LOCATION
    };
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        if (ContextCompat.checkSelfPermission(this, permissions[0]) != PackageManager.PERMISSION_GRANTED ||
                ContextCompat.checkSelfPermission(this, permissions[1]) != PackageManager.PERMISSION_GRANTED ||
                ContextCompat.checkSelfPermission(this, permissions[2]) != PackageManager.PERMISSION_GRANTED ||
                ContextCompat.checkSelfPermission(this, permissions[3]) != PackageManager.PERMISSION_GRANTED) {
            //如果应用之前请求过此权限但用户拒绝了请求,此方法将返回 true。
            ActivityCompat.requestPermissions(this, permissions, REQUEST_CODE_PERMISSIONS);
        }
        // ... ...
    }
    
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_CODE_PERMISSIONS) {
            for (int i = 0; i < grantResults.length; i++) {
                if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
                    // 用户拒绝授权
                    Toast.makeText(this, "授权后才能正常使用", Toast.LENGTH_SHORT).show();
                }
            }
        }
    }

2、打开蓝牙

    private void openBluetooth() {
        BluetoothManager manager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        if (manager != null) {
            BluetoothAdapter adapter = manager.getAdapter();
            if (adapter != null) {
                if (!adapter.isEnabled()) {
                    adapter.enable();
                }
            }
        }
    }

3、扫描 BLE 蓝牙

我没有把扫描到的名称为 null 的设备渲染到列表上

BleDevAdapter bleDevAdapter; // 自定义 Adapter
List<String> list = new ArrayList<>(); // adapter 里用到的
List<BluetoothDevice> devices = new ArrayList<>(); // 连接时用到的

private boolean isScaning = false; // 是否在扫描
private ScanCallback mScanCallback = null; // 扫描回调
private final Handler mHandler = new Handler(); // 设置30s后停止扫描
    public void Scan() {
        if (list != null) {
            list.clear();
            bleDevAdapter.notifyDataSetChanged();
        }
        scanBle();
    }

    // 扫描BLE蓝牙(不会扫描经典蓝牙)
    private void scanBle() {
        logTv("---- 扫描中 ----");
        isScaning = true;
        // 点击扫描后不能再点,扫描结束才能点
        scan.setEnabled(false);
        BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        final BluetoothLeScanner bluetoothLeScanner;
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
            devices.clear();
            mScanCallback = new ScanCallback() {
                @Override
                public void onScanResult(int callbackType, ScanResult result) {
                    BluetoothDevice device = result.getDevice();
                    if (!devices.contains(device) && device.getName() != null) {
                        devices.add(device);
                        list.add(String.format("设备名称:%s\n蓝牙地址:%s\nrssi:%s", device.getName(), device.getAddress(), result.getRssi()));
                        bleDevAdapter.notifyDataSetChanged();
                        Log.d(TAG, "onScanResult: " + result);
                    }
                }
            };

            lv.setAdapter(bleDevAdapter);
            bluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();
            // Android5.0新增的扫描API,扫描返回的结果更友好,比如BLE广播数据以前是byte[] scanRecord,而新API帮我们解析成ScanRecord类
            bluetoothLeScanner.startScan(mScanCallback);

            mHandler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    if (isScaning) {
                        bluetoothLeScanner.stopScan(mScanCallback); //停止扫描
                        logTv("---- 扫描结束 ----");
                        scan.setEnabled(true);
                        isScaning = false;
                    }
                }
            }, 30000);
        }
    }

4、连接(准备工作)

最好是用 nRF Connect 软件确认 Notify 和 Read 的服务和特征的 UUID,不然很可能拿到的 UUID 读不到值

声明

private BluetoothGatt mBluetoothGatt;
private boolean isConnected = false;

//服务、特征、描述值
private UUID write_UUID_service;
private UUID write_UUID_chara;
private UUID read_UUID_service;
private UUID read_UUID_chara;
private UUID read_UUID_D;
private UUID notify_UUID_service;
private UUID notify_UUID_chara;
private UUID indicate_UUID_service;
private UUID indicate_UUID_chara;

打印数据,并显示到 TextView 上

    private void logTv(final String msg) {
        if (isDestroyed()) return;
        runOnUiThread(new TimerTask() {
            @Override
            public void run() {
                Log.e(TAG, "run: logTV -- " + msg);
                tvLog.append("\n" + msg + "\n");
            }
        });
    }

建立新连接之前必须释放旧连接资源,在下列情况下使用

连接设备后扫描需要 进行该操作后 再扫描
连接设备时
连接设备失败时
onDestroy() 时

    private void closeConn() {
        if (mBluetoothGatt != null) {
            mBluetoothGatt.disconnect();
            mBluetoothGatt.close();
        }
        mBluetoothGatt = null;
    }

5、连接

点击 ListView item 连接。扫描中途点击 item,先停止扫描再连接设备。

lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        // 正在扫描中点击 (item)设备 停止扫描并连接设备
        if (isScaning) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                bluetoothLeScanner.stopScan(mScanCallback); //停止扫描
                logTv("---- 停止扫描 ----");
                scan.setEnabled(true);
                isScaning = false;
            }
        }
        closeConn();
        mBluetoothGatt = devices.get(position).connectGatt(MainActivity.this, false, gattCallback);
    }
});

BluetoothGattCallback 回调

onServicesDiscovered() 方法里 descriptor 设置的 ENABLE_NOTIFICATION_VALUE: 客户端主动获取服务端数据。数据每次变化后会回调 onCharacteristicChanged(),从这个方法里获取改变的值。

    private BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            logTv("---- 正在连接 ----");
            logTv(String.format("设备名:%s,蓝牙地址:%s", gatt.getDevice().getName(), gatt.getDevice().getAddress()));
            if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
                isConnected = true;
                gatt.discoverServices();
            } else {
                isConnected = false;
                closeConn();
            }
            logTv(String.format(status == 0 ? (newState == 2 ? "---- 与[%s]连接成功 ----" : "---- 与[%s]连接断开 ----")
                    : ("---- 与[%s]连接出错,错误码: " + status + " ----"), gatt.getDevice().getName()));
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                initServiceAndChara();
                // 订阅通知
                BluetoothGattCharacteristic characteristic = mBluetoothGatt.getService(notify_UUID_service)
                        .getCharacteristic(notify_UUID_chara)
                boolean result = mBluetoothGatt.setCharacteristicNotification(characteristic, true);
                if(result){
                    for(BluetoothGattDescriptor descriptor : characteristic.getDescriptors()){
                        // ENABLE_NOTIFICATION_VALUE 客户端主动获取服务端数据,设置成功后会调用 onDescriptorWrite()
                        descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                        mBluetoothGatt.writeDescriptor(descriptor);
                    }
                }
            }
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            logTv(String.format("~~ onCharacteristicRead-value: [%s] ~~", toBinaryString(characteristic.getValue())));
        }

        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            logTv("~ onCharacteristicWrite ~");
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            logTv("~ onCharacteristicChanged ~");
        }

        @Override
        public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            logTv("~ onDescriptorRead ~");
        }

        @Override
        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            logTv(String.format("~ onDescriptorWrite-value: [%s] ~", toBinaryString(descriptor.getValue())));
        }
    };

获取初始化服务和特征值并打印

    private void initServiceAndChara() {
        List<BluetoothGattService> bluetoothGattServices = mBluetoothGatt.getServices();
        for (BluetoothGattService bluetoothGattService : bluetoothGattServices) {
            List<BluetoothGattCharacteristic> characteristics = bluetoothGattService.getCharacteristics();
            for (BluetoothGattCharacteristic characteristic : characteristics) {
                int charaProp = characteristic.getProperties();
                if ((charaProp & BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
                    read_UUID_chara = characteristic.getUuid();
                    read_UUID_service = bluetoothGattService.getUuid();
                    logTv("\n---- !!!PROPERTY_READ!!! ----");
                    logTv(String.format("read_chara = %s, read_service = %s \n", read_UUID_chara, read_UUID_service));
                    for (BluetoothGattDescriptor descriptor : characteristic.getDescriptors()) {
                        read_UUID_D = descriptor.getUuid();
                        logTv(String.format("descriptor = %s", descriptor.getUuid()));
                    }
                }
                if ((charaProp & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) {
                    write_UUID_chara = characteristic.getUuid();
                    write_UUID_service = bluetoothGattService.getUuid();
                    logTv("\n---- PROPERTY_WRITE ----");
                    logTv(String.format("write_chara = %s, write_service = %s \n", write_UUID_chara, write_UUID_chara));
                }
                if ((charaProp & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) > 0) {
                    write_UUID_chara = characteristic.getUuid();
                    write_UUID_service = bluetoothGattService.getUuid();
                    logTv("\n---- PROPERTY_WRITE_NO_RESPONSE ----");
                    logTv(String.format("write_chara = %s, write_service = %s \n", write_UUID_chara, write_UUID_service));
                }
                if ((charaProp & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
                    notify_UUID_chara = characteristic.getUuid();
                    notify_UUID_service = bluetoothGattService.getUuid();
                    logTv("\n---- PROPERTY_NOTIFY ----");
                    logTv(String.format("notify_chara = %s, notify_service = %s \n", notify_UUID_chara, notify_UUID_service));
                }
                if ((charaProp & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0) {
                    indicate_UUID_chara = characteristic.getUuid();
                    indicate_UUID_service = bluetoothGattService.getUuid();
                    logTv("\n---- PROPERTY_INDICATE ----");
                    logTv(String.format("indicate_chara = %s, indicate_service = %s \n", indicate_UUID_chara, indicate_UUID_service));
                }
            }
        }
    }

byte[] -> String (16进制数),原文章

    public static String toHexString(byte[] byteArray) {
        if (byteArray == null || byteArray.length < 1)
            throw new IllegalArgumentException("this byteArray must not be null or empty");

        final StringBuilder hexString = new StringBuilder();
        for (int i = 0; i < byteArray.length; i++) {
            if ((byteArray[i] & 0xff) < 0x10)//0~F前面不零
                hexString.append("0");
            hexString.append(Integer.toHexString(0xFF & byteArray[i]) + " ");
        }
        return hexString.toString().toLowerCase();
    }

本文由 surface 创作,采用 知识共享署名4.0 国际许可协议进行许可,转载前请务必署名
  文章最后更新时间为:September 29th , 2021 at 04:37 pm
分享到:Twitter  Weibo  Facebook