欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android蓝牙BLE入门

程序员文章站 2022-03-16 17:18:28
...

Manifest文件权限配置说明

使用蓝牙,扫描蓝牙和设置蓝牙设置需要用到

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

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

Android 5.0以上需要额外配置位置权限

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

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

注意ACCESS_COARSE_LOCATION权限在6.0上需要动态获取,否则无法搜索出设备

<uses-feature android:name="android.bluetooth.le" android:required="true"/> 

这个权限是为了区分不支持BLE的android设备无法运行该APP,required=true,只能在支持BLE的android设备上安装运行该APP,required=false,所有设备均可以运行
这个权限贴出来只作了解,具体使用还是在代码中判断设备是否支持BLE作逻辑结束比较好

if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {

   Log.e("yzh","不支持BLE功能")

   finish();

}

蓝牙的交互流程

1.开启手机蓝牙模块搜索蓝牙信号

2.根绝device的uuid,name或者address找到需要连接设备

3.进行通道连接,遍历所有的通道中的服务,再遍历每个服务中的特征值

4.找到需要用的特征值进行读写操作

初始化蓝牙管理对象

BluetoothManager bluetoothManager=(BluetoothManager)getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter bluetoothAdapter=bluetoothManager.getAdapter();

判断蓝牙功能是否开启,未开始默认开始

if(bluetoothAdapter==null||!bluetoothAdapter.isEnabled()){
        bluetoothAdapter.enable();
    }

蓝牙搜索

bluetoothAdapter.getBluetoothLeScanner().startScan(callback);

private ScanCallback callback =new ScanCallback() {
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    public void onScanResult(int callbackType, ScanResult result) {
        if(!list.contains(result.getDevice())){
            Log.e("yzh","onScanResult--"+result.getDevice().getUuids()+"--"+result.getDevice().getAddress()+"--"+result.getDevice().getName());
            list.add(result.getDevice());
        }
    }

    @Override
    public void onBatchScanResults(List<ScanResult> results) {
        Log.e("yzh","onBatchscanresult");
    }

    @Override
    public void onScanFailed(int errorCode) {
        Log.e("yzh","onScanFailed"+errorCode);
    }
};

startScan(List<ScanFilter> filters, ScanSettings settings, ScanCallback callback),上面搜索方法还可以带两个参数,筛选搜索结果

 public List<ScanFilter> getScanFilters(String[] uuids){
    List<ScanFilter> filters=new ArrayList<>();
    if(uuids!=null&&uuids.length>0){
        for (String s:uuids){
            ScanFilter.Builder  builder1=new ScanFilter.Builder();
            builder1.setServiceUuid(ParcelUuid.fromString(s));
            // builder1.setDeviceName("QN-Scale");
            ScanFilter filter=builder1.build();
            filters.add(filter);
        }
    }
    return filters;
}
public ScanSettings getScanSettings(int times){
    ScanSettings.Builder builder = new ScanSettings.Builder();
    builder.setReportDelay(times);
    builder.setScanMode(ScanSettings.SCAN_MODE_BALANCED);
    // builder.setMatchMode(ScanSettings.MATCH_NUM_ONE_ADVERTISEMENT);
    ScanSettings settings=builder.build();
    return settings;
}

这里发现以前的搜索方法被废弃了,查阅是5.0以上新增api之后。网上介绍蓝牙ble的文章相对较少,很多以前的代码都会使用这2个方法,在这里贴出来说明一下

bluetooth.startLeScan;
bluetooth.stopLeScan;

连接蓝牙

//关闭蓝牙扫描 防止阻塞
bluetoothAdapter.getBluetoothLeScanner().stopScan(callback);
//通过蓝牙地址获取蓝牙设备
device=bluetoothAdapter.getRemoteDevice("F4:51:41:55:04:FC");
//设备建立通道连接
bluetoothGatt=device.connectGatt(BleConnectActivity.this,false,bleGattCallback);

//连接回调
private BluetoothGattCallback bleGattCallback =new BluetoothGattCallback() {
    @Override
    public void onPhyUpdate(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
        super.onPhyUpdate(gatt, txPhy, rxPhy, status);
    }

    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
        super.onConnectionStateChange(gatt, status, newState);
        Log.e("yzh", "onConnectionStateChange"+"--status="+status);

        if (newState == BluetoothProfile.STATE_CONNECTED) {
            Log.e("yzh", "连接成功");
            //连接成功之后扫描服务
            gatt.discoverServices();
        } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
            if(status==0){
                gatt.connect();
            }else{
                //尝试重连的代码
                gatt.disconnect();
                gatt.close();
                device.connectGatt(BleConnectActivity.this,false,bleGattCallback);

            }
            Log.e("yzh", "断开");
        }

    }

    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
        super.onServicesDiscovered(gatt, status);
        Log.e("yzh", "onServicesDiscovered");
        String uuid = null;
        if (status == BluetoothGatt.GATT_SUCCESS) {
            //读取服务和每个服务下对应的特征值
            List<BluetoothGattService> gattServices = gatt.getServices();
            for (BluetoothGattService gattService : gattServices) {
                uuid = gattService.getUuid().toString();
                Log.e("yzh", "serviceUUid--" + uuid);
                List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics();
                for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
                    uuid = gattCharacteristic.getUuid().toString();
                    Log.e("yzh", "characteristic--" + uuid+"--"+gattCharacteristic.getProperties());
                }
            }
        }
    }

    @Override
    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
        super.onCharacteristicRead(gatt, characteristic, status);
        Log.e("yzh", "onCharacteristicRead");
        if (status == BluetoothGatt.GATT_SUCCESS) {
            //读取特征值携带的信息  下面用了很多方式来解读返回的byte数组 实际要与硬件方沟通对接
            String uuid = null;
            uuid = characteristic.getUuid().toString();
            Log.e("yzh","--uid--"+uuid);
            try{
            Log.e("yzh","--value--"+bytesToHexString(characteristic.getValue()));
            Log.e("yzh","GBKvalue"+ new String(characteristic.getValue(),"GBK").toString());
                Log.e("yzh","UTF-8value"+ new String(characteristic.getValue(),"UTF-8").toString());
                Log.e("yzh","unicode-8value"+ new String(characteristic.getValue(),"unicode").toString());
                Log.e("yzh","ISO8859-1-8value"+ new String(characteristic.getValue(),"ISO8859-1").toString());
                Log.e("yzh","base64value"+  android.util.Base64.encodeToString(characteristic.getValue(), android.util.Base64.DEFAULT));
            }catch (Exception e){
            }

      
                final byte[] data = characteristic.getValue();


                for(int i=0;i<data.length;i++){
                    Log.e("yzh", "detailValue--"+data[i] + "--");
                }
        }
    }

    @Override
    public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
        super.onCharacteristicChanged(gatt, characteristic);
    }
};

注意:这里有几点存在疑问或者说没有找到最优解

1.connectGatt()方法第二个参数的解读是在蓝牙断开,会进行自动重连,但是在验证过程中并没有重连的现象

2.onConnectionStateChange方法里写的重连方法,在验证时一直无法重连,返回状态值133,gatt.disconnect,gatt.close是网上找的一些建议,但并未生效,找到一个有效方式是先开启搜索再连接,网上也有人是这样做的,最好用不同的硬件和不同的手机系统去验证这个问题。

蓝牙数据读取

bluetoothGatt.readCharacteristic(characteristic);

//在回调方法里面接受特征值里面的数据
onCharacteristicRead

蓝牙数据写入

characteristic.setValue()//传入需要写入的数据
bluetoothGatt.writeCharacteristic(characteristic);//往设备写入数据
//开启 Android 端接收通知的开关
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);
//CLIENT_CHARACTERISTIC_CONFIG = "00002902-0000-1000-8000-00805f9b34fb"
//characteristic的Descriptor属性写入开启通知的数据,保证硬件数据变化时,主动往手机发送数据
 BluetoothGattDescriptor descriptor = characteristic.getDescriptor( UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG)); descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); mBluetoothGatt.writeDescriptor(descriptor); 

监听写入成功

 @Override
    public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
        super.onCharacteristicWrite(gatt, characteristic, status);
    }

监听写入数据之后数据的返回通知

@Override
    public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
        super.onCharacteristicChanged(gatt, characteristic);
    }