蓝牙Ble开发(支持API18<Android 4.3 Jelly>及以上)

  Android4.3(api18)开始支持蓝牙Ble(Bluetooth Low Energy)开发,到Android5.0(api21)开始修改了部分方法库,详细请看“蓝牙Ble开发(支持API21<Android 5.0 Jelly>及以上)”。

  https://note.youdao.com/ynoteshare1/index.html?id=4e9e1de1c3604df0f5b017eea73a19e1&type=note#/

一、注意

1、所需权限

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

 2、蓝牙Ble协议默认最大发送20字节(不包含头尾,包含头尾总共22字节);

二、遗留问题

1、蓝牙发送数据可以正常回调方法onCharacteristicWrite,蓝牙读数据无法正常回调方法onCharacteristicRead,但是可以正常回调方法onCharacteristicChanged,可以通过该方法获取蓝牙已读数据。原因未知。

三、代码详情

1、先判断蓝牙是否开启

     // 获取蓝牙管理器
        BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        // 获取蓝牙适配器
        BluetoothAdapter bAdapter = bluetoothManager.getAdapter();
        // 判断是否可用
        if(bAdapter.isEnabled())
        {
            // 蓝牙已打开
} else { // 弹出蓝牙打开对话框(系统) Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); startActivityForResult(intent, 1); }

 蓝牙选择结果:

@Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        // 蓝牙选择结果判断
        if (requestCode == 1)
        {
            switch (resultCode)
            {
                case 120:
                    // 蓝牙已打开
                    break;
                case 0:
                    // 蓝牙未打开
                    break;
            }
        }
    }

2、开始扫描(回调扫描结果方法)-- bAdapter.startLeScan(leScanCallback)返回device

 
/**
     * 开始扫描ble
     */
    boolean canReconntect = false;

    public void startLeScan() {
        // 开始扫描后允许重连,防止意外断开连接
        canReconntect = true;
        if (bAdapter != null) {
            boolean result = bAdapter.startLeScan(leScanCallback);
            if (result) {
                // LE开始扫描
            }
            Timer timer = new Timer();
            timer.schedule(new TimerTask() {
                @Override
                public void run() {
                    // 扫描10s后停止扫描
                    stopLenScan();
                }
            }, 10000);
        }
    }

    /**
     * 停止扫描
     */
    public void stopLenScan() {
        if (bAdapter != null) {
            bAdapter.stopLeScan(leScanCallback);
            LogUtils.instance().logI("LE扫描停止...");
        }
    }

    // 扫描回调方法
    private BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback()
    {
        @Override
        public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord)
        {
            // 扫描结果 device.getName()
            if (device.getName() != null && device.getName().equals("指定蓝牙名称"))
            {
                LogUtils.instance().logI("扫描到指定结果:" + device.getName());
                // 停止扫描
                stopLenScan();

                // 根据扫描到的蓝牙地址进行连接
                requestConnect(device);
                LogUtils.instance().logI("开始获取gatt...");
            }
        }
    };

3、建立链接 -- device.connectGatt(context, true, bluetoothGattCallback)返回gatt

/**
     *发起连接(供外部调用)
     * @param device:目标设备
     */
    public void requestConnect(BluetoothDevice device)
    {
        BluetoothGatt gatt = device.connectGatt(context, true, mGattCallback);

        // 为了防止重复的gatt,连接成功先检查是否有重复的,有则断开
        BluetoothGatt last = listBluetoothGatts.remove(device.getAddress());
        if(last != null)
        {
            last.disconnect();
            last.close();
        }
        // 添加当前gatt
        listBluetoothGatts.put(device.getAddress(), gatt);
    }


    /**
     * 断开连接
     * @param address
     */
    public synchronized void disconnect(String address)
    {
        if (null != listBluetoothGatts && listBluetoothGatts.containsKey(address))
        {
            BluetoothGatt gatt = listBluetoothGatts.remove(address);
            if (gatt != null)
            {
                gatt.disconnect();
                gatt.close();
                LogUtils.instance().logE("LE GATT释放成功," + gatt.getDevice().getName());
            }
        }
    }

    /**
     * 释放所有连接
     */
    public void disconnectAll()
    {
        // 手动停止后不需要重连
        canReconntect = false;
        if(null != listBluetoothGatts)
        {
            for(String address : listBluetoothGatts.keySet())
            {
                disconnect(address);
            }
        }
        LogUtils.instance().logI("已释放所有连接");
    }

4、发现服务 -- gatt.discoverServices()返回gatt

5、获取服务,并查找出与通信有关的服务 -- gatt.getServices()返回服务列表listBluetoothGattService

6、获取特征值(读、写) -- bluetoothGattService.getCharacteristics()返回bluetoothGattCharacteristic

/**
     * gatt连接状态回调
     */
    private BluetoothGattCallback mGattCallback = new BluetoothGattCallback()
    {

        /**
         * 连接状态改变回调
         * @param gatt Gatt服务对象
         * @param status 是否成功执行连接操作
         * @param newState 连接状态(已经连接、已经断开)
         */
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState)
        {
            super.onConnectionStateChange(gatt, status, newState);

            String address = gatt.getDevice().getAddress();
            // 成功执行连接操作
            if(status == BluetoothGatt.GATT_SUCCESS)
            {
                // 设备已连接
                if(newState == BluetoothProfile.STATE_CONNECTED)
                {
                    // 重置重连次数
                    reConnectCount = 0;
                    LogUtils.instance().logI("LE设备已连接");

                    // 发现服务进行通信(成功会回调方法onServicesDiscovered())
                    if(gatt != null)
                    {
                        // 发现服务成功后回调onServicesDiscovered()方法
                        if(gatt.discoverServices())
                        {
                            LogUtils.instance().logI( "LE发现服务正常");
                        }
                        else
                        {
                            LogUtils.instance().logE( "LE发现服务失败");
                        }
                    }
                }
                // 设备已断开
                else if(newState == BluetoothProfile.STATE_DISCONNECTED)
                {
                    // 先断开原有连接
                    disconnect(address);
                    LogUtils.instance().logE( "LE设备已断开");

                    // 重新连接
                    reConnectCount ++;
                    if(reConnectCount <= Constant.RE_CONNECT_COUNT && canReconntect)
                    {
                        requestConnect(gatt.getDevice());
                        LogUtils.instance().logI( "LE设备开始重新连接");
                    }
                    else
                    {
                        LogUtils.instance().logE( "LE设备重新连接次数已到上线;" + reConnectCount + "或已手动停止");
                    }
                }
            }
            else
            {
                LogUtils.instance().logE( "LE连接Gatt失败" + status + "//" + newState);
                // 如果断开则重新连接
                if(newState != BluetoothProfile.STATE_CONNECTED && canReconntect)
                {
                    // 先断开原有连接
                    disconnect(address);

                    reConnectCount ++;
                    if(reConnectCount <= Constant.RE_CONNECT_COUNT && canReconntect)
                    {
                        // 重新连接
                        requestConnect(gatt.getDevice());
                        LogUtils.instance().logI( "LE设备开始重新连接");
                    }
                    else
                    {
                        LogUtils.instance().logE( "LE设备重新连接次数已到上线;" + reConnectCount + "或已手动停止");
                    }
                }
            }
        }

        /**
         * 发现服务回调
         * @param gatt
         * @param status
         */
        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status)
        {
            super.onServicesDiscovered(gatt, status);
            LogUtils.instance().logI( "LE发现服务回调");

            // api21以上方法,以下不支持
//            boolean r = gatt.requestMtu(100);
//            LogUtils.instance().logE(r + "突破");

            if(status == BluetoothGatt.GATT_SUCCESS && gatt != null)
            {
                // 获取服务列表(包含通信的服务)
                listGattServices = gatt.getServices();
                for(BluetoothGattService s : listGattServices)
                {
                    if(s.getType() == BluetoothGattService.SERVICE_TYPE_PRIMARY && s.getUuid().toString().equals(Constant.UUID_SERVER.toLowerCase()))
                    {
                        // 找到指定通信服务(使用UUID匹配)
                        LogUtils.instance().logI( "LE发现蓝牙为主模式,UUID Service确认OK" + listGattServices.size());

                        // 给Servic、Gatt赋值,供主线程调用
                        bluetoothGattMain = gatt;

                        List<BluetoothGattCharacteristic> listCharacteristic = s.getCharacteristics();
                        for(BluetoothGattCharacteristic c : listCharacteristic)
                        {
                            // 写入数据(蓝牙工作模式)
                            if(c.getUuid().toString().equals(Constant.UUID_RECEIVE.toLowerCase()))
                            {
                                // 给Servic、Gatt赋值,供主线程调用
                                bluetoothGattCharacteristicWriteMain = c;
                            }
                            else if(c.getUuid().toString().equals(Constant.UUID_TRANS.toLowerCase()))
                            {
                                // 给Servic、Gatt赋值,供主线程调用
                                bluetoothGattCharacteristicReadMain = c;
                            }

                            // 扫描到蓝牙设备
                            reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("listenerBluetoothState", "蓝牙已连接");
                        }

                    }
                    else if(s.getType() == BluetoothGattService.SERVICE_TYPE_SECONDARY)
                    {
                        LogUtils.instance().logI( "LE发现蓝牙为从模式");
                    }
                }
            }
        }

        /**
         * 数据接收回调(未回调,待确认...)
         * @param gatt
         * @param characteristic
         * @param status
         */
        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicRead(gatt, characteristic, status);
            LogUtils.instance().logI( "onCharacteristicRead");
            if(status == BluetoothGatt.GATT_SUCCESS)
            {
                LogUtils.instance().logI( "LE蓝牙数据读入成功回调:");
            }
            else
            {
                LogUtils.instance().logE( "LE蓝牙数据读入失败回调:");
            }
        }

        /**
         * 数据发送回调(OK)
         * @param gatt
         * @param characteristic
         * @param status
         */
        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status)
        {
            super.onCharacteristicWrite(gatt, characteristic, status);
            LogUtils.instance().logI( "LE蓝牙模式发送成功回调:" +  characteristic.getValue().toString());
        }


        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic)
        {
            super.onCharacteristicChanged(gatt, characteristic);
//            LogUtils.instance().logI( "LE  onCharacteristicChanged:" + characteristic.getValue().toString());
            getDataFromCharacteristic(characteristic);
        }

        /**
         *
         * @param gatt
         * @param descriptor
         * @param status
         */
        @Override
        public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            super.onDescriptorRead(gatt, descriptor, status);
        }

        @Override
        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            super.onDescriptorWrite(gatt, descriptor, status);
        }
    };

7、使用对应的进行读写操作

蓝牙读写操作尽量放在主线程中,所以这里需要发送广播,在广播接收器中进行蓝牙读写操作,

    /**
     * 发送通知
     */
    public void startNotification()
    {
        Intent iWrite = new Intent();
        iWrite.setAction(Constant.BLE_WRITE);
        context.sendBroadcast(iWrite);

        // 发送广播开始从蓝牙读数据
        Intent iRead = new Intent();
        iRead.setAction(Constant.BLE_READ);
        context.sendBroadcast(iRead);
    }

广播接收器接收消息,进行蓝牙读写操作:

public class BluetoothBroadcast extends BroadcastReceiver
{
    @Override
    public void onReceive(Context context, Intent intent)
    {

        if(intent.getAction().equals(Constant.BLE_WRITE))
        {
            LogUtils.instance().logE("广播接收器收到消息了");
//            LogUtils.instance().logE("广播接收器:" + BluetoothBleUtils.instance().bluetoothGatt.getDevice().getName() + "///" + Thread.currentThread().getName());

            BluetoothGattCharacteristic characteristicWrite = BluetoothBleLowUtils.instance().bluetoothGattCharacteristicWriteMain;
            characteristicWrite.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
            characteristicWrite.setValue(Constant.BLUETOOTH_MODE);

            BluetoothGatt gatt = BluetoothBleLowUtils.instance().bluetoothGattMain;
            gatt.setCharacteristicNotification(characteristicWrite, true);

            boolean result = gatt.writeCharacteristic(characteristicWrite);

            LogUtils.instance().logE("广播接收器:开始写了" + result);
        }

        if(intent.getAction().equals(Constant.BLE_READ))
        {
            LogUtils.instance().logE("广播接收器:开始读了");
            BluetoothGattCharacteristic characteristicRead = BluetoothBleLowUtils.instance().bluetoothGattCharacteristicReadMain;

            BluetoothGatt gatt = BluetoothBleLowUtils.instance().bluetoothGattMain;
            gatt.setCharacteristicNotification(characteristicRead, true);

            for(BluetoothGattDescriptor descriptor : characteristicRead.getDescriptors())
            {
                descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                boolean b = gatt.writeDescriptor(descriptor);
            }
//            boolean result = gatt.readCharacteristic(characteristicRead);
            LogUtils.instance().logE("广播接收器:开始读了");
        }
    }
}
原文地址:https://www.cnblogs.com/bluejump/p/10143076.html