Android蓝牙4.0Ble开发

    现在随着智能化潮流的到来,智能设备越来越多,而其中很多都使用的ble技术进行通讯,很多android开发人员会接触到ble开发。我是去年开始接触ble开发的,那时候百度基本没什么资料,苦逼的我只能上谷歌,踩了不少坑,所以现在就把我所学到的东西记录下来,方便以后查询。

    现在手机APP连接ble设备基本使用的是主模式,即手机作为主机(中心),ble设备作为从机(外围),手机主动发起连接到ble设备,安卓5.0之后开始支持从模式,这篇文章的是主模式(中心),从模式(外围)请移步:Android Ble从模式(Peripheral)开发

    首先,ble设备与手机的通讯过程大概是:ble设备发出广播并提供用于通讯的服务和特征字-->手机开启蓝牙进行扫描扫描到ble设备之后发起连接-->连接完成之后通过ble设备提供的用于通讯的服务和特征字开始收发数据

    今天先讲手机如何开启蓝牙进行扫描并连接。

    第一步,先检查蓝牙是否打开,没有则打开

    /**
     * 检测蓝牙是否打开
     */
    void openBluetoothScanDevice() {
        if (!BluetoothAdapter.getDefaultAdapter().isEnabled()) {
            //蓝牙没打开则去打开蓝牙
            boolean openresult = toEnable(BluetoothAdapter.getDefaultAdapter());
            if(!openresult){
                Toast.makeText(MainActivity.this, "打开蓝牙失败,请检查是否禁用了蓝牙权限",Toast.LENGTH_LONG).show();
                return;
            }
            //停个半秒再检查一次
            SystemClock.sleep(500);
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    int i = 0;
                    while (!BluetoothAdapter.getDefaultAdapter().isEnabled()){
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        if (i >= 15){
                            Toast.makeText(MainActivity.this, "打开蓝牙失败,请检查是否禁用了蓝牙权限",Toast.LENGTH_LONG).show();
                            break;
                        }else{
                            i++;
                        }

                    }
                    //发现蓝牙打开了,则进行开启扫描的步骤
                    scanDevice();
                }
            });
        } else {
            //检查下当前是否在进行扫描 如果是则先停止
            if (mBle != null && mScanning){
                mBle.stopScan();
            }
            scanDevice();
        }
    }

    前面的代码注释很齐全,就不多讲了,就是确定蓝牙是开启状态之后就去开启ble扫描,接下上开启扫描的代码,开关扫描是同一句方法scanLeDevice,由参数来决定是开还是关

  @UiThread
    void scanDevice() {
        //如果此时发蓝牙工作还是不正常 则打开手机的蓝牙面板让用户开启
        if (mBle != null && !mBle.adapterEnabled()) {
            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
        }

        myhandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                //检查一下去那些,如果没有则动态请求一下权限
                requestPermission();
                //开启扫描
                scanLeDevice(true);
            }
        },500);
    }
    private void scanLeDevice(final boolean enable) {
        //获取ble操作类
        mBle = AppContext.getInstance().getmBle();
        if (mBle == null) {
            return;
        }
        if (enable) {
            //开始扫描
            if (mBle != null) {
                boolean startscan = mBle.startScan(resultCallback);
                if (!startscan){
                    Toast.makeText(MainActivity.this, "开启蓝牙扫描失败,请检查蓝牙是否正常工作!",Toast.LENGTH_LONG).show();
                    return;
                }
                mScanning = true;
                //扫描一分钟后停止扫描
                myhandler.postDelayed(stopRunnable,SCAN_PERIOD);
            }
        } else {
            //停止扫描
            mScanning = false;
            if (mBle != null) {
                mBle.stopScan();
                myhandler.removeCallbacksAndMessages(null);
            }
        }

    }

    这块代码我们用到了一个mBle(BleHelper)的stopScan、startScan、adapterEnabled方法和一个resultCallback(BleScanResultCallback),这是我自己封装的ble操作类和callback

public interface BleScanResultCallback {
    void onSuccess();
    void onFail();
    void onFindDevice(BluetoothDevice device, int rssi,
                      byte[] scanRecord);
}

    

/**
 * Ble操作类
 * Created by xu on 2017/5/25.
 */
public class BleHelper {

    private final static String TAG = "BleHelper";

    /**
     * 扫描结果的callback
     */
    private BleScanResultCallback resultCallback;
    /**
     * 设备连接状态监听的callback
     */
    private BleConnectionCallback connectionStateCallback;
    /**
     * 蓝牙的适配器
     */
    private BluetoothAdapter mBtAdapter;
    /**
     * 上下文
     */
    private Context context;
    /**
     * gatt的集合
     */
    private Map<String, BluetoothGatt> mBluetoothGatts;
    /**
     * 重连的次数
     */
    int reconnectCount = 0;
    /**
     * 是否进行重连
     */
    private boolean canreconntect = false;
    /**
     * gatt连接callback
     */
    private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {

        @Override
        public void onConnectionStateChange(final BluetoothGatt gatt, int status,
                                            int newState) {
            super.onConnectionStateChange(gatt, status, newState);
            Log.e(TAG, "onConnectionStateChange()       status:" + status + "    newState:" + newState);
            String address = gatt.getDevice().getAddress();
            if (status == BluetoothGatt.GATT_SUCCESS) {
                //如果是主动断开的 则断开
                if (newState == BluetoothProfile.STATE_CONNECTED) {

                    reconnectCount = 0;
                    Log.e(TAG, "连接成功");
                    if(connectionStateCallback != null){
                        connectionStateCallback.onConnectionStateChange(status,newState);
                    }

                } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                    disconnect(address);
                    Log.e(TAG, "断开成功");
                    if(connectionStateCallback != null){
                        connectionStateCallback.onConnectionStateChange(status,newState);
                    }
                }
            }else{
                //如果是收到莫名其妙的断开状态的话 则重连一次
                if (newState != BluetoothProfile.STATE_CONNECTED && canreconntect) {
                    //先断开原有的连接
                    disconnect(address);
                    if(reconnectCount >= 2){
                        //重连三次不成功就失败
                        if(connectionStateCallback != null){
                            connectionStateCallback.onConnectionStateChange(status,newState);
                        }
                        reconnectCount = 0;
                        canreconntect = false;
                        return;
                    }
                    //再次重新连接
                    boolean connect_resule = requestConnect(address,connectionStateCallback,true);
                    reconnectCount++;
                    Log.e(TAG,"正在尝试重新连接:"+connect_resule);
                }else{
                    if(connectionStateCallback != null){
                        connectionStateCallback.onConnectionStateChange(status,newState);
                    }
                    disconnect(address);

                }

            }


        }
    };

    /**
     * 扫描ble设备的callback
     */
    private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
        @Override
        public void onLeScan(final BluetoothDevice device, int rssi,
                             byte[] scanRecord) {
            if(resultCallback != null){
                resultCallback.onFindDevice(device,rssi,scanRecord);
            }

        }
    };

    public BleHelper() {

    }
    /**
     *初始化ble操作类
     * @param context:上下文  建议用持久的
     */
    public boolean init(Context context) {
        this.context = context;
        final BluetoothManager bluetoothManager = (BluetoothManager) context
                .getSystemService(Context.BLUETOOTH_SERVICE);
        mBtAdapter = bluetoothManager.getAdapter();
        if (mBtAdapter == null) {
            return false;
        }
        mBluetoothGatts = new HashMap<String, BluetoothGatt>();
        return true;
    }
    /**
     *开启扫描
     * @param resultCallback:扫描结果的calllback
     */
    public boolean startScan(BleScanResultCallback resultCallback) {
        this.resultCallback = resultCallback;
        try {
            return mBtAdapter.startLeScan(mLeScanCallback);
        } catch (Exception e) {
            e.printStackTrace();
            Log.d(TAG, "使用BLE扫描API开始扫描出错了 错误:" + e.getMessage());
            return false;
        }

    }
    /**
     *停止扫描
     */
    public void stopScan() {
        try {
            mBtAdapter.stopLeScan(mLeScanCallback);
            resultCallback = null;
        } catch (Exception e) {
            e.printStackTrace();
            Log.d(TAG, "使用的BLE扫描API停止扫描出错了 错误:" + e.getMessage());
        }
    }
    /**
     *断开连接
     */
    public synchronized void disconnect(String address) {
        if (mBluetoothGatts.containsKey(address)) {
            BluetoothGatt gatt = mBluetoothGatts.remove(address);
            if (gatt != null) {
                gatt.disconnect();
                gatt.close();
                Log.e(TAG, "执行到了设备断开的指令");
            }
        }
    }
    /**
     *发起连接(供外部调用)
     * @param address:目标设备的address地址
     * @param connectionStateCallback:设备连接状态的callback
     * @param canreconntect:是否启用重连机制 true:重连三次 false:不进行重连
     */
    public boolean requestConnect(String address,BleConnectionCallback connectionStateCallback,boolean canreconntect) {
        this.connectionStateCallback = connectionStateCallback;
        this.canreconntect = canreconntect;
        BluetoothGatt gatt = mBluetoothGatts.get(address);
        if (gatt != null && gatt.getServices().size() == 0) {
            return false;
        }
        return connect(address);
    }
    /**
     *连接到设备
     * @param address:目标设备的address地址
     */
    private boolean connect(String address) {
        BluetoothDevice device = mBtAdapter.getRemoteDevice(address);
        BluetoothGatt gatt = device.connectGatt(context, false, mGattCallback);
        //BluetoothGatt gatt = device.connectGatt(mService, false, mGattCallback, BluetoothDevice.TRANSPORT_BREDR | BluetoothDevice.TRANSPORT_LE);

        //为了防止重复的gatt,连接成功先检查是否有重复的,有则断开
        BluetoothGatt last = mBluetoothGatts.remove(address);
        if(last != null){
            last.disconnect();
            last.close();
        }
        if (gatt == null) {
            mBluetoothGatts.remove(address);
            return false;
        } else {
            // TODO: if state is 141, it can be connected again after about 15
            // seconds
            mBluetoothGatts.put(address, gatt);
            return true;
        }
    }
    /**
     *蓝牙适配器是否正常
     */
    public boolean adapterEnabled() {
        if (mBtAdapter != null) {
            return mBtAdapter.isEnabled();
        }
        return false;
    }
}

    到这里就可以扫描到ble设备了。6.0之后,要扫描到设备还需要定位权限,部分手机还需要打开GPS定位才能扫描到手机。

    扫描到了之后我们需要对其建立起连接,我们可以通过扫描的时候得到的目标设备的BluetoothDevice类的getAddress()方法得到目标设备的address,然后调用ble操作类的requestConnect方法建立连接(根据设备不同,有的设备连接时间会有那么一点点长),此处的BleConnectionCallback也是我自己封装的一个连接callback。

public interface BleConnectionCallback {
    void onConnectionStateChange(int status, int newState);
    void onFail(int errorCode);
}

    到了这里,ble设备的扫描和连接就完成了。

    通讯建立了,接下来讲讲如何进行数据的收发,传统蓝牙用的是socket来进行通讯,而ble则不大一样,使用的是characteristic。

    characteristic的属性有三种:可读、可写、可通知。一个characteristic可以同时具备多种属性。如果你要发送数据,就只能通过拥有可写属性的characteristic发送出去。要接收数据记得通过拥有可读或可通知属性的的characteristic接收。

    我们先讲如何发送数据,首先你得先得到用于发送数据的BluetoothGattCharacteristic,而BluetoothGattCharacteristic是属于ble的service(跟安卓的service不是一个概念)下的,你可以先通过service的UUID先去获取到service实例,再通过BluetoothGattCharacteristic的UUID去从service实例下获取对应的BluetoothGattCharacteristic。UUID这个设备帮都会主动告诉你的,如果没有,请去找他们要。

    

            bleGattCharacteristicOthers =
                    mBle.getService(deviceAddress,
                            UUID.fromString(BleBase.BLE_SERVICE_UUID)).getCharacteristic(
                            UUID.fromString(BleBase.BLE_CHARACTERISTIC_UUID));

    获取到了characteristic之后,把你要发送的数据通过BluetoothGattCharacteristic.setValue(byte[] data)方法填到characteristic里面,然后调用BluetoothGatt.writeCharacteristic(BluetoothGattCharacteristic mGattCharacteristicWrite)方法传输到设备。ble通讯传递的也是16进制数据。

 /**
     *写数据
     * @param address:目标设备的address
     * @param data:要发送的内容
     */
    public boolean requestWriteCharacteristic(String address,byte[] data) {
        BluetoothGatt gatt = mBluetoothGatts.get(address);
        if (gatt == null || mGattCharacteristicWrite == null) {
            if (gatt == null) {
                Log.e(TAG, "发送BLE数据失败:gatt = null");
            }

            if (mGattCharacteristicWrite == null) {
                Log.e(TAG, "发送BLE数据失败:characteristic = null");
            }
            return false;
        }

        Log.d(TAG, "data:" + CodeUtil.bytesToString(mGattCharacteristicWrite.getValue()));
        boolean result = false;
        try {
            mGattCharacteristicWrite.setValue(data);
            result = gatt.writeCharacteristic(mGattCharacteristicWrite);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return result;
    }

    发送成功与否会调用BluetoothGattCallback的onCharacteristicWrite返回。

        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt,
                                          BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicWrite(gatt, characteristic, status);
            Log.e(TAG, "onCharacteristicWrite()");
            if (status != BluetoothGatt.GATT_SUCCESS) {
                Log.e(TAG, "onCharacteristicWrite()检测到不可以写数据    status:" + status);
                return;
            }
            Log.e(TAG, "onCharacteristicWrite()检测到可以去写数据了");
            Log.d("写出的数据:", CodeUtil.bytesToString(characteristic.getValue()));
        }

    此时我们数据发送出去,接下来讲一讲怎么接收设备端发送过来的数据。一般接收数据有两种发送,一是通过拥有可读属性的BluetoothGattCharacteristic去主动读取,二是通过拥有可通知属性的BluetoothGattCharacteristic去被动接收。现在基本使用的都是第二种,第一种使用场景有限。所以我们来讲讲第二种方式怎么做。

    使用UUID获取到对应的service以及BluetoothGattCharacteristic前面已经讲了,那样要接收到拥有可通知属性发送过来的数据,首先先要订阅一下:BluetoothGatt.setCharacteristicNotification(BluetoothGattCharacteristic characteristic,boolean enable) 。enable表示是否接收此BluetoothGattCharacteristic的通知。

    订阅完成之后还需要写一下描述者BluetoothGatt.writeDescriptor(BluetoothGattDescriptor descriptor),此时便可以接收到ble设备发送过来的数据了。

    /**
     * 订阅并写描述者  (指定UUID的service下的所有Notification的特征字)
     */
    public boolean characteristicNotification(String uuid){
        BluetoothGatt gatt = mBluetoothGatts.get(deviceAddress);
        if (gatt == null) {
            Log.e(TAG, "BluetoothAdapter not initialized");
            return false;
        }
        BluetoothGattService service = getService(UUID.fromString(uuid));
        if(service == null){
            Log.e(TAG, "service is null");
            return false;
        }
        List<BluetoothGattCharacteristic> gattCharacteristics = service.getCharacteristics();
        for (int j = 0; j < gattCharacteristics.size(); j++) {
            BluetoothGattCharacteristic chara = gattCharacteristics.get(j);
            //判断是否有可通知的特征字,有则进行订阅写描述者
            if ( (chara.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
                boolean b = gatt.setCharacteristicNotification(chara, true);
                BluetoothGattDescriptor descriptor = chara.getDescriptor(DESC_CCC);
                Log.e(TAG, "订阅结果:" + b + "        characteristic:" + chara.getUuid());
                if (descriptor == null) {
                    return false;
                }
                byte[] val_set = null;
                val_set = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
                if (!descriptor.setValue(val_set)) {
                    Log.e(TAG, "descriptor.setValue失败" );
                    return false;
                }
                boolean f = gatt.writeDescriptor(descriptor);
                Log.e(TAG, "写描述者结果:" + f + "        characteristic:" + chara.getUuid());
            }
            try {
                Thread.sleep(150);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
        return true;
    }


    ble设备发送过来的数据时,底层会调用BluetoothGattCallback的onCharacteristicChanged方法,我们可以在这里获取到ble设备发送过来的数据。

@Override
        public void onCharacteristicChanged(BluetoothGatt gatt,
                                            BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicChanged(gatt, characteristic);
            Log.e(TAG, "onCharacteristicChanged()");
            String address = gatt.getDevice().getAddress();
            long starttime = System.currentTimeMillis();
            byte[] bytes = characteristic.getValue();
            if(dataCallback != null){
                dataCallback.onGetData(characteristic.getUuid().toString(),bytes);
            }
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < bytes.length; i++) {
                sb.append(String.format("%02x", bytes[i]));
                Log.d(TAG," time :" + starttime + "     bytes:" + i + "     =" + Integer.toHexString(bytes[i]));
            }
            Log.d(TAG, "出来的数据:" + sb.toString());

        }

    
    好了,android的Ble通讯到这里就基本通了。就可以跟ble设备正常的收发数据了

    什么时候有时间我再整理成一个框架出来。下面附上我写的一个小demo,虽然简陋,但是有关ble的操作我都封装到BleHelper类里面了,其他项目要用的话直接拿去用就好了,本来打算整理成一个框架的,现在想想其实也没不要了。

demo

demo的下载我之前没设置下载积分的,不知道为什么现在变成30了,如果没积分的同学直接百度网盘下吧。

链接:https://pan.baidu.com/s/17kF9APsZMDjgfmEjn42oZw 密码:a45z

    

猜你喜欢

转载自blog.csdn.net/a287574014/article/details/72724250