Android ble蓝牙开发介绍以及遇到的坑2

Android ble蓝牙开发

BLE介绍

安卓4.3(API 18)为BLE的核心功能提供平台支持和API,App可以利用它来发现设备、查询服务和读写特性。相比传统的蓝牙,BLE更显著的特点是低功耗。这一优点使Android App可以与具有低功耗要求的BLE设备通信,如近距离传感器、心脏速率监视器、健身设备等。

BLE开发

BLE权限添加

为了在app中使用蓝牙功能,必须声明蓝牙权限BLUETOOTH。利用这个权限去执行蓝牙通信,例如请求连接、接受连接、和传输数据。如果想让你的app启动设备发现或操纵蓝牙设置,必须声明BLUETOOTH_ADMIN权限。注意:如果你使用BLUETOOTH_ADMIN权限,你也必须声明BLUETOOTH权限。在你的app manifest文件中声明蓝牙权限。

设置BLE

你的app能与BLE通信之前,你需要确认设备是否支持BLE,如果支持,确认已经启用。虽然现在的手机基本都支持BLE,但是考虑到程序的健硕性,如果设置为false,这个检查是必需的。

BluetoothAdapter类介绍

获取:所有的蓝牙活动都需要蓝牙适配器。BluetoothAdapter代表设备本身的蓝牙适配器(蓝牙无线)。整个系统只有一个蓝牙适配器,而且你的app使用它与系统交互。下面的代码片段显示了如何得到适配器。注意该方法使用getSystemService()返回BluetoothManager,然后将其用于获取适配器的一个实例。Android 4.3(API 18)引入BluetoothManager

// 初始化蓝牙适配器
final BluetoothManager bluetoothManager =

(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);

mBluetoothAdapter = bluetoothManager.getAdapter();

有了mBluetoothAdapter之后就可以判断当前蓝牙开关状态、蓝牙未开启情况下代码里面自动开启蓝牙、以及扫描周边的ble设备

开启蓝牙

接下来,你需要确认蓝牙是否开启。调用isEnabled()去检测蓝牙当前是否开启。如果该方法返回false,蓝牙被禁用。下面的代码检查蓝牙是否开启,如果没有开启,可以提示用户去设置开启蓝牙。

// 确保蓝牙在设备上可以开启

if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
    //蓝牙未开启
}

发现BLE设备

为了发现BLE设备,使用startLeScan()方法。这个方法需要一个参数BluetoothAdapter.LeScanCallback。你必须实现它的回调函数,那就是返回的扫描结果。因为扫描非常消耗电量,你应当遵守以下准则:

1·只要找到所需的设备,停止扫描。

2·不要在循环里扫描,并且对扫描设置时间限制。以前可用的设备可能已经移出范围,继续扫描消耗电池电量。

以下代码显示如何扫描设备和停止扫描设备

// 10秒后停止寻找.

private static final long SCAN_PERIOD = 10000;

private void scanLeDevice(final boolean enable) {
    if (enable) {
        // 经过预定扫描期后停止扫描
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                mScanning = false;
                mBluetoothAdapter.stopLeScan(mLeScanCallback);
            }

        }, SCAN_PERIOD);
        mScanning = true;
        mBluetoothAdapter.startLeScan(mLeScanCallback);
    } else {
    mScanning = false;
    //停止扫描
    mBluetoothAdapter.stopLeScan(mLeScanCallback);
    }
}

如果你只想扫描指定类型的外围设备,可以改为调用startLeScan(UUID[], BluetoothAdapter.LeScanCallback),需要提供你的app支持的GATT servicesUUID对象数组。

扫描的信息在LeScallCallback里面返回

private BluetoothAdapter.LeScanCallback mLeScanCallback =

    new BluetoothAdapter.LeScanCallback() {
    
        //device 里面包含设备的mac地址和设备的名称
        //scanRecord里面就是ble设备发出的广播包数据
        //rssi表示ble设备的信号值,该值为负数,值越大表示信号值越好
    
        @Override
        public void onLeScan(final BluetoothDevice device, int rssi,
        byte[] scanRecord) {
            //如果操作UI,切换主线程
            runOnUiThread(new Runnable() {
            @Override
            public void run() {
                
                ...
            
            }
        
        });
    
    }

};

连接到GATT服务端

与一个BLE设备交互的第一步就是连接它——更具体的,连接到BLE设备上的GATT服务端。为了连接到BLE设备上的GATT服务端,需要使用connectGatt()方法。这个方法需要三个参数:一个Context对象,自动连接(boolean值,表示只要BLE设备可用是否自动连接到它),和BluetoothGattCallback调用。

mBluetoothGatt = device.connectGatt(this, false, mGattCallback);

连接到GATT服务端时,由BLE设备做主机,并返回一个BluetoothGatt实例,然后你可以使用这个实例来进行GATT客户端操作。请求方(Android app)是GATT客户端。BluetoothGattCallback用于传递结果给用户,例如连接状态,以及任何进一步GATT客户端操作。

private final BluetoothGattCallback mGattCallback =
new BluetoothGattCallback() {
    @Override
    public void onConnectionStateChange(BluetoothGatt gatt, int status,
    int newState) {//当连接状态发生改变
    
        String intentAction;
        if (newState == BluetoothProfile.STATE_CONNECTED) {//当蓝牙设备已经连接
        
            //获取ble设备上面的服务
            Log.i(TAG, "Attempting to start service discovery:" +
            mBluetoothGatt.discoverServices());
        
        } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {//当设备  无法连接
        
        }
    
    }
    
    //调用discoverServices后的回调    
    @Override
    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
    
        if (status == BluetoothGatt.GATT_SUCCESS) {
        
        //获取服务成功
        
        } else {
        
        Log.w(TAG, "onServicesDiscovered received: " + status);
        
        }
    
    }
    // 读写特性
    @Override   
    public void onCharacteristicRead(BluetoothGatt gatt,
        BluetoothGattCharacteristic characteristic,int status) {
    
            if (status == BluetoothGatt.GATT_SUCCESS) {
            
            }
        
        }
    
    ...

};

发送数据

首先通过UUID拿到对应的服务,再通过UUID拿到服务的特征,设置特征的属性是BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE。设置成功后可以在该特征值上发送数据到ble设备和接收ble设备的数据。看到这里也许各位不熟ble开发和刚ble开发的看官也许就一脸懵逼,我只是想发送数据到ble设备,怎么一下子搞出个UUID 服务和特征值了,难道就不能和B/S开发一样,连接之后我把数据发送到一个接口,服务器端就返回我需要的数据那么简单。这还得从ble蓝牙的架构说起。

BLE分为三部分Service、Characteristic、Descriptor,这三部分都由UUID作为唯一标示符。一个蓝牙4.0的终端可以包含多个Service,一个Service可以包含多个Characteristic,一个Characteristic包含一个Value和多个Descriptor,一个Descriptor包含一个Value。service是characteristic的集合.一个characteristic包括一个单一变量和0-n个用来描述characteristic变量的descriptor.Descriptor用来描述characteristic变量的属性。例如,一个descriptor可以规定一个可读的描述,或者一个characteristic变量可接受的范围,或者一个characteristic变量特定的测量单位。一般来说,Characteristic是手机与BLE终端交换数据的关键.。

举个栗子:当我们想要用手机与BLE设备进行通信时,实际上也就相当于我们要去找一个学生交流,首先我们需要搭建一个管道,也就是我们需要先获取得到一个BluetoothGatt,其次我们需要知道这个学生在哪一个班级,学号是什么,这也就是我们所说的serviceUUID,和charUUID。这里我们还需要注意一下,找到这个学生后并不是直接和他交流,他就好像一个中介一样,在手机和BLE终端设备之间帮助这两者传递着信息,我们手机所发数据要先经过他,在由他传递到BLE设备上,而BLE设备上的返回信息,也是先传递到他那边,然后手机再从他那边进行读取。

在发送数据之前需先设置特征的具有notificaion功能

private BluetoothGatt mBluetoothGatt;

BluetoothGattCharacteristic characteristic;

boolean enabled;

mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);

BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));

descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)

mBluetoothGatt.writeDescriptor(descriptor);

设置完成后回调

@Override
public final void onDescriptorWrite(final BluetoothGatt gatt, final BluetoothGattDescriptor descriptor, final int status) {

    //设置成功
    if (status == BluetoothGatt.GATT_SUCCESS) {
    
    }

}

设置成功后就开始发送数据了。

//将指令放置进特征中

characteristic.setValue(data);

//设置回复形式characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);

//开始写数据

mBluetoothGatt.writeCharacteristic(chharacteristic);

写入数据成功后回调,在BluetoothGattCallback里面

protected void onCharacteristicWrite(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {

    //发送数据成功啦啦啦

}

如何设备回复数据则会回调BluetoothGattCallback

@Override
public final void onCharacteristicChanged(final BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {

}

关闭客户端App

当你的app完成BLE设备的使用后,应该调用close(),系统可以合理释放占用资源。

public void close() {
    if (mBluetoothGatt == null) {
        return;
    }
    mBluetoothGatt.close();
    mBluetoothGatt = null;

}

最后分享我在BLE 开发中遇到的坑和一些经验

1 在所有蓝牙的回调中不要操作UI。我是不会告诉你我是怎么发现这个坑的。

2 在所有的蓝牙回调中不要执行耗时操作。

3 发送数据要等到上一条数据发送成功后再发下一条数据,毕竟BLE设备运算没有手机快,这里可以推荐一个nodic的开源蓝牙连接nRf工具,里面非常好的对发送的数据做了一个数据队列。

4 合理的控制扫描过程,一般出现133错误的时候重连就可以先去扫描再去连接。若扫描不到时不要马上又去扫描,不然你把手机放那一夜,把设备远离它,第二天回来看手机时会惊喜的发现手机没电自动关机了

遇到的坑

1 断线重连的时候总是报133错误,

断线后不要马上去连接.先扫描设备,扫描到设备后再去连接。

2 扫描不到设备

手动关闭蓝牙再打开蓝牙开关。这个可能是重连里面的扫描引起的,如果设备未在周边,一直去扫描的话,后来设备在身边也可能扫描不到设备。如果未能连接设备,也不能一直去扫描。扫描不到设备时说明设备并不到周边,可以延迟多少时间后再去扫描

3 连接设备后发送数据,发送数据的回调函数也已经走了。没有接收到数据

查看设置特征值的描述值

descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE)
mBluetoothGatt.writeDescriptor(descriptor)的回调里面是不是回调成功了

4反复断开蓝牙后再重连导致连接失败

断开蓝牙后应该调用close()方法释放资源.连接时应该设置超时,在超时时间内继续去连接,基本低、中、高端机都能重新连接上。

5 连接上之后自动断开连接,重连上之后又自动断开连接,如此反复。

我们的BLE设备在某些低端机会遇到这种问题。听固件工程师说是BLE设备蓝牙芯片频率和手机蓝牙频率问题,需调BLE设备频率。遇到这种问题APP就束手无策了。

6 反复操作断开和连接导致系统蓝牙挂掉(无响应)

基本也是没有合理释放资源导致

7 调用扫描操作导致APP无响应

查看系统蓝牙是否挂掉了。基本和问题6类似

参考文章
Android BLE开发——Android手机与BLE终端通信初识

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350