关于蓝牙的各种概念,不太熟悉的,可以看我的51CTO的博客https://blog.51cto.com/4259297/1959736,上面的文章纯粹是做笔记用,有点杂乱,这里作一下系统的梳理。

网上找的一些比较好的博客:
https://www.jianshu.com/p/f20327b40268

客户端实现

客户端蓝牙要实现的功能就是扫描外围设备蓝牙,连接后实现通讯。

第1步:蓝牙初始化环境判断

1.判断android系统是否支持蓝牙

我这里说的是android系统,包括了手机,手机一般都有蓝牙模块。但是有些搭载android系统的硬件设备,不需要使用蓝牙,为了节约成功,砍掉了蓝牙模块。

 BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        if (mBluetoothAdapter == null) {
            // Device does not support Bluetooth
            responseFailure(pluginCallback,ErrorPool.BluetoothError.CODE_10009);
            return;
        }

mBluetoothAdapter null就表示系统不支持蓝牙。

2.打开系统的蓝牙开关

判断蓝牙开关是否打开

mBluetoothAdapter.isEnabled()

开启蓝牙,注意加上权限:

mBluetoothAdapter.enable()

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

<pre><code class="line-numbers">关闭蓝牙
```java
mBluetoothAdapter.disable()

监听蓝牙是否打开,通过监听BluetoothAdapter.ACTION_STATE_CHANGED这个广播。
需要说明的是BluetoothAdapter.STATE_ON和BluetoothAdapter.STATE_OFF这两个广播有时候不会回调(9.0 vivo iqoo手机),所以需要监听另外的两个状态会比较准确。

 @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            int btState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
            Logger.d(TAG, "[XXunBtReceiver] >> action: " + action);
            if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
                switch (btState) {
                    case BluetoothAdapter.STATE_TURNING_ON:
                        Logger.d(TAG, "[XXunBtReceiver] >> bt state turning on. ");
                        if(onBlueToothCallBack != null){
                            onBlueToothCallBack.onBlueToothOpen();
                        }

                        break;

                    case BluetoothAdapter.STATE_ON:
                        Logger.d(TAG, "[XXunBtReceiver] >> bt state is on.");

                        break;

                    case BluetoothAdapter.STATE_TURNING_OFF:
                        Logger.d(TAG, "[XXunBtReceiver] >> bt state is turning off. ");
                        if(onBlueToothCallBack != null){
                            onBlueToothCallBack.onBlueToothClosed();
                        }

                        break;

                    case BluetoothAdapter.STATE_OFF:        //有的时候不会回调
                        Logger.d(TAG, "[XXunBtReceiver] >> bt state is off. ");
                        break;

                }
            }
        }

第2步:扫描连接蓝牙

1.扫描外围蓝牙设备
1)定位权限申请

注意,BLE(蓝牙4.0)扫描对于6.0以上手机先要动态申请Manifest.permission.ACCESS_FINE_LOCATION这个权限(对于9.0手机,即使是动态权限也要在XML里声明,否则申请的时候不会有提示的UI。)

2)打开定位开关

判断GPS是否打开的方法

public static boolean isOPen(Context context) {
        int locationMode = 0;
        String locationProviders;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            try {
                locationMode = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.LOCATION_MODE);
            } catch (Settings.SettingNotFoundException e) {
                e.printStackTrace();
                return false;
            }
            return locationMode != Settings.Secure.LOCATION_MODE_OFF;
        } else {
            locationProviders = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
            return !TextUtils.isEmpty(locationProviders);
        }
    }

如果GPS开关没有打开,引导用户打开定位开关,注意有的手机即使用户打开了GPS开关,再返回到界面onActivityResult方法的resultCode并不是Activity.RESULT_OK,所以需要在onActivityResult方法里再一次调用判断GPS是否打开的方法。

 new AlertDialog.Builder(webViewFragment.getActivity())
                        .setTitle("提示")
                        .setMessage("当前手机扫描蓝牙需要打开定位功能")
                        .setNegativeButton("取消",
                                new DialogInterface.OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialog, int which) {
                                        dialog.dismiss();
                                        Logger.e(TAG, "用户拒绝了打开位置权限");
                                        responseFailure(pluginCallback, ErrorPool.BluetoothError.CODE_10001);
                                    }
                                })
                        .setPositiveButton("前往设置",
                                new DialogInterface.OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialog, int which) {
                                        webViewFragment.addResultListsners(new ActivityResultListener() {
                                            @Override
                                            public boolean onSdkActivityResult(int requestCode, int resultCode, Intent intent) {
//                                                if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_CODE_OPEN_GPS) {
//                                                    startDiscovery(webViewFragment, paramsBean, pluginCallback, mBluetoothAdapter, leScanCallback);
//                                                    return false;
//                                                }

                                                //上面的判断方法有BUG,在YuLong手机上,即使用户打开了定位服务,resultCode返回的并不是 Activity.RESULT_OK
                                                //那就再判断一次呐,机智如我!
                                                if(requestCode == REQUEST_CODE_OPEN_GPS){
                                                    boolean gpsIsOpen = GpsUtil.isOPen(WebApplication.getInstance().getApplication());
                                                    if(gpsIsOpen){
                                                        startDiscovery(webViewFragment, paramsBean, pluginCallback, mBluetoothAdapter, leScanCallback);
                                                        return false;
                                                    }

                                                    Logger.e(TAG, "用户拒绝了打开位置权限");
                                                    responseFailure(pluginCallback, ErrorPool.BluetoothError.CODE_10001);
                                                }
                                                return false;
                                            }
                                        });
                                        Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
                                        webViewFragment.startActivityForResult(intent, REQUEST_CODE_OPEN_GPS);
                                    }
                                })

                        .setCancelable(false)
                        .show();
3)开始扫描蓝牙
开始扫描

调用mBluetoothAdapter.startLeScan()方法,点进方法的注释,官方说明了这个方法是专门用来扫描BLE设备的方法。
注意mBluetoothAdapter.startDiscovery()方法是用来扫描经典蓝牙设备的,不是用来专门扫BLE的。

停止扫描

注意停止扫描方法传入的callback必须和开始扫描创建的callback是同一个对象,而且,停止扫描方法调用之后,callback不会立马回调,所以如果在回调里有精微的处理要注意这个地方。

        mBluetoothAdapter.stopLeScan(leScanCallback);
扫描回调
leScanCallback = new BluetoothAdapter.LeScanCallback() {
                @Override
                public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {

                }
            };

onLeScan方法回调的三个参数
device:扫描到的设备
rssi:蓝牙信号强度
scanRecord:扫描到的设备广播的数据(广播数据解析,请参照:https://blog.csdn.net/zzx2436125/article/details/79698017
,注意博客里没有类,可以去源码里找然后复制一份就行)
注意callback,同一个设备,如果rssi数值有变化,也会回调,需要根据需求进行过滤。
扫描到指定设备之后,注意一定要停止扫描!

2.连接蓝牙

调用上面的callback返回的device的connectGatt方法即可连接蓝牙,蓝牙的核心就是这个方法的回调,下面我截取我的主要代码如下(直接粘贴肯定有问题):

BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
        public void onConnectionStateChange(BluetoothGatt gatt, int status,
                                            int newState) {
            if (status != BluetoothGatt.GATT_SUCCESS) {
                Logger.e(TAG, "系统异常回调onConnectionStateChange");
                return;
            }

            if (newState == BluetoothGatt.STATE_CONNECTED) {
                if (BleDeviceContext.this.isDeviceConnected) {
                    Logger.e(TAG, "蓝牙已经处于连接状态,忽略系统的重复回调。");
                    return;
                }

                Logger.e(TAG, "蓝牙连接成功");

                //请求发现服务,否则onServicesDiscovered不会回调。
                gatt.discoverServices();

            } else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
                if (BleDeviceContext.this.isDeviceConnected == false) {
                    Logger.e(TAG, "蓝牙已经处于断开连接状态(有可能是通过wx.closeConnection方法来断开的),忽略系统的重复回调。");
                    return;
                }

                Logger.e(TAG, "蓝牙真正地断开连接");


                BleDeviceContext.this.isDeviceConnected = false;
                BleDeviceContext.this.curConnectedGatt = null;

            } else if (newState == BluetoothGatt.STATE_CONNECTING) {
                Logger.e(TAG, "onConnectionStateChange: 正在连接中...");
            }
        }

        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status != BluetoothGatt.GATT_SUCCESS) {
                Logger.e(TAG, "系统异常回调onServicesDiscovered");
                return;
            }
            Logger.e(TAG, "onServicesDiscovered: ");
            BleDeviceContext.this.bluetoothGattServiceList.addAll(gatt.getServices());
        }

        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic
                characteristic,
                                         int status) {
            if (status != BluetoothGatt.GATT_SUCCESS) {
                Logger.e(TAG, "系统异常回调onCharacteristicRead");
                return;
            }

            Logger.e(TAG, "onCharacteristicRead: ");
            byte[] value = characteristic.getValue();

        }

        public void onCharacteristicWrite(BluetoothGatt gatt,
                                          BluetoothGattCharacteristic characteristic, int status) {
            if (status != BluetoothGatt.GATT_SUCCESS) {
                Logger.e(TAG, "系统异常回调onCharacteristicWrite");
                if (onCharacteristicWriteResultListener != null) {
                    onCharacteristicWriteResultListener.onWriteFailure();
                }
                return;
            }
            Logger.e(TAG, "onCharacteristicWrite: ");
            if (onCharacteristicWriteResultListener != null) {
                onCharacteristicWriteResultListener.onWriteSuccess();
            }
        }

        public void onCharacteristicChanged(BluetoothGatt gatt,
                                            BluetoothGattCharacteristic characteristic) {
            Logger.e(TAG, "onCharacteristicChanged: ");
            byte[] value = characteristic.getValue();

            if (value != null) {
                Logger.e(TAG, "onCharacteristicChanged result: " + TransferUtil.bytes2HexString(value) + "\n" + "byte[] = " + value);
            }

        }

    };

关于上面的回调,我特地在此强调一下它的坑:
上面的回调方法需要过滤status != BluetoothGatt.GATT_SUCCESS的异常回调,也要过滤成功回调的重复回调过滤(通过一些boolean值控制,一定要准确。)。

连接成功之后,记得保存一下BluetoothGatt这个对象,后面会用到这个对象根据UUID获取服务或者与设备断开连接。

第3步:BLE核心操作

也就是对服务、特征值(相当于服务端暴露给客户端的接口)的各种操作,实现与服务端设备的通讯。关于这些概念不太清楚的,可以参考我文章开头的博客链接和https://blog.csdn.net/MakeWorks/article/details/69487344,设备、服务、特征(值、描述符)是依次包涵的关系。

1.发现服务

在上面的代码中,蓝牙连接成功之后的回调里调用

 gatt.discoverServices();

会触发BluetoothGattCallback的onServicesDiscovered方法被回调,在这里把服务(BluetoothGattService)列表缓存起来。注意这个过程需要一定的时间。

2.遍历服务特征值

遍历服务是获取特征值,特征值有一些属性:可读、可写、可notify、可indicate。

1)获取服务

可以从前面缓存的服务列表里获取,也可用连接成功返回的BluetoothGatt对象通过UUID来获取服务。

2)遍历服务的特征值
List<BluetoothGattCharacteristic> characteristics = supportedGattService.getCharacteristics();
3)判断特征值的属性
 /**
     * 判断特征值是否可通知 - notify的方式
     * @param bluetoothGattCharacteristic
     * @return
     */
    public static  boolean isNotifable(BluetoothGattCharacteristic bluetoothGattCharacteristic) {
        int charaProp = bluetoothGattCharacteristic.getProperties();
        if ((charaProp & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0
               /* || (charaProp & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0*/) {
            return true;
        }
        return false;
    }


    /**
     * 判断特征值是否可indicate
     * @param bluetoothGattCharacteristic
     * @return
     */
    public  static boolean isIndicatable(BluetoothGattCharacteristic bluetoothGattCharacteristic) {
        int charaProp = bluetoothGattCharacteristic.getProperties();
        if (/*(charaProp & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0
                || */(charaProp & BluetoothGattCharacteristic.PROPERTY_INDICATE) > 0) {
            return true;
        }
        return false;
    }



    public  static boolean isReadable(BluetoothGattCharacteristic bluetoothGattCharacteristic) {
        int charaProp = bluetoothGattCharacteristic.getProperties();
        // 可读
        if ((charaProp & BluetoothGattCharacteristic.PROPERTY_READ) > 0) {
            return true;
        }
        return false;
    }


    public  static boolean isWritable(BluetoothGattCharacteristic bluetoothGattCharacteristic) {
        int charaProp = bluetoothGattCharacteristic.getProperties();
        if ((charaProp & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) > 0
                || (charaProp & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) {
            return true;
        }
        return false;
    }
3.特征值操作

假如,取到连接成功保存的BluetoothGatt对象gatt,要操作的特征值BluetoothGattCharacteristic对象为characteristicObj.

特别注意:对于同一个连接,关于特征值操作的API 一定要等这个API的回调成功之后方可继续操作,绝对不能同时操作某个特征值,这个是应用层调用特别要注意的问题。

1)读取特征值

先利用前面给的方法判断特征值是否可读,只有可读才能读取特征值。

boolean readOk = gatt.readCharacteristic(characteristicObj)      //传入到读取的特征值BluetoothGattCharacteristic

注意读取的结果在readOk为true的基础上,是在连接的回调BluetoothGattCallback的onCharacteristicRead()方法异步返回的。

 public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic
                characteristic,
                                         int status) {
            if (status != BluetoothGatt.GATT_SUCCESS) {
                Logger.e(TAG, "系统异常回调onCharacteristicRead");
                return;
            }

            Logger.e(TAG, "onCharacteristicRead: ");
            byte[] value = characteristic.getValue();

        }
2)写特征值

写特征值其实就是往服务端发送数据
先利用前面给的方法判断特征值是否可写,只有可写才能给特征值写值。

characteristicObj.setValue(byte[])                   //将要写的数据赋值给特征值对象
boolean writeOk = gatt.writeCharacteristic(characteristicObj)      //写特征值对象

特别注意:由于低层蓝牙协议栈的限定,一次性写入的字节数不可超过20字节,否则会写不成功。

注意写入的结果是在writeOk 为true的基础,在连接的回调BluetoothGattCallback的onCharacteristicWrite()方法异步返回的。

  public void onCharacteristicWrite(BluetoothGatt gatt,
                                          BluetoothGattCharacteristic characteristic, int status) {
            if (status != BluetoothGatt.GATT_SUCCESS) {
                Logger.e(TAG, "系统异常回调onCharacteristicWrite");
                if (onCharacteristicWriteResultListener != null) {
                    onCharacteristicWriteResultListener.onWriteFailure();
                }
                return;
            }
            Logger.e(TAG, "onCharacteristicWrite: ");
            if (onCharacteristicWriteResultListener != null) {
                onCharacteristicWriteResultListener.onWriteSuccess();
            }
        }
3)打开或者关闭notify/indicate开关

这个操作就是控制可notify或者indicate的特征值是否可以接收服务端的数据,打开之后BluetoothGattCallback的onCharacteristicChanged方法才能回调,也就是接收服务端写入的数据的地方。

同理,得判断一下特征值的属性是notify还是indicate。

下面这个UUID是下面开启、关闭notify/indicate需要用到的描述符的UUID,是固定不变的。

private static final String UUID_CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR = "00002902-0000-1000-8000-00805f9b34fb";
  • indicate的处理:
/**
     * 开关indicate
     * @param gatt
     * @param characteristic
     * @param enable
     * @return
     */
    public static boolean setCharacteristicIndicate(BluetoothGatt gatt,
                                                   BluetoothGattCharacteristic characteristic,
                                                  boolean enable){
        Logger.e(TAG, "setCharacteristicIndicate: " + characteristic.getUuid().toString() + ",enable = " + enable);
    //第1步:改变特征值本身
        boolean success = gatt.setCharacteristicNotification(characteristic, enable);
        if(success == false) {
            Logger.e(TAG, "setCharacteristicNotification failure");
            return false;
        }

   //第2步:改变特征值描述符
        BluetoothGattDescriptor descriptor = characteristic.getDescriptor(formUUID(UUID_CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR));
        if (descriptor != null) {
            if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) {
                descriptor.setValue(enable ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE :
                        BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
            } else if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE) != 0) {
                descriptor.setValue(enable ? BluetoothGattDescriptor.ENABLE_INDICATION_VALUE :
                        BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);

            }
            return gatt.writeDescriptor(descriptor);
        }
        return false;
    }
  • notify的处理:
/**
     * 开关notify
     * @param gatt
     * @param characteristic
     * @param enable
     * @return
     */
    public static  boolean setCharacteristicNotification(BluetoothGatt gatt,
                                                  BluetoothGattCharacteristic characteristic,
                                                  boolean enable){
        Logger.e(TAG, "setCharacteristicNotification: " + characteristic.getUuid().toString() + ",enable = " + enable);
 //第1步:改变特征值本身
        boolean success = gatt.setCharacteristicNotification(characteristic, enable);
        if(success == false) {
            Logger.e(TAG, "setCharacteristicNotification failure");
            return false;
        }
   //第2步:改变特征值描述符
        BluetoothGattDescriptor descriptor = characteristic.getDescriptor(formUUID(UUID_CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR));
        if (descriptor != null) {
            descriptor.setValue(enable ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE :
                    BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
            return gatt.writeDescriptor(descriptor);
        }
        return false;
    }
4)监听特征值变化

监听特征值变化,也就是监听服务端发来的数据。
注意这个是在上面notify、indicate打开的情况下才会回调

public void onCharacteristicChanged(BluetoothGatt gatt,
                                            BluetoothGattCharacteristic characteristic) {
            Logger.e(TAG, "onCharacteristicChanged: ");
            byte[] value = characteristic.getValue();

            if (value != null) {
                Logger.e(TAG, "onCharacteristicChanged result: " + TransferUtil.bytes2HexString(value) + "\n" + "byte[] = " + value);
            }
 }

第4步:多个外围设备的封装

综上所述,设备的区分是在连接处开始的,所以将连接、断开连接及其Callback单独封装到一个类里,Callback返回的重要参数也缓存在这个类里。然后创建一个Manager类来管理维护封装的类。
下面是我的代码,没法直接粘贴使用,仅供参考:

设备封装类

/**
 * 一台设备对应的上下文
 * 包括创建连接时所有关联的变量
 *
 * 注意:
 * 1)对于连接传给我的超时,我会做扫描和连接两个超时处理。
 *    扫描中如果超时,我会停止扫描,假如回调仍然找到了设备,超时了话,我会不进行后面的连接等操作。
 */
public class BleDeviceContext {
    private static final String TAG = "BleDeviceContext";
    private WebViewFragment webViewFragment;
    private Plugin.PluginCallback connectionImplPluginCallback;
    private BLEConnectionImpl bleConnectionImpl;
    private BluetoothGatt curConnectedGatt;
    private boolean isDeviceConnected = false;                                                    /**当前是否有设备在连接着
     特别要注意,部分android手机蓝牙断开回调会有很大延迟。//TODO 这个是在真正断开的回调里赋值的吗?
     */
    private CZWrapperBluetoothDevice czWrapperBluetoothDevice;                                    //当前连接的设备信息存储
    private boolean isOnServicesDiscoveredCalledBack = false;                                     //连接设备时,防止onServicesDiscovered方法重复回调。
    private CZBluetoothGattServiceList bluetoothGattServiceList;                                  //记录连接的设备发现的服务
    private BLEConnectionParamsBean bleConnectionParamsBean;
    private OnCharacteristicWriteResultListener onCharacteristicWriteResultListener;
    private final TimeCounter timeCounter;
    private boolean isTimeOut = false;            //判断是否是超时,如果超时,即使蓝牙连接上了也会自动断开。

    public BleDeviceContext() {
        this.bluetoothGattServiceList = new CZBluetoothGattServiceList();
        timeCounter = new TimeCounter();  //扫描、连接超时判断。
        isDeviceConnected = false;
    }

    BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
        public void onConnectionStateChange(BluetoothGatt gatt, int status,
                                            int newState) {
            if (status != BluetoothGatt.GATT_SUCCESS) {
                Logger.e(TAG, "系统异常回调onConnectionStateChange");
//                bleConnectionImpl.responseFailure(connectionImplPluginCallback,ErrorPool.BluetoothError.CODE_10008);
                return;
            }

            BleDeviceContext.this.curConnectedGatt = gatt;              //记录起来,用于断开连接。

            //关闭连接超时计时器
            timeCounter.stop();

            if (newState == BluetoothGatt.STATE_CONNECTED) {
                if (BleDeviceContext.this.isDeviceConnected) {
                    Logger.e(TAG, "蓝牙已经处于连接状态,忽略系统的重复回调。");
                    return;
                }

                if(isTimeOut) {
                   Logger.e(TAG, "连接超时,即使已经连接了,我也不处理,你连接晚了。");
                   force_disconnect();
                   return;
                }

                Logger.e(TAG, "蓝牙连接成功");
                WxBluetoothGlobalPropertyHolder.alreadyConnectedOnceDevices.add(czWrapperBluetoothDevice);
                BleDeviceContext.this.isDeviceConnected = true;

                bleConnectionImpl.responseSuccess(connectionImplPluginCallback);

                BleJsCallbackHelper.getInstance().callbackConnectionStateChange(webViewFragment, czWrapperBluetoothDevice, true);
                //请求发现服务,否则onServicesDiscovered不会回调。
                gatt.discoverServices();

            } else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
                if (BleDeviceContext.this.isDeviceConnected == false) {
                    Logger.e(TAG, "蓝牙已经处于断开连接状态(有可能是通过wx.closeConnection方法来断开的),忽略系统的重复回调。");
                    return;
                }

                Logger.e(TAG, "蓝牙真正地断开连接");
                bleConnectionImpl.responseSuccess(connectionImplPluginCallback);                            //对于closeBleConnect是成功回调

                BleDeviceContext.this.isDeviceConnected = false;
                BleDeviceContext.this.curConnectedGatt = null;

                BleJsCallbackHelper.getInstance().callbackConnectionStateChange(webViewFragment, czWrapperBluetoothDevice, false);
            } else if (newState == BluetoothGatt.STATE_CONNECTING) {
                Logger.e(TAG, "onConnectionStateChange: 正在连接中...");
            }
        }

        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            if (status != BluetoothGatt.GATT_SUCCESS) {
                Logger.e(TAG, "系统异常回调onServicesDiscovered");
                return;
            }
            Logger.e(TAG, "onServicesDiscovered: ");
            BleDeviceContext.this.bluetoothGattServiceList.addAll(gatt.getServices());
        }

        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic
                characteristic,
                                         int status) {
            if (status != BluetoothGatt.GATT_SUCCESS) {
                Logger.e(TAG, "系统异常回调onCharacteristicRead");
                return;
            }

            Logger.e(TAG, "onCharacteristicRead: ");
            byte[] value = characteristic.getValue();
            BleJsCallbackHelper.getInstance().callbackBleCharacteristicValue(webViewFragment,
                    czWrapperBluetoothDevice.getRealBluetoothDevice().getAddress(),
                    characteristic.getService().getUuid().toString(),
                    characteristic.getUuid().toString(),
                    TransferUtil.bytes2HexString(value));
            if (value != null) {
                Logger.e(TAG, "onCharacteristicRead result: " + TransferUtil.bytes2HexString(value) + "\n" + "byte[] = " + value);
            }
        }

        public void onCharacteristicWrite(BluetoothGatt gatt,
                                          BluetoothGattCharacteristic characteristic, int status) {
            if (status != BluetoothGatt.GATT_SUCCESS) {
                Logger.e(TAG, "系统异常回调onCharacteristicWrite");
                if (onCharacteristicWriteResultListener != null) {
                    onCharacteristicWriteResultListener.onWriteFailure();
                }
                return;
            }
            Logger.e(TAG, "onCharacteristicWrite: ");
            if (onCharacteristicWriteResultListener != null) {
                onCharacteristicWriteResultListener.onWriteSuccess();
            }
        }

        public void onCharacteristicChanged(BluetoothGatt gatt,
                                            BluetoothGattCharacteristic characteristic) {
            Logger.e(TAG, "onCharacteristicChanged: ");
            byte[] value = characteristic.getValue();
            BleJsCallbackHelper.getInstance().callbackBleCharacteristicValue(webViewFragment,
                    czWrapperBluetoothDevice.getRealBluetoothDevice().getAddress(),
                    characteristic.getService().getUuid().toString(),
                    characteristic.getUuid().toString(),
                    TransferUtil.bytes2HexString(value));
            if (value != null) {
                Logger.e(TAG, "onCharacteristicChanged result: " + TransferUtil.bytes2HexString(value) + "\n" + "byte[] = " + value);
            }

        }

    };

    public void connect(final BluetoothAdapter mBluetoothAdapter, WebViewFragment webViewFragment, final Plugin.PluginCallback connectionImplPluginCallback,
                        final BLEConnectionImpl bleConnectionImpl, final BLEConnectionParamsBean bleConnectionParamsBean) {
        if (isDeviceConnected) {
            bleConnectionImpl.responseFailure(connectionImplPluginCallback, ErrorPool.BluetoothError.createOtherTypeError("device already connected"));
            return;
        }
        this.webViewFragment = webViewFragment;
        this.connectionImplPluginCallback = connectionImplPluginCallback;
        this.bleConnectionImpl = bleConnectionImpl;
        this.bleConnectionParamsBean = bleConnectionParamsBean;

        this.isOnServicesDiscoveredCalledBack = false;
        BleDeviceContext.this.bluetoothGattServiceList.clear();

        final boolean[] isScanStopped = {false}; /**有时候,即使mBluetoothAdapter.stopLeScan()已经调用过了,Callback还是会有回调,需要过滤这种错误的回调。*/

        CZWrapperBluetoothDevice connectedDevice = WxBluetoothGlobalPropertyHolder.getInstance().findConnectedDevice(bleConnectionParamsBean.getDeviceId());
        if(connectedDevice != null){
            //已经连接过一次,直接连接。
            Logger.e(TAG, "已经连接过一次,直接连接 " + connectedDevice.toString() );
            BleDeviceContext.this.czWrapperBluetoothDevice = connectedDevice;

            connectedDevice.getRealBluetoothDevice().connectGatt(WebApplication.getInstance().getApplication(), false, bluetoothGattCallback);
        }else{
            //没有连接,扫描后再连接。
            final BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() {
                @Override
                public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
                    CZWrapperBluetoothDevice czWrapperBluetoothDevice = new CZWrapperBluetoothDevice(device, rssi,scanRecord);
                    if(bleConnectionParamsBean.getDeviceId().equals(czWrapperBluetoothDevice.getRealBluetoothDevice().getAddress())){
                        if(isScanStopped[0]){
                            Logger.e(TAG, "扫描已经停止了,但是系统还是在回调,拒绝这种回调,否则会导致连接不成功。");
                            return;
                        }
                        //关闭扫描超时计时器
                        timeCounter.stop();
                        if(isTimeOut) {
                            Logger.e(TAG, "扫描已经超时了,即使设备找到了,我也不做处理。");
                            return;
                        }

                        Logger.e(TAG, "扫描,找到指定的连接设备" + WxBluetoothDeviceInfo.parse(czWrapperBluetoothDevice));
                        Logger.e(TAG,"停止扫描,一定要记着,否则连接可能会有问题。");
                        mBluetoothAdapter.stopLeScan(this);
                        isScanStopped[0] = true;
                        Logger.e(TAG,"扫描后,开始连接设备");
                        BleDeviceContext.this.czWrapperBluetoothDevice = czWrapperBluetoothDevice;

                        device.connectGatt(WebApplication.getInstance().getApplication(), false, bluetoothGattCallback);
                        isTimeOut = false;
                        //开启连接超时计时器
                        if(bleConnectionParamsBean.getTimeout() > 0) {
                            timeCounter.setDelayTime(bleConnectionParamsBean.getTimeout());
                            timeCounter.setTimeCallBack(new TimeCounter.TimeCallBack() {
                                @Override
                                public void onTime(int passedCount, long delayMilliSeconds) {
                                    Logger.e(TAG, "连接超时了");
                                    timeCounter.stop();
                                    isTimeOut = true;
                                    force_disconnect();
                                    bleConnectionImpl.responseFailure(connectionImplPluginCallback, ErrorPool.BluetoothError.CODE_10012);
                                }
                            });
                            timeCounter.startCount();
                        }
                    }
                }
            };
            mBluetoothAdapter.startLeScan(leScanCallback);
            isScanStopped[0] = false;
            /**TODO bleConnectionParamsBean里会有一个超时时间,不清楚到底是扫描超时还是连接超时。
             * 目前处理办法是:扫描时用这个超时,连接也用这个超时。
             * */
            isTimeOut = false;
            if(bleConnectionParamsBean.getTimeout() > 0){
                timeCounter.setDelayTime(bleConnectionParamsBean.getTimeout());
                timeCounter.setTimeCallBack(new TimeCounter.TimeCallBack() {
                    @Override
                    public void onTime(int passedCount, long delayMilliSeconds) {
                        Logger.e(TAG, "扫描超时了");
                        isTimeOut = true;
                        timeCounter.stop();
                        bleConnectionImpl.responseFailure(connectionImplPluginCallback,ErrorPool.BluetoothError.CODE_10012);
                        mBluetoothAdapter.stopLeScan(leScanCallback);
                    }
                });
                timeCounter.startCount();
            }
        }
    }


    public void disconnect(){
        Logger.e(TAG, "disconnect: " );
        //清除之前的连接,防止用户连续调用了create2次,导致连接没有释放。
        if(this.curConnectedGatt != null && isDeviceConnected){
            this.curConnectedGatt.disconnect();
            this.curConnectedGatt = null;
            Logger.e(TAG, "disconnect:1111 " );
        }else{
            Logger.e(TAG, "disconnect:2222 " );
            bleConnectionImpl.responseFailure(connectionImplPluginCallback,ErrorPool.BluetoothError.createOtherTypeError("device already disconnected"));
        }
    }

    /**
     * 用于超时关闭,不给JS回调。
     */
    private void force_disconnect(){
        Logger.e(TAG, "disconnect: " );
        //清除之前的连接,防止用户连续调用了create2次,导致连接没有释放。
        if(this.curConnectedGatt != null && isDeviceConnected){
            this.curConnectedGatt.disconnect();
            this.curConnectedGatt = null;
            Logger.e(TAG, "disconnect:1111 " );
        }else{
            Logger.e(TAG, "disconnect:3333 " );
        }
    }



    /**
     * 是否有服务
     * @return
     */
    public  boolean isThereAnySerivce(){
        if(bluetoothGattServiceList == null || bluetoothGattServiceList.size() == 0) return false;
        return true;
    }


    /**
     * 查找指定的服务
     * @param serviceId
     * @return
     */
    public BluetoothGattService findService(String serviceId){
        if(bluetoothGattServiceList == null || bluetoothGattServiceList.size() == 0) return null;
        for (BluetoothGattService item : bluetoothGattServiceList){
            if(serviceId.equals(item.getUuid().toString())){
                return item;
            }
        }
        return null;
    }


    //写特征值监听
    public void registOnCharacteristicWriteResultListener(OnCharacteristicWriteResultListener onCharacteristicWriteResultListener){
        this.onCharacteristicWriteResultListener = onCharacteristicWriteResultListener;
    }

    public interface OnCharacteristicWriteResultListener{
        void onWriteSuccess();
        void onWriteFailure();
    }

    //java bean
    public BluetoothGatt getCurConnectedGatt() {
        return curConnectedGatt;
    }

    public void setCurConnectedGatt(BluetoothGatt curConnectedGatt) {
        this.curConnectedGatt = curConnectedGatt;
    }

    public boolean isDeviceConnected() {
        return isDeviceConnected;
    }

    public void setDeviceConnected(boolean deviceConnected) {
        isDeviceConnected = deviceConnected;
    }

    public CZWrapperBluetoothDevice getCzWrapperBluetoothDevice() {
        return czWrapperBluetoothDevice;
    }

    public void setCzWrapperBluetoothDevice(CZWrapperBluetoothDevice czWrapperBluetoothDevice) {
        this.czWrapperBluetoothDevice = czWrapperBluetoothDevice;
    }

    public List<BluetoothGattService> getBluetoothGattServiceList() {
        return bluetoothGattServiceList;
    }
}

设备封装类的管理类

/**
 * 管理设备上下文
 */
public class BleDeviceContextManager {
    private static final String TAG = "BleDeviceContextManager";
    private static final BleDeviceContextManager ourInstance = new BleDeviceContextManager();
    private Map<String,BleDeviceContext> bleDeviceContextMap;
    public static BleDeviceContextManager getInstance() {
        return ourInstance;
    }

    private BleDeviceContextManager() {
        bleDeviceContextMap =new HashMap<>();
    }

    public void release(){
        //断开连接,释放资源。
        List<BleDeviceContext> connectedBleDeviceContextList = findConnectedBleDeviceContextList();
        for (BleDeviceContext item : connectedBleDeviceContextList){
            item.disconnect();
        }
        this.bleDeviceContextMap.clear();
    }

    /**
     * 根据mac地址获取设备上下文
     * @param deviceId
     * @return
     */
    public BleDeviceContext findBleDeviceContext(String deviceId){
        Logger.e(TAG, "findBleDeviceContext: " + deviceId );
        return bleDeviceContextMap.get(deviceId);
    }

    /**
     * 获取已经连接的设备
     * @return
     */
    public List<BleDeviceContext> findConnectedBleDeviceContextList(){
        List<BleDeviceContext> result = new ArrayList<>();
        for (Iterator<Map.Entry<String, BleDeviceContext>> it = bleDeviceContextMap.entrySet().iterator(); it.hasNext();){
            Map.Entry<String, BleDeviceContext> item = it.next();
            if(item.getValue().isDeviceConnected()){
                result.add(item.getValue());
            }
        }
        return result;
    }


    /**
     * 根据mac地址创建设备上下文
     * @param deviceId
     * @return
     */
    public BleDeviceContext createBleDeviceContext(String deviceId){
        BleDeviceContext bleDeviceContext = findBleDeviceContext(deviceId);
        if(bleDeviceContext != null){
            Logger.e(TAG, "deviceId = " + deviceId + ",已经创建上下文对象,请勿重复创建。" );
            return null;
        }
        BleDeviceContext result = new BleDeviceContext();
        bleDeviceContextMap.put(deviceId,result);
        return result;
    }

    /**
     * 根据mac地址移除设备上下文
     * @param deviceId
     * @return
     */
    public void removeBleDeviceContext(String deviceId){
        BleDeviceContext bleDeviceContext = findBleDeviceContext(deviceId);
        if(bleDeviceContext == null){
            Logger.e(TAG, "deviceId = " + deviceId + ",上下文对象不存在,就别移除了。" );
            return ;
        }
        bleDeviceContextMap.remove(deviceId);
    }
}

服务端的实现,会在后续博客中持续更新,敬请关注。

如有疑惑或者好的建议,或者想纠正作者的博文,请联系如下
公众号:微信公众号搜索“修符道人”或者扫描下方二维码
qrcode_for_gh_97ef1213cc5b_258.jpg

微信号:XinYi1349308479
QQ邮箱:1349308479@qq.com

分类: 蓝牙

0 条评论

发表回复

您的电子邮箱地址不会被公开。