Android开发之Ble(Bluetooth low energy)初识

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_35189116/article/details/81033340

看见标题小伙伴们大概能看出我今天要说的是什么了!由于近来公司项目中有关于Ble方面的需求,对于Bluetooth方面的相关知识,因为这种需求工作中不多(你如果从事于智能硬件相关的岗位那另说),所以之前我也只是写过一些小demo(还是2.0的),没有实战过。再加上这次用的是4.0,所以我也是急忙的涉猎了一波。。那么对于蓝牙的前世今生这里我就不多废话,大家有兴趣可以直接度娘,蓝牙在Android系统中总的来说分为两大阶段;一个是经典蓝牙时期(在1.x~3.0之间),后面到现在的话就是比较流行的Ble全称为Bluetooth low energy也就是低功耗蓝牙它的版本是从4.x开始,当然5.0的蓝牙模块目前Google虽已发布,但是市场上的手机还远未普及(ps: Android之父Andy Rubin推出的Essential Phone已经配备),但是从各方面看它也绝对是最强的蓝牙版本了!在真正coding之前,我们有必要清楚ble工作方式

一。Ble的区别与优势

首先低功耗蓝牙较传统蓝牙,传输速度更快,覆盖范围更广,安全性更高,延迟更短,耗电极低等等优点。这也是为什么近年来智能穿戴的东西越来越多,越来越火。还有传统蓝牙与低功耗蓝牙通信方式也有所不同,传统的一般通过socket方式,而低功耗蓝牙是通过Gatt协议来实现。现在我们所处在一个蓝牙4.x的时代。在此之前如我们经常使用的蓝牙耳机就已经跨越了好多的版本 蓝牙耳机有v1.1 v1.2 v2.0 v2.1…等诸多版本,究竟有什么样的优势呢?当然是版本越高信号越好,2.0以上支持蓝牙立体声。那么我们现在所处的4.x的设备大多是属于主从模式的。什么是主从模式?一个主设备比如手机,一个从设备(这里也称为子设备或从机)主动搜索从机 可以发送 也可以接收, 从机也可以发送和接收 但只能被搜索。

二。BLE设备有什么东西

一个BLE终端可以包含多个Service(服务)一个Service可以包含多个Characteristic(特征)一个Characteristic包含一个value和多个Descriptor(描述符),一个Descriptor包含一个Value。我们要注意的是,每一个Service、Characteristic都会有一个uuid,这是一个唯一值,我们接下来的传输数据,将用到这个。每一个Characteristic都有一个Value,我们就是通过改变这个值,来对设备进行交互的。举个生活中的小例子就是你去学校找一个学生,前提是得知道哪个班级,这里学校就相当于一个bluetoothdevice,班级则是BluetoothGattService而学生就是BluetoothGattCharacteristic,大家可能发现我这些名词基本都包含了BluetoothGatt,没错这就是ble通讯的关键,我们可以把它看成Android手机与BLE终端设备建立通信的一个管道,只有有了这个管道,我们才有了通信的前提。

三,FastBle的简单使用

前面铺垫了这么多,我想大家会对Ble有一个基本认知,好了,接下来我将介绍一下FastBle(Github ble开源框架排名首位)的基本使用(前人种树,后人乘凉,如果有好的框架我们当然要学会使用,毕竟在这个时间就是金钱的时代,效率非常重要,当然是在对基础了解的情况下),首先为了对作者的尊敬,在这里贴出项目地址:https://github.com/Jasonchenlijian/FastBle 欢迎大家给作者star,fork

限于文章篇幅,这里只会介绍一小部分常用的api,如果大家想详细了解,我这里给上作者的中文文档地址供大家学习https://github.com/Jasonchenlijian/FastBle/wiki

好了,接下来我将贴出我的代码一起学习:  首先在项目module的build.gradle文件里添加

implementation 'com.clj.fastble:FastBleLib:2.3.2'

这种写法是AS3.0之后Google 推荐的大家少部分人可能看着陌生 ,随后配置相关权限(注意这里有很多坑)

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

这第一和第二个权限呢是蓝牙必备的,而后面两种(位置权限)则在6.0以上的系统中需要加入,否则会搜索不到,随后你还需要在代码中动态申请该权限,和定位(这里我就不啰嗦了,Android6.0动态申请权限大家应该比较熟悉了)

BleScanRuleConfig scanRuleConfig = new BleScanRuleConfig.Builder()
        .setScanTimeOut(5000)              // 扫描超时时间,可选,默认10秒
        .build();
BleManager.getInstance().init(getApplication());
BleManager.getInstance().initScanRule(scanRuleConfig);
BleManager.getInstance()
        .enableLog(true)
        .setReConnectCount(1, 8000)
        .setOperateTimeout(8000);

这段代码是FastBle的全局配置(是否运行日志,设置连接时重连次数和重连间隔(毫秒),默认为0次不重连,等等)

//初始化蓝牙适配器
final BluetoothManager bluetoothManager =
        (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
if (mBluetoothAdapter == null) {
    Toast.makeText(this, "您的设备不支持蓝牙BLE,将关闭", Toast.LENGTH_SHORT).show();
    finish();
}

这前面这些代码是为真正开始搜索蓝牙设备做个基调,好了现在我们就开始搜索了,

private void startScan() {

    BleManager.getInstance().scan(new BleScanCallback() {
        // 开始扫描(主线程)
        @Override
        public void onScanStarted(boolean success) {
            BleSearchData.clear();
            adapter2.notifyDataSetChanged();
            ivRefreshLogo.startAnimation(refreshAnimation());

        }

        @Override
        public void onLeScan(BleDevice bleDevice) {
            super.onLeScan(bleDevice);
        }

        @Override
        public void onScanning(BleDevice bleDevice) {
            // 扫描到一个符合扫描规则的BLE设备(主线程)
            if (!TextUtils.isEmpty(bleDevice.getName())) {
                int i = bleDevice.getName().length();
                String startStr = bleDevice.getName().substring(0, 1);
                if (i == 11 && startStr.equals("K")) {
                    BleSearchData.add(bleDevice);
                    adapter2.notifyDataSetChanged();
                }
            }
        }

        @Override
        public void onScanFinished(List<BleDevice> scanResultList) {
            // 扫描结束,列出所有扫描到的符合扫描规则的BLE设备(主线程)
            ivRefreshLogo.clearAnimation();
            if (PairedData.size() > 0) {
                if (BleSearchData.size() > 0) {
                    for (int i = 0; i < PairedData.size(); i++) {
                        for (int y = 0; y < BleSearchData.size(); y++) {
                            if (PairedData.get(i).getMac().equals(BleSearchData.get(y).getMac())) {
                                PairedData.get(i).setmRssi(BleSearchData.get(y).getRssi());
                                break;
                            } else {

                               if(y==BleSearchData.size()-1){
                                   PairedData.get(i).setmRssi(0);
                               }
                            }
                        }
                    }
                    adapter.notifyDataSetChanged();
                } else {
                    //全部没有
                    for (int i = 0; i < PairedData.size(); i++) {
                        PairedData.get(i).setmRssi(0);
                    }
                    adapter.notifyDataSetChanged();
                }
            }
        }
    });
}

扫描完成后把设备数据填充列表即可(有木有感受到框架的强大之处,简化了很多操作),之后我们在列表点击的时候将其connect

BleManager.getInstance().connect(bleDevice, mCallback);

只需将该设备实体传入方法即可,其实核心就是这个callback回调,我们看看这里的流程

private BleGattCallback mCallback = new BleGattCallback() {
    @Override
    public void onStartConnect() {
        ivOneConnectState.setBackgroundResource(R.mipmap.line_loading);
        ivTwoConnectState.setBackgroundResource(R.mipmap.line_loading);
        ivOneConnectState.startAnimation(loadAnimation);
        ivTwoConnectState.startAnimation(loadAnimation);
        tvConnectDevice.setText("正在连接设备");
        tvConnectService.setText("正在连接设备服务");
    }

    @Override
    public void onConnectFail(BleDevice bleDevice, BleException exception) {
        ivOneConnectState.clearAnimation();
        tvConnectDevice.setText("连接设备失败");
        ivOneConnectState.setBackgroundResource(R.mipmap.line_fail);
        tvConnectDevice.setTextColor(getResources().getColor(R.color.color_default_red));
        ivTwoConnectState.clearAnimation();
        tvConnectService.setText("连接设备服务失败");
        ivTwoConnectState.setBackgroundResource(R.mipmap.line_fail);
        tvConnectDevice.setTextColor(getResources().getColor(R.color.color_default_red));
        btnOk.setText("重新连接设备");
        btnOk.setClickable(true);
        btnOk.setBackground(getResources().getDrawable(R.drawable.shape_confirm_blue));

    }

    @Override
    public void onConnectSuccess(BleDevice bleDevice, BluetoothGatt gatt, int status) {

        Intent intent = new Intent();
        intent.putExtra("cBle", bleDevice);
        intent.setAction(Constants.ACTION_CONNECT_BLE);
        sendBroadcast(intent);

        ivOneConnectState.clearAnimation();
        tvConnectDevice.setText("连接设备成功");
        ivOneConnectState.setBackgroundResource(R.mipmap.line_success);
        tvConnectDevice.setTextColor(getResources().getColor(R.color.color_device_leisure));

        ivTwoConnectState.clearAnimation();
        tvConnectService.setText("获取设备服务成功");
        ivTwoConnectState.setBackgroundResource(R.mipmap.line_success);
        tvConnectService.setTextColor(getResources().getColor(R.color.color_device_leisure));
        btnOk.setText("连接设备成功");
        btnOk.setClickable(false);
        btnOk.setBackground(getResources().getDrawable(R.drawable.shape_confirm_gray));
        //0000fee9-0000-1000-8000-00805f9b34fb
        mOKBleService = bleDevice;
        service = gatt.getService(UUID.fromString("0000fee9-0000-1000-8000-00805f9b34fb"));
        List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
        for (int i = 0; i < characteristics.size(); i++) {
            if ((characteristics.get(i).getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
                mNotifyCharacteristic = characteristics.get(i);
                receiveNotify(mOKBleService, mNotifyCharacteristic);
            } else if ((characteristics.get(i).getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) {
                mWriteCharacteristic = characteristics.get(i);
                goWrite(mOKBleService, mWriteCharacteristic);
            }
        }
    }

    @Override
    public void onDisConnected(boolean isActiveDisConnected, BleDevice bleDevice, BluetoothGatt gatt, int status) {
       // finish();
        Toast.makeText(BleDeviceDetailActivity.this, "设备断开连接", Toast.LENGTH_SHORT).show();
       /*if(isActiveDisConnected){
           Toast.makeText(BleDeviceDetailActivity.this, "设备断开", Toast.LENGTH_SHORT).show();
       }*/

    }
};

四个方法回调大家看这单词意思也能大概明白(第一个是onStartConnect开始连接,onConnectFail连接失败,onConnectSuccess连接成功,最后还有个断开连接onDisConnected),回调方法的生命周期与你的初始化有关系,也就是说如果你是在application里单例的话,那么回调将在你整个APP运行期间持续,前提是你不手动断开,好了,接下来才真正来到通信的环节,毫无疑问你得在连接成功时做这个, 

service = gatt.getService(UUID.fromString("0000fee9-0000-1000-8000-00805f9b34fb"));
List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
for (int i = 0; i < characteristics.size(); i++) {
    if ((characteristics.get(i).getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
        mNotifyCharacteristic = characteristics.get(i);
        receiveNotify(mOKBleService, mNotifyCharacteristic);
    } else if ((characteristics.get(i).getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0) {
        mWriteCharacteristic = characteristics.get(i);
        goWrite(mOKBleService, mWriteCharacteristic);
    }
}

前面我提到了Ble的通信首先是gatt ,根据它你得找到你需要的service,和需要的特征来读 写,收听操作,我记得刚开始自己做的时候,就一直纠结一个问题 ,一个蓝牙设备 有那么多service和特征我怎么知道到底找哪个呢,这里我告诉你,不要想太多,当然是找硬件要了,要啥,,,?  uuid.就是上面那一串字符,关于UUID,想了解清楚的话,大家自行解决。。这里可用看到每个特征都有自己的用途,一般来说就是这几种(write,read,notify)

我们来看看写操作:

BleManager.getInstance().write(
        bleDevice,
        bluetoothGattCharacteristic.getService().getUuid().toString(),
        bluetoothGattCharacteristic.getUuid().toString(),
        HexUtil.hexStringToBytes("AAABAC"),
        writeCallback);

这个方法需要你传入蓝牙设备实体类,service的uuid,特征的uuid,和你需要写的字符,最后去实现一个callback回调,我们来看看

private BleWriteCallback writeCallback = new BleWriteCallback() {

    @Override
    public void onWriteSuccess(final int current, final int total, final byte[] justWrite) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {

                //Toast.makeText(BleDeviceDetailActivity.this, "写入成功", Toast.LENGTH_SHORT).show();
            }
        });
    }

    @Override
    public void onWriteFailure(final BleException exception) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(BleDeviceDetailActivity.this, "写入失败", Toast.LENGTH_SHORT).show();
            }
        });
    }
};

两个方法分别为写入成功和失败,非常人性化有木有。如果写入成功后,蓝牙设备如果有回复的话,会在notify特征中接收

BleManager.getInstance().notify(
        bleDevice,
        bluetoothGattCharacteristic.getService().getUuid().toString(),
        bluetoothGattCharacteristic.getUuid().toString(),
        notifyCallback);

前提你的开启notify,传入的参数和上面一致,我们同样来看看callback

private BleNotifyCallback mnotifyCallback = new BleNotifyCallback() {
    @Override
    public void onNotifySuccess() {
        //接收成功
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
               Toast.makeText(BleDeviceDetailActivity.this, "接收成功", Toast.LENGTH_SHORT).show();
            }
        });
    }

    @Override
    public void onNotifyFailure(BleException exception) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                Toast.makeText(BleDeviceDetailActivity.this, "接收失败", Toast.LENGTH_SHORT).show();
            }
        });
    }

    @Override
    public void onCharacteristicChanged(final byte[] data) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                HexUtil.formatHexString(data);
                //拿到数据进行操作
            }
        });
    }
};

里面已经写的很直白了,重点关注onCharacteristicChanged方法,这里是我们拿数据的地方,这样我们就实现和ble设备的通信了,值得注意的是这三个回调都不是在主线程完成的,这点大家记住了,由于安卓当中不允许在UiThread中进行耗时的操作而且还是异步的!大家清楚性能优化的都知道,在Android中activity响应的时间不能超过5秒,广播是10秒,否则就会引起程序无响应进而崩溃,也就是ANR(Application Not Responding)错误,所以蓝牙操作都是异步的,也就是在小线程里发生的,而我们更新UI的话又必须回到mainthread,所以就用到了runOnUiThread方法,当然也可以用Handler(runOnUiThread方法底层实现其实就是handler),值得注意的是接收的数据是以字节数组的形式,所以你得转成你想要的类型,

好了!以上就是ble通信的全过程,当然一些细节大家在coding的时候自己多加注意就行!

猜你喜欢

转载自blog.csdn.net/qq_35189116/article/details/81033340