Android中的实现

1. 扫描广播包

  • 首先获取 BluetoothManagerBluetoothAdapter
  private BluetoothAdapter mBluetoothAdapter;  BluetoothManager bluetoothManager = (BluetoothManager)   context.getSystemService(Context.BLUETOOTH_SERVICE);  mBluetoothAdapter= bluetoothManager.getAdapter();
  • 开始扫描
    /**     * 开始扫描,扫描结果在 BluetoothAdapter.LeScanCallback 中返回     * @param deviceName 扫描的设备名     */    public void startScan(String deviceName) {        if (mBluetoothAdapter == null) {            // error            return;        }         BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();        this.mScanning = true;        List filters = new ArrayList<>();        ScanFilter filter = new ScanFilter.Builder()                .setDeviceName(deviceName)                .build();        filters.add(filter);        scanner.startScan(filters, new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build(), mScanCallback);    }

这里的扫描设置了过滤的参数,只返回设备名为传入参数的广播。如果要扫描特定的设备,也可以根据设备的Mac地址过滤。或者也可以用不过滤方式扫描,过滤逻辑可以在扫描结果中处理。

    private ScanCallback mScanCallback = new ScanCallback() {        @Override        public void onScanResult(int callbackType, ScanResult result) {            // 广播的信息,可以在result中获取            Log.e(TAG, "onScanResult: name: " + result.getDevice().getName() +                                ", address: " + result.getDevice().getAddress() +                                ", rssi: " + result.getRssi() + ", scanRecord: " + result.getScanRecord());        }    };

开启蓝牙扫描会增加手机功耗,Android官方也不建议一直进行扫描,因此无论有没有扫描到目标设备,都应该设置一个超时时间,避免长时间扫描。停止扫描指令如下:

    public void stopScan() {        BluetoothLeScanner scanner = mBluetoothAdapter.getBluetoothLeScanner();        scanner.stopScan(mScanCallback);    }

2. 建立蓝牙连接

如果发现需要进行蓝牙连接的设备,就可以发起建立蓝牙连接请求:

    /**     * 建立蓝牙连接     * @param address 设备的Mac地址     * @result 连接请求是否成功     */    public boolean connect(Context context, String address) {        if (mBluetoothAdapter == null || address == null) {            Log.w(TAG, "BluetoothAdapter not initialized or unspecified address.");            error();            return false;        }        Log.i(TAG, "connecting ");        // Previously connected device.  Try to reconnect.        if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress)                && mBluetoothGatt != null) {            Log.i(TAG, "尝试使用已有的GATT连接");            if (mBluetoothGatt.connect()) {                return true;            } else {                return false;            }        }        final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);        if (device == null) {            Log.i(TAG, "Device not found.  Unable to connect.");            return false;        }        // 连接的核心代码        mBluetoothGatt = device.connectGatt(context, false, mGattCallback);        Log.d(TAG, "Trying to create a new connection.");        return true;    }

连接是否成功可以在回调函数中获取:

private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {        @Override        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {            if (newState == BluetoothProfile.STATE_CONNECTED) {                mConnectionState = STATE_CONNECTED;                Log.i(TAG, "Connected to GATT server.");                // 连接成功                ...            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {                mConnectionState = STATE_DISCONNECTED;                // 断开连接                ...            } else {                // 其他错误情况            }        }    };

onConnectionStateChange方法在连接状态改变后被调用,status是之前状态,newState是改变后的状态。类BluetoothGattCallback 除了得到连接状态的变化,还可以得到其他信息。稍后会逐步介绍。

3. 发现服务

如果蓝牙连接成功,就可以通过GATT协议进行通讯。前面介绍过,蓝牙的信息是通过特征值(characteristics)和服务(services)的传输。因此首先得获取设备的服务:

    public void discoverServices() {        mBluetoothGatt.discoverServices();    }

发现服务的结果在BluetoothGattCallback的另一个方法中返回:

    private BleDeviceService  mBleDeviceService ;    private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {        @Override        public void onServicesDiscovered(BluetoothGatt gatt, int status) {            if (status == BluetoothGatt.GATT_SUCCESS) {                //获取服务成功                //可用gatt.getServices()获取Service,并用BleDeviceService缓存起来,供访问使用。                mBleDeviceService = new BleDeviceService(gatt.getServices());            } else {                //获取服务失败            }        }    };

其中BleDeviceService 用List缓存了相关的Service,并提供了用UUID查询的方法:

public class BleDeviceService {    private final List bluetoothGattServices;    public BleDeviceService(List bluetoothGattServices) {        this.bluetoothGattServices = bluetoothGattServices;    }    /**     * 获取全部 BluetoothGattService     */    public List getBluetoothGattServices() {        return bluetoothGattServices;    }    /**     * 获取特定 BluetoothGattService     * @param serviceUuid UUID service的标识     * @return BluetoothGattService 查找不到返回null     */    public BluetoothGattService getService(@NonNull final UUID serviceUuid){        for (BluetoothGattService bluetoothGattService : bluetoothGattServices) {            if (bluetoothGattService.getUuid().equals(serviceUuid)){                return bluetoothGattService;            }        }        return null;    }    /**     * 获取特定特征值     * @param characteristicUuid 特征值UUID     * @return BluetoothGattCharacteristic 特征值,查找不到返回null     */    public BluetoothGattCharacteristic getCharacteristic(@NonNull UUID serviceUuid,@NonNull UUID characteristicUuid) {        BluetoothGattService bluetoothGattService = getService(serviceUuid);        if (bluetoothGattService != null){            BluetoothGattCharacteristic characteristic = bluetoothGattService.getCharacteristic(characteristicUuid);            if (characteristic != null){                return characteristic;            }        }        return null;    }}

4. 读写特征值

有了Service的信息,就可以进行读写特征值了,读特征值是获取设备的信息,写特征值是,发送信息给设备。
读特征值:

/**     * 读特征值     *     * @param serviceUUID        服务 UUID     * @param characteristicUUID 特征值 UUID     */    public void readCharacteristic(String serviceUUID, String characteristicUUID) {        if (mBluetoothAdapter == null || mBluetoothGatt == null) {            Log.w(TAG, "BluetoothAdapter not initialized");            return;        }        boolean isError = false;        if (mBleDeviceService != null) {            BluetoothGattCharacteristic characteristic = mBleDeviceService.getCharacteristic(UUID.fromString(serviceUUID),                    UUID.fromString(characteristicUUID));            if (characteristic != null) {                int permission = characteristic.getProperties();                if ((permission & BleConstants.AT_BLE_PERMISSION_CHAR_READ)                        == BleConstants.AT_BLE_PERMISSION_CHAR_READ) {                    Log.i(TAG, "reading characteristic");                    mBluetoothGatt.readCharacteristic(characteristic);                } else {                    Log.i(TAG, "read permission denied");                    isError = true;                }            } else {                Log.i(TAG, "characteristic is null");                isError = true;            }        } else {            Log.i(TAG, "mBleDeviceService is null");            isError = true;        }        if (isError) {            // 处理错误        }    }

写特征值:

    private void write(String serviceUUID, String characteristicUUID, byte[] data) {        if (mBluetoothAdapter == null || mBluetoothGatt == null) {            //错误            return;        }        if (mBleDeviceService != null) {            BluetoothGattCharacteristic characteristic = mBleDeviceService.getCharacteristic(UUID.fromString(serviceUUID),                    UUID.fromString(characteristicUUID));            if (characteristic != null) {                int permission = (byte) characteristic.getProperties();                if ((permission & BleConstants.AT_BLE_PERMISSION_CHAR_WRITE)                        == BleConstants.AT_BLE_PERMISSION_CHAR_WRITE ||                        (permission & BleConstants.AT_BLE_PERMISSION_CHAR_SIGNED_WRITE)                                == BleConstants.AT_BLE_PERMISSION_CHAR_SIGNED_WRITE ||                        (permission & BleConstants.AT_BLE_PERMISSION_CHAR_WRITE_WITHOUT_RESPONSE)                                == BleConstants.AT_BLE_PERMISSION_CHAR_WRITE_WITHOUT_RESPONSE ||                        (permission & BleConstants.AT_BLE_PERMISSION_CHAR_RELIABLE_WRITE)                                == BleConstants.AT_BLE_PERMISSION_CHAR_RELIABLE_WRITE) {                 //可以设置特征值的类型,默认为有应答。                //characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);                       characteristic.setValue(data);                    mBluetoothGatt.writeCharacteristic(characteristic);                    Log.i(TAG, "writing characteristic done");                } else {                    Log.i(TAG, "writing permission denied");                    //error                }            } else {                Log.i(TAG, "null characteristic");               //error            }        }    }

这里注释的这一行(characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);)需要说明一下,如果不设置,默认的写特征值类型是有应答的。有无应答体现在底层通信上,不会影响到写特征值的回调函数的调用。目前发现区别是:对某些蓝牙芯片,无应答的通讯速率会略快,而且对调大的最大MTU(Maximum Transmission Unit,传输单元),也会减少出错的几率。
读写是否成功的结果同样也是在回调函数中获得:

private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {        @Override        public void onCharacteristicRead(BluetoothGatt gatt,                                         BluetoothGattCharacteristic characteristic,                                         int status) {            if (status == BluetoothGatt.GATT_SUCCESS) {                Log.i(TAG, "read characteristic success");                // 通过characteristic.getValue()获取信息                ...            } else {                Log.i(TAG, "read characteristic fail " + status);            }        }        @Override        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {            super.onCharacteristicWrite(gatt, characteristic, status);            if (status == BluetoothGatt.GATT_SUCCESS) {                Log.i(TAG, "characteristic write success ");                ...            } else {                Log.i(TAG, "characteristic write fail " + status);                ...            }        }    };

5. 使能和接收通知

读特征值的方式只能是手机去读取设备的信息,而不能设备主动发送信息过来。如果想要实现设备主动发送信息给手机,必须手机端先使能一个特征值,之后设备就可以通过这个特征值发送信息。
使能的方法:

/**     * 使能通知     *     * @param serviceUUID        服务UUID     * @param characteristicUUID 特征值UUID     * @param notificationFlag   是否开启     */    public void toggleNotification(String serviceUUID, String characteristicUUID, boolean notificationFlag) {        if ( mBluetoothAdapter == null || mBluetoothGatt == null) {            Log.w(TAG, "BluetoothAdapter not initialized");             // error            return;        }        if (mBleDeviceService != null) {            BluetoothGattCharacteristic characteristic = mBleDeviceService.getCharacteristic(UUID.fromString(serviceUUID),                    UUID.fromString(characteristicUUID));            if (characteristic != null) {                int permission = (byte) characteristic.getProperties();                if ((permission & BluetoothGattCharacteristic.PROPERTY_NOTIFY) == BluetoothGattCharacteristic.PROPERTY_NOTIFY) {                    UUID CCC = UUID.fromString(BleConstants.CONFIG_DESCRIPTOR);                    mBluetoothGatt.setCharacteristicNotification(characteristic, notificationFlag); //Enabled locally                    BluetoothGattDescriptor config = characteristic.getDescriptor(CCC);                    config.setValue(notificationFlag ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE :                            BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);                    mBluetoothGatt.writeDescriptor(config); //Enabled remotely                } else {                    Log.i(TAG, "characteristic has no notification property");                    // error                }            } else {                Log.i(TAG, "null characteristic");                 // error            }        }    }

获取使能是否成功的结果以及接收设备发送来的信息的回调方法:

private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {        @Override        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {            super.onCharacteristicChanged(gatt, characteristic);            Log.i(TAG, "onCharacteristicChanged: " + Arrays.toString(characteristic.getValue()) + " " +                    characteristic.getUuid().toString());            // 可以用characteristic.getValue()读取信息。        }        @Override        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {            super.onDescriptorWrite(gatt, descriptor, status);            if (status == BluetoothGatt.GATT_SUCCESS) {                Log.i(TAG, "Notification/indication  enabled successfully");                ...            } else {                Log.i(TAG, "Notification/indication enabled failed");                // error            }        }    };

代码中用的是Notification,还有另一个方法是关于Indication的(将toggleNotification方法内的BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE替换为BluetoothGattDescriptor.ENABLE_INDICATION_VALUE),二者的区别也是前者类似TCP后者类似UDP。

5. 断开连接

最后,就是通讯结束后断开连接的方法:

    public void disconnect() {        if (mBluetoothAdapter == null || mBluetoothGatt == null) {            Log.i(TAG, "BluetoothAdapter not initialized");            return;        }        mBluetoothGatt.disconnect();    }

更多相关文章

  1. Android(安卓)蓝牙配对、连接和通信
  2. Android学习笔记——蓝牙入门
  3. Android调用MediaScanner进行扫描
  4. android开发笔记之2012版辅助开发工具包(ADT)新功能特性介绍及安装
  5. 2012版辅助开发工具包(ADT)新功能特性介绍及安装使用
  6. 启动qt_Qt编程实例:基于Android的BLE通信软件
  7. 条码扫描二维码扫描——ZXing android 源码简化
  8. Android(安卓)Wear手表蓝牙连接Android(安卓)Studio调试/开发的
  9. android Zxing二维码扫描 竖屏切换问题的解决

随机推荐

  1. android横竖屏切换后不重建不销毁Activit
  2. 反弹效果实现
  3. Android(安卓)default phone mechanism
  4. Android(安卓)ActionBar
  5. Android(安卓)—— Retrofit 请求注解分
  6. Android(安卓)中文 API (28) ―― CheckedT
  7. Android在一个应用程序中启动另一个应用
  8. Android记事本NotePad应用功能拓展(五)
  9. Android错误之--Error retrieving parent
  10. Android(安卓)killer和Apktool回编译错误