Android蓝牙BLE基本用法

  • Android应用权限
  • 蓝牙相关对象获取
  • 检查设备是否支持BLE
  • 开启设备的蓝牙功能
  • 使设备的蓝牙可被发现
  • 开启BLE服务端
    • 新建一个GATT服务
    • 新建一个GATT特征值
    • 新建一个特征值描述(可选)
    • 特征值加入特征值描述(可选)
    • 服务加入特征值
    • 开启GATT服务端
    • GATT服务端加入刚才创建的GATT Service
    • 开始发送BLE广播
  • BLE客户端
    • 扫描设备和服务
    • 扫描回调接口
    • 客户端连接
    • 读操作
    • 写操作

Android应用权限

蓝牙相关对象获取

    private BluetoothAdapter bluetoothAdapter;    private BluetoothManager bluetoothManager;    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        // bluetoothAdapter        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();                // bluetoothManager BLE需要这个类        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {            bluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);        }    }

检查设备是否支持BLE

public void checkBLESupport() {        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {            Toast.makeText(this, "不支持BLE", Toast.LENGTH_LONG).show();            Log.d("kaikai", "不支持BLE");        } else {            Toast.makeText(this, "支持BLE", Toast.LENGTH_LONG).show();            Log.d("kaikai", "支持BLE");        }    }

开启设备的蓝牙功能

public void onEnableBlueTooth() {        if (!bluetoothAdapter.isEnabled()) {            Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);            startActivityForResult(enableIntent, 0);        } else {            Toast.makeText(this, "蓝牙已开启", Toast.LENGTH_LONG).show();        }    }

使设备的蓝牙可被发现

public void onDiscoverable() {        Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);        startActivity(discoverableIntent);        }

开启BLE服务端

新建一个GATT服务

BluetoothGattService service = new BluetoothGattService(YOUR_SERVICE_UUID, BluetoothGattService.SERVICE_TYPE_PRIMARY);

新建一个GATT特征值

BluetoothGattCharacteristic characteristic = new BluetoothGattCharacteristic(YOUR_CHARACTERISTIC_UUID,// 注意,如果要使特征值可写,PROPERTY需设置PROPERTY_WRITE(服务端需要返回响应)或PROPERTY_WRITE_NO_RESPONSE(服务端无需返回响应),BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_NOTIFY | BluetoothGattCharacteristic.PROPERTY_WRITE,BluetoothGattCharacteristic.PERMISSION_READ | BluetoothGattCharacteristic.PERMISSION_WRITE);

注意,BluetoothGattCharacteristic构造函数的第二个参数,如果要使特征值可被客户端进行反写,需要有BluetoothGattCharacteristic.PROPERTY_WRITE或PROPERTY_WRITE_NO_RESPONSE标志位,其中,PROPERTY_WRITE标志位要求服务端在接收写请求时,返回响应,而PROPERTY_WRITE_NO_RESPONSE则不需要响应,但数据可能会被截断。

新建一个特征值描述(可选)

BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(YOUR_DESCRIPTER_UUID, BluetoothGattDescriptor.PERMISSION_READ | BluetoothGattDescriptor.PERMISSION_WRITE);

特征值加入特征值描述(可选)

characteristic.addDescriptor(descriptor);

服务加入特征值

service.addCharacteristic(characteristic);

开启GATT服务端

        // 使用一个类成员保存BluetoothGattServer引用        private BluetoothGattServer bluetoothGattServer;        // onCreate方法中或某个按钮触发的回调函数中        bluetoothGattServer = bluetoothManager.openGattServer(this, new BluetoothGattServerCallback() {                @Override                public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {                    // 连接状态改变                    if (newState == BluetoothProfile.STATE_CONNECTED) {                        Log.d("kaikai", "BLE服务端:BLE Connected!");                    } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {                        Log.d("kaikai", "BLE服务端:BLE Disconnected!");                    }                }                @Override                public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattCharacteristic characteristic) {                    Log.d("kaikai", "BLE服务端:接收到特征值读请求!" + " requestId=" + requestId + " offset=" + offset);                    BluetoothGattCharacteristic gattCharacteristic = bluetoothGattServer.getService(TIME_SERVICE_UUID).getCharacteristic(TIME_CHARACTERISTIC_UUID);                    if (characteristic == gattCharacteristic) {                        // 证明characteristic与通过gattServer得到的是同一个对象                        Log.d("kaikai", "BLE服务端:same characteristic!");                    }                    byte[] value = characteristic.getValue();                    if (value == null) {                        value = "init responseData".getBytes();                        characteristic.setValue(value);                    }                    // offset != 0,如果读的偏移不为0,证明是分段读,返回特征值的对应便宜的截断数据就行(每次返回的长度都至末尾)                    if (offset != 0) {                        int newLen = value.length - offset;                        byte[] retVal = new byte[value.length - offset];                        System.arraycopy(value, offset, retVal, 0, newLen);                        value = retVal;                    }                    // 请求读特征                    if (TIME_CHARACTERISTIC_UUID.equals(characteristic.getUuid())) {                        bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);                    }                }                @Override                public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId, BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {                    // super.onCharacteristicWriteRequest(device, requestId, characteristic, preparedWrite, responseNeeded, offset, value);                    Log.d("kaikai", "BLE Server端 onCharacteristicWriteRequest"                            + " requestId:" + requestId + " offset:" + offset + " prepareWrite:" + preparedWrite                            + " responseNeeded:" + responseNeeded + " value:" + new String(value));                    // 不是分段写                    if (!preparedWrite) {                        characteristic.setValue(value);                    }                    // 分段写                    else {                        if (offset == 0) {                            bleDataByteArray = new ByteArrayOutputStream();                        }                        try {                            bleDataByteArray.write(value);                        } catch (IOException e) {                            throw new RuntimeException(e);                        }                        writingObj = characteristic;                    }                    // 尝试用更改后的数据作为响应,发现客户端的回调的状态值为133,不为0                    byte[] valueWrong = new byte[value.length];                    System.arraycopy(value, 0, valueWrong, 0, value.length);                    valueWrong[0]++;                    if (responseNeeded) {                        // 注意,如果写特征值需要响应(特征值的属性是PROPERTY_WRITE不是PROPERTY_WRITE_NO_RESPONSE),必需发送value作为响应数据                        bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);                        // 此处为使用错误的数据做响应//                      bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, valueWrong);                    }                }                @Override                public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {                    super.onExecuteWrite(device, requestId, execute);                    // 当分段写时,才会回调此方法                    Log.d("kaikai", "onExecuteWrite called! requestId=" + requestId + " execute=" + execute);                    if (execute) {                        byte[] data = bleDataByteArray.toByteArray();                        String dataStr = new String(data);                        Log.d("kaikai", "onExecuteWrite 拼接数据:" + dataStr);//                        BluetoothGattCharacteristic characteristic1 = bluetoothGattServer.getService(TIME_SERVICE_UUID).getCharacteristic(TIME_CHARACTERISTIC_UUID);//                        characteristic1.setValue(data);                        if (writingObj != null) {                            if (writingObj instanceof BluetoothGattCharacteristic) {                                ((BluetoothGattCharacteristic) writingObj).setValue(data);                            } else if (writingObj instanceof BluetoothGattDescriptor) {                                ((BluetoothGattDescriptor) writingObj).setValue(data);                            } else {                                throw new RuntimeException("writingObj类型不明");                            }                        } else {                            throw new RuntimeException("writingObj为空");                        }                        // 注意,当写数据过长时,会自动分片,多次调用完onCharacteristicWriteRequest后,便会调用此方法,要在此方法中发送响应,execute参数指示是否执行成功,可按照此参数发送响应的状态                        bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, null);                    } else {                        Log.d("kaikai", "BLE SERVER: onExecuteWrite发送失败响应");                        // 发送一个失败响应                        bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, 0, null);                    }                }                @Override                public void onServiceAdded(int status, BluetoothGattService service) {                    super.onServiceAdded(status, service);                    Log.d("kaikai", "onServiceAdded");                }                @Override                public void onMtuChanged(BluetoothDevice device, int mtu) {                    super.onMtuChanged(device, mtu);                    Log.d("kaikai", "BLE SERVER:onMTUChanged! mtu=" + mtu);//                    bluetoothGattServer.sendResponse(device,0,0,0,null);                }                @Override                public void onDescriptorReadRequest(BluetoothDevice device, int requestId, int offset, BluetoothGattDescriptor descriptor) {                    super.onDescriptorReadRequest(device, requestId, offset, descriptor);                    Log.d("kaikai", "BLE服务端:onDescriptorReadRequest  requestId=" + requestId + "  offset=" + offset);                    byte[] value = descriptor.getValue();                    if (value == null) {                        value = "descriptor1 value".getBytes();                    }                    // 与characteristic的处理一样                    if (offset > 0) {                        int newLen = value.length - offset;                        byte[] newValue = new byte[newLen];                        System.arraycopy(value, offset, newValue, 0, newLen);                        value = newValue;                    }                    bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);                }                @Override                public void onDescriptorWriteRequest(BluetoothDevice device, int requestId, BluetoothGattDescriptor descriptor, boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {                    super.onDescriptorWriteRequest(device, requestId, descriptor, preparedWrite, responseNeeded, offset, value);                    Log.d("kaikai", "BLE服务端:onDescriptorWriteRequest  requestId=" + requestId + "  preparedWrite=" + preparedWrite + "  responseNeeded=" + responseNeeded                            + "  value=" + new String(value));                    if (!preparedWrite) {                        descriptor.setValue(value);                    } else {                        if (offset == 0) {                            bleDataByteArray = new ByteArrayOutputStream();                        }                        try {                            bleDataByteArray.write(value);                        } catch (IOException e) {                            throw new RuntimeException(e);                        }                        writingObj = descriptor;                    }                    if (responseNeeded) {                        bluetoothGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset, value);                    }                }            });

有几点需要注意:

  1. Characteristic和Descriptor的读和写,在交互数据过长(超出MTU)的情况下,都会产生分段(会回调多次相关的方法,并传入对应的offset值)。写操作主要判断prepareWrite参数,如果为true,则是分段写,并且在最后会回调onExecuteWrite方法。读操作主要用offset判断,只需返回Characteristic或Descriptor的value的在offset处的截断即可,详看代码解释。
  2. 可用一个临时成员保存正在分段写的Characteristic或Descriptor引用,上述代码使用了一个Object writingObj。
  3. 写操作的responseNeeded取决于Characteristic的构造函数的第二个参数。在responseNeeded==true时,服务端才需返回响应,并且返回的数据需要和传入的参数value相同。

GATT服务端加入刚才创建的GATT Service

bluetoothGattServer.addService(service);

开始发送BLE广播

    /**     * 通知服务开启(发广告)     */    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)    private void startAdvertising() {        BluetoothLeAdvertiser advertiser = bluetoothAdapter.getBluetoothLeAdvertiser();        if (advertiser == null) {            Log.d("kaikai", "advertiser为空");            return;        }        AdvertiseSettings settings = new AdvertiseSettings.Builder().setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_BALANCED)                .setConnectable(true)                .setTimeout(0)                .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM)                .build();        AdvertiseData data = new AdvertiseData.Builder().setIncludeDeviceName(true)                .setIncludeTxPowerLevel(false)                .addServiceUuid(new ParcelUuid(TIME_SERVICE_UUID))                .build();        advertiser.startAdvertising(settings, data, new AdvertiseCallback() {            @Override            public void onStartSuccess(AdvertiseSettings settingsInEffect) {                Log.d("kaikai", "BLE Advertise started!");            }            @Override            public void onStartFailure(int errorCode) {                Log.d("kaikai", "BLE Advertise fail!errorCode=" + errorCode);            }        });    }

此处有几点需要注意:

  1. 如果设备名称过长,可能会导致广播失败
  2. 如果没有开启应用的定位权限,也可能会导致广播失败,请检查应用的相关权限有没开启

AdvertiseCallback回调接口用于判断是否成功开启BLE广播

至此,BLE服务端成功开启。

BLE客户端

扫描设备和服务

public void onBLEScan(View view) {        bluetoothAdapter.getBluetoothLeScanner().startScan(scanCallback);    }

扫描回调接口

        scanCallback = new ScanCallback() {            @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)            @Override            public void onScanResult(int callbackType, ScanResult result) {                super.onScanResult(callbackType, result);                BluetoothDevice device = result.getDevice();                Log.d("kaikai", "BLE发现设备:" + device.getName() + " " + device.getAddress());                deviceAdapter.add("设备名:" + device.getName() + "地址:" + device.getAddress());// 这里我添加了自己特定的处理逻辑,大家按回自己的逻辑进行处理                if (device.getName() != null && device.getName().equalsIgnoreCase("kk")) {                    // 搜索到设备名为 “kk”,即可停止搜索了                    Log.d("kaikai", "match!");                    bluetoothAdapter.getBluetoothLeScanner().stopScan(scanCallback);                    // 这里可接着写连接BluetoothGATT的逻辑,下面会介绍                }            }            @Override            public void onBatchScanResults(List results) {                super.onBatchScanResults(results);                Log.d("kaikai", "BatchScanResult" + results.toString());            }            @Override            public void onScanFailed(int errorCode) {                super.onScanFailed(errorCode);                Log.d("kaikai", "BLE Scan failed!");            }        };

客户端连接

当搜索到你想要的设备对象(BluetoothDevice)时,就可进行连接了,使用BluetoothGATT对象进行

 // 连接 blueToothGatt = device.connectGatt(MainActivity.this, false, new BluetoothGattCallback() {     @Override     public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {         super.onConnectionStateChange(gatt, status, newState);         if (newState == BluetoothProfile.STATE_CONNECTED) {             Log.d("kaikai", "BLE客户端:BLE连接成功");             blueToothGatt.discoverServices();             if (blueToothGatt == gatt) {                 Log.d("kaikai", "BLE客户端:same gatt object!");             }             Log.d("kaikai", "BLE客户端:开始搜索外围服务");         } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {             Log.d("kaikai", "BLE客户端:断开BLE连接");         }     }     @Override     public void onServicesDiscovered(BluetoothGatt gatt, int status) {         super.onServicesDiscovered(gatt, status);         if (status == BluetoothGatt.GATT_SUCCESS) {             Log.d("kaikai", "BLE客户端:成功搜索到服务");             List services = gatt.getServices();             BluetoothGattService service = gatt.getService(TIME_SERVICE_UUID);             if (service == null) {                 Log.d("kaikai", "service为空");                 return;             }             // 对应的特征             BluetoothGattCharacteristic characteristic = service.getCharacteristic(TIME_CHARACTERISTIC_UUID);             gatt.setCharacteristicNotification(characteristic, true);             boolean b;             // 更改特性描述             // 读一下特性//                                b = gatt.readCharacteristic(characteristic);//                                Log.d("kaikai", "b:" + b);             // 如果调用了readCharacteristic,则睡眠数秒后再调用readDescriptor也会返回false,应该是同一线程内不能与服务端交互多次//                                try {//                                    Thread.sleep(5000);//                                } catch (InterruptedException e) {//                                    e.printStackTrace();//                                }             // 注意,前面如果调用了readCharacteristic,这里就不能直接进行descriptor读取,会返回false。如果没有调用则可             BluetoothGattDescriptor descriptor = characteristic.getDescriptor(TIME_DESCRIPTER_UUID);             b = gatt.readDescriptor(descriptor);             Log.d("kaikai", "descriptor b:" + b);         }     }     @Override     public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {         super.onCharacteristicRead(gatt, characteristic, status);         if (status == BluetoothGatt.GATT_SUCCESS) {             Log.d("kaikai", "BLE客户端:成功读取到特性");             byte[] value = characteristic.getValue();             try {                 String val = new String(value, "utf8");                 Log.d("kaikai", val);             } catch (UnsupportedEncodingException e) {                 e.printStackTrace();             }             // 注意,descriptor的读取须要在完成characteristic读取后执行//                                BluetoothGattDescriptor descriptor = characteristic.getDescriptor(TIME_DESCRIPTER_UUID);//                                boolean b = gatt.readDescriptor(descriptor);//                                Log.d("kaikai", "b2:" + b);         }     }     @Override     public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {         super.onCharacteristicWrite(gatt, characteristic, status);         Log.d("kaikai", "BLE客户端:onCharacteristicWrite called!status=" + status);         if (status == BluetoothGatt.GATT_SUCCESS) {             Log.d("kaikai", "BLE客户端:写特征值成功");             byte[] value = characteristic.getValue();             Log.d("kaikai", "value:" + new String(value));         }     }     @Override     public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {         super.onCharacteristicChanged(gatt, characteristic);         Log.d("kaikai", "BLE客户端:onCharacteristicChanged");         byte[] newValue = characteristic.getValue();         try {             Log.d("kaikai", new String(newValue, "utf8"));         } catch (UnsupportedEncodingException e) {             e.printStackTrace();         }     }     @Override     public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {         super.onDescriptorRead(gatt, descriptor, status);         Log.d("kaikai", "BLE客户端:onDescriptorRead  status=" + status);         Log.d("kaikai", "BLE客户端:成功读取descriptor:" + new String(descriptor.getValue()));     }     @Override     public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {         super.onDescriptorWrite(gatt, descriptor, status);         Log.d("kaikai", "BLE客户端:onDescriptorWrite  status=" + status);     }     @Override     public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {         super.onMtuChanged(gatt, mtu, status);         Log.d("kaikai", "BLE客户端: onMTUChanged. mtu=" + mtu + " status=" + status);         if (status != BluetoothGatt.GATT_SUCCESS) {             Log.d("kaikai", "BLE客户端: onMTUChanged status不为0");         }     } });

客户端不需要处理分段问题。
onCharacteristicChanged方法可监听服务端推送的特征值改变请求

读操作

    /**     * BLE客户端读取特征值1     *     * @param view     */    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)    public void onBLEClientReadCharacteristic1(View view) {        BluetoothGattCharacteristic characteristic = blueToothGatt.getService(TIME_SERVICE_UUID).getCharacteristic(TIME_CHARACTERISTIC_UUID);        blueToothGatt.readCharacteristic(characteristic);    }   /**     * BLE客户端读取descriptor     *     * @param view     */    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)    public void onClientReadDescriptor(View view) {        BluetoothGattDescriptor descriptor = blueToothGatt.getService(TIME_SERVICE_UUID).getCharacteristic(TIME_CHARACTERISTIC_UUID).getDescriptor(TIME_DESCRIPTER_UUID);        boolean readDescriptorBool = blueToothGatt.readDescriptor(descriptor);        Log.d("kaikai", "readDescriptorBool:" + readDescriptorBool);    }

读取成功后,会回调BluetoothGattCallback的相关方法

写操作

    /**     * BLE客户端写特征值1     *     * @param view     */    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)    public void onBLEClientWriteCharacteristic1(View view) throws UnsupportedEncodingException {        BluetoothGattCharacteristic characteristic = blueToothGatt.getService(TIME_SERVICE_UUID).getCharacteristic(TIME_CHARACTERISTIC_UUID);        byte[] characteristicData = characteristic.getValue();        if (characteristicData != null) {            Log.d("kaikai", "BLE客户端写特征值前的特征值为:" + new String(characteristicData, "utf8"));        } else {            Log.d("kaikai", "characteristic value为空");        }        characteristic.setValue("client write charrrrrrrrrrrrrr 9999999994444".getBytes());        blueToothGatt.writeCharacteristic(characteristic);    }
    /**     * BLE客户端读写descriptor     *     * @param view     */    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)    public void onClientWriteDescriptor(View view) {        byte[] dataToWrite = "1111111111222222222233333333334444444444".getBytes();        BluetoothGattDescriptor descriptor = blueToothGatt.getService(TIME_SERVICE_UUID).getCharacteristic(TIME_CHARACTERISTIC_UUID)                .getDescriptor(TIME_DESCRIPTER_UUID);        descriptor.setValue(dataToWrite);        boolean result = blueToothGatt.writeDescriptor(descriptor);        Log.d("kaikai", "descriptorWriteBool:" + result);    }

写操作成功后,会回调BluetoothGattCallback的相关方法

更多相关文章

  1. 基于web的android图像处理示例(Win7+Apache+PHP+Matlab+Android)
  2. 『转』Android(安卓)推送方式
  3. Android艺术探索学习笔记:第2章 IPC机制
  4. Android(安卓)之 多线程和Socket套接字的使用介绍
  5. Android(安卓)IPC机制之 Android的各种IPC方式
  6. Android远程服务例程
  7. 桌面云的三种模式 VDI IDV VOI (笔记)
  8. Nginx系列教程(二)| 一文带你读懂Nginx的正向与反向代理
  9. RHEL 6 下 DHCP+TFTP+FTP+PXE+Kickstart 实现无人值守安装

随机推荐

  1. Linux下安装配置Android开发环境
  2. Android(安卓)Handler消息处理机制面试5
  3. 系统屋索引
  4. 对比onSaveInstanceState和onRestoreInst
  5. Android拨打电话权限总结
  6. 关于LinearLayout布局中,子控件平分宽度
  7. Android(安卓)P WMS初始化过程
  8. Android(安卓)继承SQLiteOpenHelper自定
  9. Android中判断网络连接的工具类
  10. android 动态布局