Android蓝牙——手机与蓝牙设备连接及通信

2020-04-07  本文已影响0人  何小送

前序

目录

一.Android蓝牙BLE关键类
1.1. BluetoothAdapter
1.2. BluetoothLeScanner
1.3. BluetoothDevice
1.4. BluetoothGatt
1.5. BluetoothGattCallback
1.6. BluetoothGattService
1.7. BluetoothGattCharacteristic
1.8. BluetoothGattDescriptor

二. 蓝牙连接
2.1. 蓝牙连接——手机app开始蓝牙前的准备工作
2.2. 蓝牙连接——第一步:获取设备BluetoothDevice
2.3. 蓝牙连接——第二步:把BluetoothGattCallback对象作为connectGatt方法的参数获取BluetoothGatt
2.4 蓝牙连接——第三步:BluetoothGattCallback主要回调方法处理分析

三. 注意事项

四.完整代码

一. Android 蓝牙BLE关键类

在蓝牙4.0BLE的API里需要熟悉以下8个关键类:
BluetoothAdapter,
BluetoothLeScanner,
BluetoothDevice,
BluetoothGatt,
BluetoothGattCallback,
BluetoothGattService,
BluetoothGattCharacteristic,
BluetoothGattDescriptor

1.1.BluetoothAdapter:这个类映射了设备的蓝牙模块,蓝牙功能的使用将从它开始。

获取方式:
BluetoothAdapter.getDefaultAdapter()
(即使通过BluetoothManager来获取,但其最终还是调用BluetoothAdapter.getDefaultAdapter()获取的)

常用方法 介绍
static boolean checkBluetoothAddress(String address) (静态方法)检查Mac地址是否是一个有效的蓝牙地址
static synchronized BluetoothAdapter getDefaultAdapter() (静态方法)获取一个默认的BluetoothAdapter对象
BluetoothDevice getRemoteDevice(String address) 通过Mac地址获取到蓝牙设备BluetoothDevice
BluetoothLeScanner getBluetoothLeScanner() 获取BluetoothLeScanner(Android5.0以上扫描和停止扫描需要用到BluetoothLeScanner)
boolean isEnabled() 判断蓝牙的开启状态
boolean enable() 弹出一个系统的是否打开/关闭蓝牙的对话框,选择禁止或者未处理返回false,选择允许返回true
boolean disable() 关闭蓝牙
boolean startLeScan(LeScanCallback callback) 扫描蓝牙(仅限Android4.3和4.4)
void stopLeScan(LeScanCallback callback) 停止扫描(仅限Android4.3和4.4)
...... ......

BluetoothAdapter类更多方法请详见
https://developer.android.google.cn/reference/android/bluetooth/BluetoothAdapter?hl=en

1.2. BluetoothLeScanner:Android5.0以上用来扫描和停止扫描的类

其获取方式:
BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner()

常用方法 介绍
void startScan(final ScanCallback callback) 开始扫描
void stopScan(ScanCallback callback) 停止扫描
void flushPendingScanResults(ScanCallback callback) 刷新存储在蓝牙控制器中的等待批扫描结果。
...... ......

BluetoothLeScanner类更多方法请详见https://developer.android.google.cn/reference/android/bluetooth/le/BluetoothLeScanner?hl=en

1.3. BluetoothDevice:蓝牙设备(即外围设备)

其获取方式有两种:
1.3.1 Mac地址直连
BluetoothAdapter.getDefaultAdapter().getRemoteDevice(mMacAdress);

1.3.2. 蓝牙扫描获取
Android4.3和4.4:
BluetoothAdapter#startLeScan(BluetoothAdapter.LeScanCallback)
Android5.0以上:
BluetoothLeScanner#startScan(ScanCallback)
BluetoothLeScanner#startScan(List<ScanFilter>, ScanSettings, ScanCallback)。

常用方法 Added in API level 介绍
BluetoothGatt connectGatt(Context context, boolean autoConnect,BluetoothGattCallback callback) 18(Android4.3) 连接Gatt并返回一个BluetoothGatt,该方法是BLE连接的核心方法
BluetoothGatt connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback, int transport, int phy, Handler handler) 26 Android(8.0) 同上
BluetoothGatt connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback, int transport, int phy) 26 Android(8.0) 同上
BluetoothGatt connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback, int transport) 23 Android(6.0) 同上
String getName() 获取蓝牙设备的名字
ParcelUuid[] getUuids() 获取蓝牙设备的uuid
String getAddress() 获取蓝int牙设备的Mac地址
int getBondState() 获取蓝牙设备的绑定状态
int getType() 获取蓝牙设备的类型
...... ......

BluetoothLeScanner类更多方法请详见
https://developer.android.google.cn/reference/android/bluetooth/BluetoothDevice?hl=en

1.4.BluetoothGatt:该类是核心类

其获取方式(请看1.3 BluetoothDevice的常用方法4种connectGatt)
autoConnect 参数表示是否断后自动重连,一般都设为false

此对象是对GATT协议的封装,布尔类型参数表示是否断后重连。由于是从远程设备处获取信息,所以蓝牙设备是服务端而手机是客户端。BluetoothGatt对象可对客户端进行相关操作。

常用方法 介绍 是否必要
boolean discoverServices() 蓝牙连接成功之后(还不能通信)调用该方法:发现远程设备提供的服务及其特征和描述符。 必要
boolean connect() 重新连接回远程设备,设备不在范围内后回来就可以使用该方法进行连接,true表示连接成功 \
void disconnect() 断开连接 必要
boolean writeDescriptor(BluetoothGattDescriptor descriptor) 把描述符的值写入到关联的远程设备中,写入成功则返回true 必要
boolean readDescriptor(BluetoothGattDescriptor descriptor) 从关联的远程设备读取给定描述符的值,读取成功则返回true \
boolean readCharacteristic(BluetoothGattCharacteristic characteristic) 从关联的远程设备读取请求的特征。读取操作成功启动则为true(该方法是主动向远程设备读取设备特征值,得看远程设备允不允许,一般不用该方法) \
boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) 将给定的特征及其值写入关联的远程设备。写入操作成功启动则为true (手机发送数据给远程设备,必须调用该方法) 必要
boolean setCharacteristicNotification(BluetoothGattCharacteristic characteristic,boolean enable) 设置特征值通知,enable为true则启用。设置成功返回true。(一般都使用该方法,然后远程设备数据有变化就会发出该特征通知) 必要
BluetoothGattService getService(UUID uuid) 获取Gatt中指定UUID的service 必要
List<BluetoothGattService> getServices() 获取Gatt的service列表 \
BluetoothDevice getDevice() 获取Gatt的远程设备device \
void close() 关闭BluetoothGatt,需要先调用disconnect()方法,再在BluetoothGattCallback#onConnectionStateChange里调用close()方法,再把BluetoothGatt置为null 必要
...... ......

BluetoothGatt类更多方法请详见
https://developer.android.google.cn/reference/android/bluetooth/BluetoothGatt?hl=en

1.5. BluetoothGattCallback:作为connectGatt方法的参数,实现BluetoothGatt的回调,非常重要

该类所有回调方法中的status都是指操作是否成功

常用方法 介绍 是否必要
void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) 连接状态的改变 必要
void onServicesDiscovered(BluetoothGatt gatt, int status) 发现服务成功后回调该方法。服务的获取,特征的读取和写入,描述符的读取和写入,设置特征通知等等都在该方法里写 必要
void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) 描述符值写入远程设备操作的结果的回调,写入成功则蓝牙可以开始通信啦 必要
void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) 描述符读取操作的结果的回调 \
void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) 由于远程特征通知而触发的回调。远程设备特征(数据)改变就会回调该方法 必要
void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) 指示特征写操作的结果。即手机发送数据后的回调 可选
void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic,int status) 回调报告特征读取操作的结果。 \
...... ...... \

BluetoothGattCallback类更多方法请详见
https://developer.android.google.cn/reference/android/bluetooth/BluetoothGattCallback?hl=en

1.6. BluetoothGattService:蓝牙Gatt服务,核心类

获取方式:
1.6.1. BluetoothGatt#getService(UUID uuid)
1.6.2. BluetoothGatt#getServices()
1.6.3. BluetoothGattCharacteristic#getService()

常用方法 介绍 是否必要
BluetoothGattCharacteristic getCharacteristic(UUID uuid) 获取此服务提供的特征列表之外具有给定UUID的特征。 必要
List<BluetoothGattCharacteristic> getCharacteristics() 获取此服务中包含的特征列表。 \
UUID getUuid() 获取此服务的UUID \
...... ...... \

BluetoothGattService类更多方法请详见
https://developer.android.google.cn/reference/android/bluetooth/BluetoothGattService?hl=en

1.7. BluetoothGattCharacteristic:特征,核心类

获取方式:
1.7.1. BluetoothGattDescriptor#getCharacteristic()
1.7.2. BluetoothGattService#getCharacteristic(UUID uuid)BluetoothGattDescriptor

常用方法 介绍 是否必要
BluetoothGattDescriptor getDescriptor(UUID uuid) 返回此特性的描述符列表之外具有给定UUID的描述符。 必要
List<BluetoothGattDescriptor> getDescriptors() 返回此特征的描述符列表。 \
UUID getUuid() 返回此特征的UUID \
byte[] getValue() 获取该特性的存储值。 必要
boolean setValue(String value) 设置该特性的本地存储值。 \
...... ...... \

BluetoothGattCharacteristic类更多方法请详见
https://developer.android.google.cn/reference/android/bluetooth/BluetoothGattCharacteristic?hl=en

1.8. BluetoothGattDescriptor:描述符,核心类

获取方式:
BluetoothGattCharacteristic#getDescriptor(UUID uuid)

常用方法 介绍 是否必要
boolean setValue(byte[] value) 更新此描述符的本地存储值。 必要
BluetoothGattCharacteristic getCharacteristic() 返回此描述符所属的特征。 \
UUID getUuid() 返回此描述符的UUID。 \
...... ...... \

BluetoothGattDescriptor类更多方法请详见
https://developer.android.google.cn/reference/android/bluetooth/BluetoothGattDescriptor?hl=en

一个Gatt包含多个服务;一个服务包含多个特征;一个特征包含多个描述符;
一个描述符对应一个特征;一个特征对应一个服务;一个服务对应一个Gatt

二. 蓝牙连接

2.1 蓝牙连接——手机app开始蓝牙前的准备工作

2.1.1.清单文件声明权限

<!--蓝牙权限-->
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<!-- LE Beacons位置相关权限-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!--蓝牙模块 设置为true表示只有支持蓝牙的手机才能安装-->
<uses-feature
    android:name="android.hardware.bluetooth_le"
    android:required="true" />

2.1.2.动态申请定位权限(代码里一定要检查GPS是否打开)
动态申请权限我用了一个第三方的库,这里就不放代码了,这里放的是判断GPS是否打开的代码

    /**
     * 判断GPS是否开启,GPS或者AGPS开启一个就认为是开启的
     *
     * @return true 表示开启
     */
    private boolean isOPenGps() {
        LocationManager locationManager
                = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
        // 通过GPS卫星定位,定位级别可以精确到街(通过24颗卫星定位,在室外和空旷的地方定位准确、速度快)
        boolean gps = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
        // 通过WLAN或移动网络(3G/2G)确定的位置(也称作AGPS,辅助GPS定位。主要用于在室内或遮盖物(建筑群或茂密的深林等)密集的地方定位)
        boolean network = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
        if (gps || network) {
            Log.d(TAG, "GPS状态:打开");
            return true;
        }
        Log.e(TAG, "GPS状态:关闭");
        return false;
    }

2.1.3.蓝牙是否打开
这里写了一个判断蓝牙是否打开(包含手动打开关闭)的方法。里面先是判断当前蓝牙状态,如果处于关闭状态,则调用enable方法,则会弹出一个系统的是否打开/关闭蓝牙的对话框,禁止或者未处理返回false,允许返回true,然后再将其结果返回

    /**
     * 打开手机蓝牙
     *
     * @return true 表示打开成功
     */
    public boolean enable() {
        if (!getBluetoothAdapter().isEnabled()) {
            //若未打开手机蓝牙,则会弹出一个系统的是否打开/关闭蓝牙的对话框,禁止或者未处理返回false,允许返回true
            //若已打开手机蓝牙,直接返回true
            boolean enableState = getBluetoothAdapter().enable();
            Log.d(TAG, "(用户操作)手机蓝牙是否打开成功:" + enableState);
            return enableState;
        } else return true;
    }

2.1.4.设备的uuid。
设备的蓝牙服务uuid,特征值uuid(一个或多个),描述uuid等等有关的uuid。这些uuid表找设备硬件工程师要。
设备的Mac地址正常情况下也由硬件工程师提供,这样更方便。
2.1.5.准备一个子线程,到时耗时操作操作都塞给该子线程

    //子线程的HandlerThread,为子线程提供Looper
    private HandlerThread workHandlerThread;
    //子线程
    private Handler workHandler;
    private void initWorkHandler() {
        workHandlerThread = new HandlerThread("BleWorkHandlerThread");
        workHandlerThread.start();
        workHandler = new Handler(workHandlerThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        };
}

2.2.蓝牙连接——第一步:获取设备BluetoothDevice

当手机app都给了定位权限,gps打开了,蓝牙打开了,uuid啥的准备好了,子线程准备好了,我们就可以开始进行蓝牙连接了。
蓝牙连接第一步:获取目标蓝牙设备BluetoothDevice。 获取目标BluetoothDevice有两种方法:

BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(mMacAdress);

2.2.1. 在android 4.3 和 android 4.4进行蓝牙扫描,可使用Api

//进行蓝牙扫描。
BluetoothAdapter#startLeScan(BluetoothAdapter.LeScanCallback)
//关闭蓝牙扫描。
BluetoothAdapter#stopLeScan(BluetoothAdapter.LeScanCallback)

2.2.2. 在 android 5.0之后的版本(包括 5.0)建议使用新的Api进行蓝牙扫描(Android8.0以上建议加后台允许扫描):

//进行蓝牙扫描。
BluetoothLeScanner#startScan(ScanCallback)
BluetoothLeScanner#startScan(List<ScanFilter>, ScanSettings, ScanCallback)  Android8.0后台继续扫描需要使用该方法
//关闭蓝牙扫描。
BluetoothLeScanner#stopScan(ScanCallback)

2.2.3.扫描获取BluetoothDevice的API的详细使用

//打开扫描
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {//Android5.0(包括)以上扫描
    BluetoothLeScanner bluetoothLeScanner = BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner();
    if (bluetoothLeScanner != null)
        bluetoothLeScanner.startScan(highScanCallback);
} else {//Android4.3,4.4扫描
    BluetoothAdapter.getDefaultAdapter().startLeScan(lowScanCallback);
}
-----------------------------------------------------------------------------------------------------
//关闭扫描
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {//Android5.0(包括)以上关闭扫描
    BluetoothLeScanner bluetoothLeScanner = BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner();
    if (bluetoothLeScanner != null)
        bluetoothLeScanner.stopScan(highScanCallback);
} else{//Android4.3,4.4关闭扫描
    BluetoothAdapter.getDefaultAdapter().stopLeScan(lowScanCallback);
}
-----------------------------------------------------------------------------------------------------
/**
 * 高版本扫描回调
 * Android 5.0(API 21)(包含)以上的蓝牙回调
 */
private ScanCallback highScanCallback = new ScanCallback() {
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    public void onScanResult(int callbackType, ScanResult result) {
        super.onScanResult(callbackType, result);
        //根据目标蓝牙Mac地址去过滤
        if (mMacAdress.equals(result.getDevice().getAddress())) {
            //关闭扫描(放子线程)
            //doing...
            Log.d(TAG, "LeScanCallback-->" + "蓝牙扫描已找到设备,即将开始连接");
            mBluetoothDevice = result.getDevice();
            //开始连接(放子线程)
            //doing...
        }
    }

    @Override
    public void onBatchScanResults(List<ScanResult> results) {
        super.onBatchScanResults(results);
        Log.d(TAG, "ScanCallback: onBatchScanResults");
    }

    @Override
    public void onScanFailed(int errorCode) {
        super.onScanFailed(errorCode);
        Log.e(TAG, "ScanCallback: onScanFailed");
    }
};
-----------------------------------------------------------------------------------------------------
/**
 * 低版本扫描回调
 * Android 4.3(API 18)(包含)以上,Android 5.0(API 21)(不包含)以下的蓝牙回调
 */
private BluetoothAdapter.LeScanCallback lowScanCallback = new BluetoothAdapter.LeScanCallback() {
    @Override
    public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
        //根据目标蓝牙Mac地址去过滤
        if (mMacAdress.equals(device.getAddress())) {
            //关闭扫描(放子线程)
            //doing...
            Log.d(TAG, "LeScanCallback-->" + "蓝牙扫描已找到设备,即将开始连接");
            mBluetoothDevice = result.getDevice();
            //开始连接(放子线程)
            //doing...
        }
    }
};

注意:1. 扫描到目标设备后,要关闭蓝牙扫描,耗电。2. 扫描和关闭扫描都是耗时操作,需要放在子线程中

2.3. 蓝牙连接——第二步:把BluetoothGattCallback对象作为connectGatt方法的参数获取BluetoothGatt

通过目标BluetoothDevice的connectGatt方法进行连接。并将BluetoothGattCallback作为connectGatt方法的参数

BluetoothDevice#connectGatt方法:连接到由该设备托管的GATT服务器,该方法的返回类型为BluetoothGatt。

connect()和connectGatt区别:都是连接BLE设备的方法,但二者用法不同。
connectGatt是BluetoothDevice类下的方法,功能是向BLE设备发起连接,然后得到一个BluetoothGatt类型的返回值,利用这个返回值可以进行下一步操作。
connect是BluetoothGatt类下的方法,功能是re-connect,重新连接。如果BLE设备和APP已经连接过,但是因为设备超出了蓝牙的连接范围而断掉,那么当设备重新回到连接范围内时,可以通过connect()重新连接。

2.3.1. 新建一个BluetoothGattCallback,将其作为connectGatt的回调参数,非常核心
Android18以上所有版本通用:
BluetoothDevice#connectGatt(Context context, boolean autoConnect,BluetoothGattCallback callback)
Android23以上版本建议:
BluetoothDevice#connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback, int transport)

2.4 蓝牙连接——第三步:BluetoothGattCallback主要回调方法处理分析

\color{red}{2.4.1. onConnectionStateChange(BluetoothGatt gatt, int status, int newState)}方法:
2.4.1.1 status为GATT_SUCCESS时:说明操作成功(如连接成功这个操作)。然后判断newState取值(四种:连接中,已连接,断开连接中,已断开连接a),我们监听已连接和已断开两种状态就可以了,
2.4.1.1.1 newState为已连接时,我们就开g始g发现服务即调用gatt的discoverServices方法。
2.4.1.2 status为错误的133时:调用gatt的disconnect方法,然后在onConnectionStateChange方法里调用Gatt的close方法并置空gatt并重新开始使用connectGatt方法进行连接

@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
    super.onConnectionStateChange(gatt, status, newState);
    if (status == BluetoothGatt.GATT_SUCCESS) {
        switch (newState) {
            case BluetoothProfile.STATE_DISCONNECTED:
                mBluetoothGatt.close();
                mBluetoothGatt = null;
                break;
            case BluetoothProfile.STATE_CONNECTED:
                //发现服务
                mBluetoothGatt.discoverServices();
                break;
        }
        return;
    }
    if (status == 133) {
        //1.需要清除Gatt缓存  2.断开连接  3.关闭Gatt  4.重新连接
        //doing something...
    }
}

\color{red}{2.4.2. onServicesDiscovered(BluetgoothGatt gatt, int statugs)}方法
2.4.2.1 status为GATT_SUCCESS时,调用gatt的的getService(UUID uuid)获取一个BluetoothGattService对象,或者gatt的getServices()获取服务列表拿到想要的BluetoothGattService对象。
然后通过service的getCharacteristic(UUID uuid)获取一个BluetoothGattCharacteristic特征A对象(同样可以getCharacteristics()获取),我们特征对象可以为1个或者多个,这个特征个数得看硬件工程师那边。然后给特征A设置通知即gatt的setCharacteristicNotification(BluetoothGattCharacteristic characteristic,boolean enable),这样远程设备数据发生改变,就会直接回调onCharacteristicChanged方法从而获取远程设备发送的数据。我们通过特征A的getDescriptor(UUID uuid)方法获取特征A的BluetoothGattDescriptor指定描述符对象,然后给特征A的指定描述符设置值setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);在把描述符作为参数,调用gatt的writeDescriptor(BluetoothGattDescriptor descriptor),将描述符写入gatt,成功了将会在onDescriptorWrite回调
2.4.1.2 status为错误的133时:调用gatt的disconnect方法,然后在onConnectionStateChange方法里调用Gatt的close方法并置空gatt并重新开始使用connectGatt方法进行连接

//发现服务成功后,会触发该回调方法。status:远程设备探索是否成功
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
    super.onServicesDiscovered(gatt, status);

    if (status == BluetoothGatt.GATT_SUCCESS) {
        Log.d(TAG, "onServicesDiscovered-->" + "status:" + status + "操作成功");
        //根据指定的服务uuid获取指定的服务
        BluetoothGattService gattService = gatt.getService(UUID.fromString(mServiceUUID));

        //根据指定特征值uuid获取指定的特征值A
        mGattCharacteristicA = gattService.getCharacteristic(UUID.fromString(mReadCharacteristicUUID));

        //设置特征A通知,即设备的值有变化时会通知该特征A,即回调方法onCharacteristicChanged会有该通知
        mBluetoothGatt.setCharacteristicNotification(mGattCharacteristicA , true);

        //获取特征值其对应的通知Descriptor
        BluetoothGattDescriptor descriptor = mGattCharacteristicA .getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
        //写入你需要传递给外设的特征的描述值(即传递给外设的信息)
        descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
        //通过GATT实体类将,特征值写入到外设中。成功则在 onDescriptorWrite 回调
        mBluetoothGatt.writeDescriptor(descriptor);
    }

}

上面方法里的总结:1.获取Gatt的服务S。2.获取S服务的特征A。3.给Gatt设置特征A通知。4.获取特征A的描述符D。5.把描述符D写入Gatt
注意:一般远程设备硬件工程师会对一个服务给予多个特征,比如给予2个特征,一个用来读取数据,一个用来写入数据。

\color{red}{2.4.3. onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status)}方法
如果status为GATT_SUCCESS,那么恭喜该服务以及该服务下的特征可以通信啦(但并不代表双方可以互相发送数据通信,因为需要确认是否设置特征通知成功,并且手机发送数据的特征是否也是成功的,如果这两个都是成功的,那么恭喜可以互相成功发送数据啦)。最好等待个200ms再发送命令。如果没有回调该方法,说明还不能通信,请检查uuid之类的是否正确,如果uuid之类的没问题,看是否硬件工程师那边有问题。

//设置Descriptor后回调
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
    super.onDescriptorWrite(gatt, descriptor, status);
    if (status == BluetoothGatt.GATT_SUCCESS) {
        Log.d(TAG, "onDescriptorWrite-->" + "描述符写入操作成功,蓝牙连接成功并通信桥梁成功打通!" );
        //如果设置特征通知是成功的,手机发送数据的特征也是成功的,那么就可以互相成功发送数据了,那么到这里就连接到通信整个过程都已完成,可以互相收发数据了
         //等待个200ms,使其通信通道更稳定
        //doing...
    }
}

\color{red}{2.4.4. onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic)}方法
当设置了特征通知,那么在设备对应的特征值有变化时会调用该方法

        //设备的值有变化时会主动返回
        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicChanged(gatt, characteristic);
            Log.d(TAG, "onCharacteristicChanged-->" + characteristic.getUuid());
            //过滤,判断是否是目标特征值
            if (!mReadCharacteristicUUID.equals(characteristic.getUuid().toString())) return;
            if (bleCallback != null){//通过自己写的一个接口回调传出去
                bleCallback.getDeviceReturnData(characteristic.getValue());
              }
        }

2.4.5 和. onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status)
手机发送数据成功会回调该方法,我们可以对比发送的数据是否正确

//发送数据后的回调,可以在此检测发送的数据包是否有异常
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
    super.onCharacteristicWrite(gatt, characteristic, status);
    if (status == BluetoothGatt.GATT_SUCCESS) {
        Log.d(TAG, "onCharacteristicWrite:发送数据成功:" + binaryToHexString(characteristic.getValue()));
    } 
}

其他的回调方法一般都可以不用了,用上面四个回调方法就够了,甚至只要前三个回调方法就可以了。其他的回调方法请看 1.5. BluetoothGattCallback:实现BluetoothGatt的回调

2.4 蓝牙连接——第四步:手机发送数据:

注意:

  1. 写入数据的特征并不一定就是设置特征通知的那个特征,这个得看远程设备的硬件工程师的那边的定义
  2. 发送数据最多20字节,超过20字节则需要切割分包,发送一次数据后,需要睡一会儿,防止下一条数据发送过快
  3. 发送成功后会回调onCharacteristicWrite方法,在该方法里可以检查所发送的数据
    private void sendData(byte[] data) {
        try {
            if (data.length <= 20) {
                if (mGattCharacteristicA == null) {
                    Log.e(TAG, "mGattCharacteristicA 为空,发送数据失败");
                    return;
                }
                if (mBluetoothGatt == null) {
                    Log.e(TAG, "mBluetoothGatt为空,发送数据包失败");
                    return;
                }
                mGattCharacteristicA .setValue(data);
                mBluetoothGatt.writeCharacteristic(mGattCharacteristicA );
            } else {
                Log.i(TAG, "数据包分割");
                byte[] b1 = new byte[20];
                byte[] b2 = new byte[data.length - 20];
                for (int i = 0; i < 20; i++) {
                    b1[i] = data[i];
                }
                for (int i = 20; i < data.length; i++) {
                    b2[i - 20] = data[i];
                }
                sendData(b1);
                sleep();
                sendData(b2);
                sleep();//防止下一条数据发送过快
            }
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, "发送数据包异常" + e.toString());
        }
    }

注意:至于手机发送数据给远程设备,也是需要特征对象的,其特征可以和上面的特征A一样,也有可能不一样,这个是要看远程设备的硬件工程师那边怎么处理的。而且硬件工程师可以对一个服务设置很多个特征,也可以对一个gatt设置很多个服务,比如新加一个功能,硬件工程师就可以新开一个服务等等

三.注意事项:

  1. connectGatt方法,Android6.0以上建议使用connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback, int transport)方法,transport设为TRANSPORT_LE。
  2. 扫描/关闭蓝牙 30s内不能超过5次,Android7.0以上Google为了防止BLE扫描滥用
  3. 调用disconnect()后不能立即调用close(),需要在onConnectionStateChange回调里调用close()
  4. 使用完后要把gatt给断掉并且关掉并置空,不然连接成功个六七次后就连接不上了
  5. 出现133错误,或者连接以及通信有问题时,应清除gatt缓存,断开并关闭和置空gatt,然后重新连接
  6. 手机发送数据最多20字节,如果手机发送数据超过20字节,则需要进行分包
  7. 有的地方需要等一会儿,保证执行完毕,如停止搜索需要时间,不能马上调用connectGatt,应等一会儿使停止搜索能执行完毕。发送完一条数据,需要等一会儿,防止下一条发送过快。在onDescriptorWrite回调里如果成功了,务必等一会儿再进行发送指令之类的
  8. connectGatt(context, autoConnect, callback);autoConnect务必设置成false
  9. 服务uuid和特征uuid不能搞错,而且硬件工程师可能会给予多个特征uuid,比如会有两个特征uuid,一个用来读取,一个用来发送。描述符uuid一般为“00002902-0000-1000-8000-00805f9b34fb”
  10. 要检查GPS是否打开,Android6.0以上要动态申请定位权限
  11. Android8.0以上退到后台或息屏后需要继续扫描(谷歌在8.0以上为了省电默认退到后台和息屏后无法扫描),请使用BluetoothLeScanner#startScan(List<ScanFilter>, ScanSettings, ScanCallback)方法,限于篇幅具体请大家百度相关内容。

四.完整代码

  1. 代码是基于android包的。androidx包需要自行修改一下代码
  2. 需要引用第三方权限库 implementation 'com.github.tbruyelle:rxpermissions:0.10.2'
  3. AndroidManifest.xml需要的权限
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  4. BleHelp.java的使用:调用init方法,再调用setMacAndUuids方法,再调用start方法即可。退出需要调用disConnect方法。其中的远程设备的服务uuid,特征uuid还有Mac地址需要其硬件工程师提供,代码中的特征uuid看硬件工程师给几个,然后我们在代码里自行修改一下便是了

BleHelp.java文件

package ble.hsj.pri.ble;

import android.Manifest;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
import android.content.Context;
import android.location.LocationManager;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.support.annotation.RequiresApi;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import android.widget.Toast;

import com.tbruyelle.rxpermissions2.RxPermissions;

import java.lang.reflect.Method;
import java.util.List;
import java.util.UUID;

import io.reactivex.functions.Consumer;

/**
 * author : 何送军
 * date   : 2020/3/22  21:18
 * desc   :仅支持Android4.3(API 18)以上版本
 * version: 1.0
 */

@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
public class BleHelp  {

    private FragmentActivity context;
    private static final String TAG = "BleHelp-->";

    //UUID和Mac地址
    private String mServiceUUID;
    private String mReadCharacteristicUUID;//特征uuid
    private String mWriteCharacteristicUUID;//特征uuid
    private String mMacAdress;

    //蓝牙设备
    private BluetoothDevice mBluetoothDevice;
    //蓝牙服务
    private BluetoothGatt mBluetoothGatt;
    //子线程的HandlerThread,为子线程提供Looper
    private HandlerThread workHandlerThread;
    //子线程
    private Handler workHandler;
    //蓝牙读取特征值
    BluetoothGattCharacteristic mReadGattCharacteristic;
    //蓝牙写出特征值
    BluetoothGattCharacteristic mWriteGattCharacteristic;


    private static final int LINK_TIME_OUT = 1000;
    private static final int START_SCAN = 1001;
    private static final int STOP_SCAN = 1002;
    private static final int CONNECT_GATT = 1003;
    private static final int DISCOVER_SERVICES = 1004;
    private static final int DISCONNECT_GATT = 1005;
    private static final int CLOSE_GATT = 1006;
    private static final int SEND_DATA = 1007;

    //调用disConnect()方法后是否需要调用close方法
    private boolean isDisConnectNeedClose = true;
    //Android8.0以上,退到后台或者息屏后,是否还需要扫描(谷歌为省电8.0以上默认关闭)
    private boolean isAllowSacnHomeSuperM = false;
    //默认连接时间25秒
    private int linkTime = 25000;

    private BleCallback bleCallback;

    private BleHelp() {
    }

    public static BleHelp getInstance() {
        return SingleInstance.sInstance;
    }

    /**
     * 静态内部类,单例
     */
    private static class SingleInstance {
        private static final BleHelp sInstance = new BleHelp();
    }

    public void init(FragmentActivity activity, BleCallback bleCallback) {
        this.context = activity;
        this.bleCallback = bleCallback;
    }

    private boolean checkAllUUID(FragmentActivity activity) {
        this.context = activity;
        if (this.context == null) {
            Log.e(TAG, "BleHelp初始化失败:" + "context为空......");
            Toast.makeText(context, "蓝牙模块初始化失败,请联系开发商...", Toast.LENGTH_LONG).show();
            return false;
        }
        if (this.bleCallback == null) {
            Log.e(TAG, "BleHelp初始化失败:" + "bleCallback为空......");
            Toast.makeText(context, "蓝牙模块初始化失败,请联系开发商...", Toast.LENGTH_LONG).show();
            return false;
        }
        if (!enable()) {
            Log.e(TAG, "BleHelp初始化失败:" + "(用户操作)未打开手机蓝牙,蓝牙功能无法使用......");
            Toast.makeText(context, "未打开手机蓝牙,蓝牙功能无法使用...", Toast.LENGTH_LONG).show();
            return false;
        }
        if (!isOPenGps()) {
            Log.e(TAG, "BleHelp初始化失败:" + "(用户操作)GPS未打开,蓝牙功能无法使用...");
            Toast.makeText(context, "GPS未打开,蓝牙功能无法使用", Toast.LENGTH_LONG).show();
            return false;
        }
        if (!BluetoothAdapter.checkBluetoothAddress(mMacAdress)) {
            Log.e(TAG, "BleHelp初始化失败:" + "不是一个有效的蓝牙MAC地址,蓝牙功能无法使用...");
            Toast.makeText(context, "不是一个有效的蓝牙MAC地址,蓝牙功能无法使用", Toast.LENGTH_LONG).show();
            return false;
        }
        if (mServiceUUID == null) {
            Log.e(TAG, "BleHelp初始化失败:" + "gattServiceUUID为空,蓝牙功能无法使用...");
            Toast.makeText(context, "gattServiceUUID为空,蓝牙功能无法使用", Toast.LENGTH_LONG).show();
            return false;
        }
        if (mReadCharacteristicUUID == null) {
            Log.e(TAG, "BleHelp初始化失败:" + "mReadCharacteristicUUID为空,蓝牙功能无法使用...");
            Toast.makeText(context, "mReadCharacteristicUUID为空,蓝牙功能无法使用", Toast.LENGTH_LONG).show();
            return false;
        }
        if (mWriteCharacteristicUUID == null) {
            Log.e(TAG, "BleHelp初始化失败:" + "mWriteCharacteristicUUID为空,蓝牙功能无法使用...");
            Toast.makeText(context, "mWriteCharacteristicUUID为空,蓝牙功能无法使用", Toast.LENGTH_LONG).show();
            return false;
        }
        return true;

    }

    public void setMacAndUuids(String macAdress, String gattServiceUUID,
                               String readGattCharacteristicUUID, String writeGattCharacteristicUUID) {
        this.mMacAdress = macAdress;
        this.mServiceUUID = gattServiceUUID;
        this.mReadCharacteristicUUID = readGattCharacteristicUUID;
        this.mWriteCharacteristicUUID = writeGattCharacteristicUUID;
    }

    public void setLinkTime(int linkTime) {
        this.linkTime = linkTime;
    }

    public void start() {
        if (!checkAllUUID(context)) return;
        initWorkHandler();
        permissionLocation();
    }

    private void initWorkHandler() {
        workHandlerThread = new HandlerThread("BleWorkHandlerThread");
        workHandlerThread.start();
        workHandler = new Handler(workHandlerThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                    case LINK_TIME_OUT:
                        removeMessages(LINK_TIME_OUT);
                        sendEmptyMessage(STOP_SCAN);
                    case START_SCAN:
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                            BluetoothLeScanner bluetoothLeScanner = getBluetoothAdapter().getBluetoothLeScanner();
                            if (bluetoothLeScanner == null) return;
                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {//允许8.0以上退到后台能继续扫描
                                if (isAllowSacnHomeSuperM) {//Android8.0以上退到后台或息屏后是否还要扫描。我们将其默认为false
                                    //doing....
                                    return;
                                }
                            }
                            bluetoothLeScanner.startScan(highScanCallback);
                            return;
                        }
                        getBluetoothAdapter().startLeScan(lowScanCallback);
                        break;
                    case STOP_SCAN:
                        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
                            BluetoothLeScanner bluetoothLeScanner = getBluetoothAdapter().getBluetoothLeScanner();
                            if (bluetoothLeScanner != null)
                                bluetoothLeScanner.stopScan(highScanCallback);
                        } else
                            getBluetoothAdapter().stopLeScan(lowScanCallback);
                        //停止搜索需要一定的时间来完成,建议加以100ms的延时,保证系统能够完全停止搜索蓝牙设备。
                        sleep();
                        break;
                    case CONNECT_GATT:
                        mBluetoothGatt = mBluetoothDevice.connectGatt(context, false, bluetoothGattCallback);
                        break;
                    case DISCOVER_SERVICES:
                        mBluetoothGatt.discoverServices();
                        break;
                    case DISCONNECT_GATT:
                        boolean isRefreshSuccess = refreshDeviceCache(mBluetoothGatt);
                        if (isRefreshSuccess) mBluetoothGatt.disconnect();
                        else
                            Log.e(TAG, "bluetoothGatt断开连接失败:因清除bluetoothGatt缓存失败,故未调用disconnect()方法");
                        break;
                    case CLOSE_GATT://需要disconnect()方法后回调onConnectionStateChange,再调用close(),
                        mBluetoothGatt.close();
                        mBluetoothGatt = null;
                        Log.d(TAG, "bluetoothGatt关闭成功并置为null");
                        break;
                    case SEND_DATA:
                        sendData((byte[]) msg.obj);
                        break;
                }
            }
        };
    }

    /**
     * 是否手机蓝牙状态
     *
     * @return true 表示处于打开状态,false表示处于关闭状态
     */
    public boolean isEnabled() {
        boolean isEnabled = getBluetoothAdapter().isEnabled();
        Log.d(TAG, "手机蓝牙是否打开:" + isEnabled);
        return isEnabled;
    }

    /**
     * 打开手机蓝牙
     *
     * @return true 表示打开成功
     */
    public boolean enable() {
        if (!getBluetoothAdapter().isEnabled()) {
            //若未打开手机蓝牙,则会弹出一个系统的是否打开/关闭蓝牙的对话框,禁止或者未处理返回false,允许返回true
            //若已打开手机蓝牙,直接返回true
            boolean enableState = getBluetoothAdapter().enable();
            Log.d(TAG, "(用户操作)手机蓝牙是否打开成功:" + enableState);
            return enableState;
        } else return true;
    }

    /**
     * 关闭手机蓝牙
     *
     * @return true 表示关闭成功
     */
    public boolean disable() {
        if (getBluetoothAdapter().isEnabled()) {
            boolean disabledState = getBluetoothAdapter().disable();
            Log.d(TAG, "(用户操作)手机蓝牙是否关闭成功:" + disabledState);
            return disabledState;
        } else return true;
    }

    /**
     * 判断是否可以通过Mac地址直连
     * 判断通过Mac地址获取到的Device的name是否为空来确定是否可以直连
     * 该方式不是绝对的,仅供参考,需具体情况具体分析
     */
    private boolean isDirectConnect() {
        BluetoothDevice device = getBluetoothAdapter().getRemoteDevice(mMacAdress);
        if (device.getName() != null) {
            mBluetoothDevice = null;
            mBluetoothDevice = device;
            return true;
        } else return false;
    }

    /**
     * 断开连接
     *
     * @param isNeedClose 执行mBluetoothGatt.disconnect方法后是否需要执行mBluetoothGatt.close方法
     *                    执行
     */
    public void disConnect(boolean isNeedClose) {
        if (mBluetoothGatt == null) return;
        isDisConnectNeedClose = isNeedClose;
        workHandler.sendEmptyMessage(DISCONNECT_GATT);
    }

    /**
     * 该方法作为扩展方法,暂时设为private
     * 8.0以上退到后台或者息屏后,在没有停止扫描的情况下是否还能继续扫描,谷歌默认不扫描
     */
    private void allowSacnHomeSuperM(boolean isAllow) {
        this.isAllowSacnHomeSuperM = isAllow;
    }

    public void sendDataToDevice(byte[] data) {
        Message message = new Message();
        message.what = SEND_DATA;
        message.obj = data;
        workHandler.sendMessage(message);
    }

    private void sendData(byte[] data) {
        try {
            if (data.length <= 20) {
                if (mWriteGattCharacteristic == null) {
                    Log.e(TAG, "mWriteGattCharacteristic为空,发送数据失败");
                    return;
                }
                if (mBluetoothGatt == null) {
                    Log.e(TAG, "mBluetoothGatt为空,发送数据包失败");
                    return;
                }
                mWriteGattCharacteristic.setValue(data);
                mBluetoothGatt.writeCharacteristic(mWriteGattCharacteristic);
            } else {
                Log.i(TAG, "数据包分割");
                byte[] b1 = new byte[20];
                byte[] b2 = new byte[data.length - 20];
                for (int i = 0; i < 20; i++) {
                    b1[i] = data[i];
                }
                for (int i = 20; i < data.length; i++) {
                    b2[i - 20] = data[i];
                }
                sendData(b1);
                sleep();
                sendData(b2);
                sleep();//防止下一条数据发送过快
            }
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(TAG, "发送数据包异常" + e.toString());
        }
    }

    /**
     * 高版本扫描回调
     * Android 5.0(API 21)(包含)以上的蓝牙回调
     */
    private ScanCallback highScanCallback = new ScanCallback() {
        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            super.onScanResult(callbackType, result);
            Log.d(TAG, "LeScanCallback-->" + "扫描啊啊" + result.getDevice().getAddress());
            if (mMacAdress.equals(result.getDevice().getAddress())) {
                workHandler.removeMessages(LINK_TIME_OUT);
                workHandler.sendEmptyMessage(STOP_SCAN);
                Log.d(TAG, "LeScanCallback-->" + "蓝牙扫描已找到设备,即将开始连接");
                mBluetoothDevice = null;
                mBluetoothDevice = result.getDevice();
                workHandler.sendEmptyMessage(CONNECT_GATT);
            }
        }

        @Override
        public void onBatchScanResults(List<ScanResult> results) {
            super.onBatchScanResults(results);
            Log.d(TAG, "ScanCallback: onBatchScanResults");
        }

        @Override
        public void onScanFailed(int errorCode) {
            super.onScanFailed(errorCode);
            Log.e(TAG, "ScanCallback: onScanFailed");
        }
    };
    /**
     * 低版本扫描回调
     * Android 4.3(API 18)(包含)以上,Android 5.0(API 21)(不包含)以下的蓝牙回调
     */
    private BluetoothAdapter.LeScanCallback lowScanCallback = new BluetoothAdapter.LeScanCallback() {
        @Override
        public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
            if (mMacAdress.equals(device.getAddress())) {
                workHandler.removeMessages(LINK_TIME_OUT);
                workHandler.sendEmptyMessage(STOP_SCAN);
                Log.d(TAG, "LeScanCallback-->" + "蓝牙扫描已找到设备,即将开始连接");
                mBluetoothDevice = null;
                mBluetoothDevice = device;
                workHandler.sendEmptyMessage(CONNECT_GATT);
            }
        }
    };

    /**
     * 回调都是在子线程中,不可做更新 UI 操作
     */
    private BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
        @Override
        public void onPhyUpdate(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
            super.onPhyUpdate(gatt, txPhy, rxPhy, status);
            Log.d(TAG, "onPhyUpdate");
        }

        @Override
        public void onPhyRead(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
            super.onPhyRead(gatt, txPhy, rxPhy, status);
            Log.d(TAG, "onPhyRead");
        }

        //
        //status-->操作是否成功,如连接成功这个操作是否成功。会返回异常码
        //newState-->新的连接的状态。共四种:STATE_DISCONNECTED,STATE_CONNECTING,STATE_CONNECTED,STATE_DISCONNECTING
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            super.onConnectionStateChange(gatt, status, newState);
            if (status == BluetoothGatt.GATT_SUCCESS) {
                switch (newState) {
                    case BluetoothProfile.STATE_DISCONNECTED:
                        Log.d(TAG, "BluetoothGattCallback:onConnectionStateChange-->" + "status:" + status + "操作成功;" + " newState:" + newState + " 已断开连接状态");
                        if (isDisConnectNeedClose) workHandler.sendEmptyMessage(CLOSE_GATT);
                        break;
                    case BluetoothProfile.STATE_CONNECTED:
                        Log.d(TAG, "BluetoothGattCallback:onConnectionStateChange-->" + "status:" + status + "操作成功;" + " newState:" + newState + " 已连接状态,可进行发现服务");
                        //发现服务
                        workHandler.sendEmptyMessage(DISCOVER_SERVICES);
                        break;
                }
                return;
            }
            Log.e(TAG, "BluetoothGattCallback:onConnectionStateChange-->" + "status:" + status + "操作失败;" + " newState:" + newState);
            if (status == 133) {//需要清除Gatt缓存并断开连接和关闭Gatt,然后重新连接
                gattError133("onConnectionStateChange");
            }

        }

        //发现服务成功后,会触发该回调方法。status:远程设备探索是否成功
        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);
            for (int i = 0; i < gatt.getServices().size(); i++) {
                Log.d(TAG, "onServicesDiscovered-->" + "status:" + status + "操作成功急啊急啊" + gatt.getServices().get(i).getUuid());
            }
            if (status == BluetoothGatt.GATT_SUCCESS) {
                Log.d(TAG, "onServicesDiscovered-->" + "status:" + status + "操作成功");
                //根据指定的服务uuid获取指定的服务
                BluetoothGattService gattService = gatt.getService(UUID.fromString(mServiceUUID));
                if (gattService == null) {
                    Log.e(TAG, "onServicesDiscovered-->" + "获取服务指定uuid:" + mServiceUUID + "的BluetoothGattService为空,请联系外设设备开发商确认uuid是否正确");
                    return;
                }
                //根据指定特征值uuid获取指定的特征值一
                mReadGattCharacteristic = gattService.getCharacteristic(UUID.fromString(mReadCharacteristicUUID));
                if (mReadGattCharacteristic == null) {
                    Log.e(TAG, "onServicesDiscovered-->" + "获取指定特征值的uuid:" + mReadCharacteristicUUID + "的BluetoothGattCharacteristic为空,请联系外设设备开发商确认特征值uuid是否正确");
                    return;
                }
                //根据指定特征值uuid获取指定的特征值二
                mWriteGattCharacteristic = gattService.getCharacteristic(UUID.fromString(mWriteCharacteristicUUID));
                if (mWriteGattCharacteristic == null) {
                    Log.e(TAG, "onServicesDiscovered-->" + "获取指定特征值的uuid:" + mReadCharacteristicUUID + "的BluetoothGattCharacteristic为空,请联系外设设备开发商确认特征值uuid是否正确");
                    return;
                }
                //设置特征值通知,即设备的值有变化时会通知该特征值,即回调方法onCharacteristicChanged会有该通知
                mBluetoothGatt.setCharacteristicNotification(mReadGattCharacteristic, true);
                //获取特征值其对应的通知Descriptor
                BluetoothGattDescriptor descriptor = mReadGattCharacteristic.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));
                //写入你需要传递给外设的特征的描述值(即传递给外设的信息)
                descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                //通过GATT实体类将,特征值写入到外设中。在 onDescriptorWrite 回调里面发送握手
                boolean isSuccessWriteDescriptor = mBluetoothGatt.writeDescriptor(descriptor);
                if (!isSuccessWriteDescriptor) {
                    Log.e(TAG, "onServicesDiscovered-->" + "bluetoothGatt将特征值BluetoothGattDescriptor写入外设失败");
                }
                //通过Gatt对象读取特定特征(Characteristic)的特征值。从外设读取特征值,这个可有可无,一般远程设备的硬件工程师可能不会给该权限
                boolean isSuccessReadCharacteristic = mBluetoothGatt.readCharacteristic(mReadGattCharacteristic);
                if (!isSuccessReadCharacteristic) {
                    Log.e(TAG, "onServicesDiscovered-->" + "读取外设返回的值的操作失败,无法回调onCharacteristicRead,多半硬件工程师的问题或者没给权限");
                }
                return;
            }
            Log.e(TAG, "onServicesDiscovered-->" + "status:" + status + "操作失败");
            if (status == 133) {//需要清除Gatt缓存并断开连接和关闭Gatt,然后重新连接
                gattError133("onServicesDiscovered");
            }

        }

        //接收到的数据,不一定会回调该方法
        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicRead(gatt, characteristic, status);
            Log.d(TAG, "onCharacteristicRead-->" + characteristic.getValue().toString());
        }

        //发送数据后的回调,可以在此检测发送的数据包是否有异常
        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicWrite(gatt, characteristic, status);
            if (status == BluetoothGatt.GATT_SUCCESS) {
                Log.d(TAG, "onCharacteristicWrite:发送数据成功:" + binaryToHexString(characteristic.getValue()));
            } else Log.e(TAG, "onCharacteristicWrite:发送数据失败");
        }

        //设备的值有变化时会主动返回
        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicChanged(gatt, characteristic);
            Log.d(TAG, "onCharacteristicChanged-->" + characteristic.getUuid());
            //过滤,判断是否是目标特征值
            if (!mReadCharacteristicUUID.equals(characteristic.getUuid().toString())) return;
            if (bleCallback != null)
                bleCallback.getDeviceReturnData(characteristic.getValue());
        }

        @Override
        public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            super.onDescriptorRead(gatt, descriptor, status);
            Log.d(TAG, "onDescriptorRead-->" + "status:" + status + descriptor.getUuid());
        }

        //设置Descriptor后回调
        @Override
        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            super.onDescriptorWrite(gatt, descriptor, status);
            if (status == BluetoothGatt.GATT_SUCCESS) {
                Log.d(TAG, "onDescriptorWrite-->" + "描述符写入操作成功,蓝牙连接成功并可以通信成功!!!" + descriptor.getUuid());
                if (bleCallback != null)
                    bleCallback.connectSuccess();
            } else {
                Log.e(TAG, "onDescriptorWrite-->" + "描述符写入操作失败,蓝牙通信失败...");
            }
        }

        @Override
        public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
            super.onReliableWriteCompleted(gatt, status);
            Log.d(TAG, "onReliableWriteCompleted");
        }

        @Override
        public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
            super.onReadRemoteRssi(gatt, rssi, status);
            Log.d(TAG, "onReadRemoteRssi");
        }

        @Override
        public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
            super.onMtuChanged(gatt, mtu, status);
            Log.d(TAG, "onMtuChanged");
        }
    };

    //Gatt操作失败status为133时
    private void gattError133(String method) {
        Log.e(TAG, "BluetoothGattCallback:" + method + "--> 因status=133,所以将关闭Gatt重新连接...");
        disConnect(true);//断开连接并关闭Gatt
        if (isDirectConnect()) {
            Log.d(TAG, "此次为MAC地址直连");
            workHandler.sendEmptyMessage(CONNECT_GATT);
        } else {
            Log.d(TAG, "此次为蓝牙扫描连接");
            workHandler.sendEmptyMessage(START_SCAN);
        }
    }

    /**
     * 清理本地的BluetoothGatt 的缓存,以保证在蓝牙连接设备的时候,设备的服务、特征是最新的
     */
    private boolean refreshDeviceCache(BluetoothGatt gatt) {
        Method refreshtMethod = null;
        if (null != gatt) {
            try {
                for (Method methodSub : gatt.getClass().getDeclaredMethods()) {
                    if ("connect".equalsIgnoreCase(methodSub.getName())) {
                        Class<?>[] types = methodSub.getParameterTypes();
                        if (types != null && types.length > 0) {
                            if ("int".equalsIgnoreCase(types[0].getName())) {
                                refreshtMethod = methodSub;
                            }
                        }
                    }
                }
                if (refreshtMethod != null) {
                    refreshtMethod.invoke(gatt);
                }
                Log.d(TAG, "refreshDeviceCache-->" + "清理本地的BluetoothGatt 的缓存成功");
                return true;
            } catch (Exception localException) {
                localException.printStackTrace();
            }
        }
        Log.e(TAG, "refreshDeviceCache-->" + "清理本地清理本地的BluetoothGatt缓存失败");
        return false;
    }


    /**
     * 获取BluetoothAdapter,使用默认获取方式。无论如何都不会为空
     */
    private BluetoothAdapter getBluetoothAdapter() {
        return BluetoothAdapter.getDefaultAdapter();//用默认的
    }

    /**
     * 定位权限
     */
    @SuppressLint("CheckResult")
    private void permissionLocation() {
        if (context == null) return;
        final RxPermissions rxPermissions = new RxPermissions(context);
        rxPermissions.request(Manifest.permission.ACCESS_FINE_LOCATION).subscribe(new Consumer<Boolean>() {
            @Override
            public void accept(Boolean aBoolean) {
                if (aBoolean) {
                    //申请的定位权限允许
                    //设置整个连接过程超时时间
                    workHandler.sendEmptyMessageDelayed(LINK_TIME_OUT, linkTime);
                    //如果可以Mac直连则不扫描
                    if (isDirectConnect()) {
                        Log.d(TAG, "此次为MAC地址直连");
                        workHandler.sendEmptyMessage(CONNECT_GATT);
                    } else {
                        Log.d(TAG, "此次为蓝牙扫描连接");
                        workHandler.sendEmptyMessage(START_SCAN);
                    }
                } else {
                    //只要有一个权限被拒绝,就会执行
                    Log.d(TAG, "未授权定位权限,蓝牙功能不能使用:");
                    Toast.makeText(context, "未授权定位权限,蓝牙功能不能使用", Toast.LENGTH_LONG).show();
                }
            }
        });
    }

    /**
     * 判断GPS是否开启,GPS或者AGPS开启一个就认为是开启的
     *
     * @return true 表示开启
     */
    private boolean isOPenGps() {
        LocationManager locationManager
                = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
        // 通过GPS卫星定位,定位级别可以精确到街(通过24颗卫星定位,在室外和空旷的地方定位准确、速度快)
        boolean gps = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
        // 通过WLAN或移动网络(3G/2G)确定的位置(也称作AGPS,辅助GPS定位。主要用于在室内或遮盖物(建筑群或茂密的深林等)密集的地方定位)
        boolean network = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
        if (gps || network) {
            Log.d(TAG, "GPS状态:打开");
            return true;
        }
        Log.e(TAG, "GPS状态:关闭");
        return false;
    }

    /**
     * 睡一下:1.停止扫描时需要调用;2.发送特征值给外设时需要有一定的间隔
     */
    private void sleep() {
        try {
            Thread.sleep(100);//延时100ms
        } catch (InterruptedException e) {
            e.printStackTrace();
            Log.i("测试", "延迟异常");
        }
    }

    /**
     * @param bytes
     * @return 将二进制转换为十六进制字符输出
     * new byte[]{0b01111111}-->"7F" ;  new byte[]{0x2F}-->"2F"
     */
    private static String binaryToHexString(byte[] bytes) {
        String result = "";
        if (bytes == null) {
            return result;
        }
        String hex = "";
        for (int i = 0; i < bytes.length; i++) {
            //字节高4位
            hex = String.valueOf("0123456789ABCDEF".charAt((bytes[i] & 0xF0) >> 4));
            //字节低4位
            hex += String.valueOf("0123456789ABCDEF".charAt(bytes[i] & 0x0F));
            result += hex + ",";
        }
        return result;
    }

    public interface BleCallback {
        void connectSuccess();//连接成功

        void getDeviceReturnData(byte[] data);

        void error(int e);
    }

}
上一篇下一篇

猜你喜欢

热点阅读