在阅读这篇文章之前你应该对GATT和Android蓝牙框架有一定的了解。这里不会向你解释ServiceCharacteristics等蓝牙知识。这里只是我写下我对Android Ble的再次封装来适应APP的业务需求。

BLE模块

在开发时APP需要连接多个Ble设备,可能很多人会想Ble这种长时间运行的程序应该写进Android Service里面。对的写入Service是必须的,但是写入的方法也是对APP有很大的影响的。如果你把所有的Ble连接、数据交互都写入Service中一但Service被杀APP的BLE模块就失效你想再次去连接只能自己开启Service或等到Android调试Service。我的实现方法BLE模块不依赖Service仅仅只是在Service中运行即使Service被杀BLE模块还在对APP不会有任何影响。

抽象GATT

定义IGattClient接口来抽象出BLE的连接的管理。GATT的行为大致可分为:

  1. 连接设备
  2. 发现服务
  3. 断开连接
  4. 关闭GATT

实际开发过程中我将连接到发现服务合并在一起了,因为如果连接成功但是发现服务没有成功时GATT也是不可用的。还有断开连接这个功能我在使用过程中也用的非常少一般都是关闭GATT释放资源。同时定义一些通用的错误信息如:蓝牙不可用、没有扫描到设备等错误信息。

package im.xingzhe.ble.base;

import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt; public interface IGattClient {
int ERROR_SHIFT = 8; //100000000
int STATE_SHIFT = 0; // int STATE_IDLE = -1;
int STATE_CONNECTING = BluetoothGatt.STATE_CONNECTING ;
int STATE_CONNECTED = BluetoothGatt.STATE_CONNECTED ;
int STATE_DISCONNECTING = BluetoothGatt.STATE_DISCONNECTING ;
int STATE_DISCONNECTED = BluetoothGatt.STATE_DISCONNECTED ;
int STATE_SERVICES_DISCOVERED = 0x8;
int ERROR_NONE = 0;
int ERROR_BLUETOOTH = 0x1 << ERROR_SHIFT ;
int ERROR_TIMEOUT = 0x2 << ERROR_SHIFT;
int ERROR_CONNECT_LOSE = 0x8 << ERROR_SHIFT;
int ERROR_NOT_FOUND_DEVICE = 0x10 << ERROR_SHIFT; //4096
int ERROR_DEVICE_BUSY = 0x11 << ERROR_SHIFT;
int ERROR_UNKNOWN = 0x12 << ERROR_SHIFT; BluetoothDevice getBluetoothDevice();
String getName();
String getAddress();
BluetoothGatt getBluetoothGatt();
int getLastError();
void connect();
void discoverServices();
void disconnect();
void close();
int getConnectionState();
void registerConnectionListener(ConnectionListener listener);
void unregisterConnectionListener(ConnectionListener listener); interface ConnectionListener {
void onStateChanged(IGattClient client, int newState);
}
}

定义接口后就是如何来实现以上接口的问题。在实现过程中是利用Android Handler机制来实现的,每一个GattClient中都有一个Handler来处理连接、发现服务、断开连接和关闭Gatt。由于篇幅原因IGattClient实现代码我就不全部贴出来了只拿出部分来讲解一下。

定义通用Handler

通过Handler机制来同步处理Gatt基本操作这样维护起来也比较方便同样可以保持设备的状态不会乱掉。

package im.xingzhe.ble.base;

import android.os.Handler;
import android.os.Looper;
import android.os.Message; public class GattClientHandler extends Handler { AbsGattClient mClientRef;
public GattClientHandler(AbsGattClient client, Looper looper) {
super(looper);
this.mClientRef = client;
}
@Override
public void handleMessage(Message msg) {
AbsGattClient client = mClientRef;
try{
if( client != null)
client .handleMessage(msg);
} catch (Exception exception){
client.e(exception);
}
}
}

同步状态与发现服务

由于IGattClient要维护自己的设备,有时候这些状态是由程序主动发起的也有可能由于设备信号不足导致的。不管怎样这些状态Android BLE框架中都会有回调。Android通过BluetoothGattCallback来回调Gatt状态,其中onConnectionStateChange这个方法是用来告诉我们蓝牙设备连接已经改变。我们应该这个方法中刷新IGattClient中维护的状态。这里我定义了BaseBluetoothGattCallback来同步状态。

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public class BaseBluetoothGattCallback<CLIENT extends AbsBleDevice> extends BluetoothGattCallback { public CLIENT mClientRef; public BaseBluetoothGattCallback(CLIENT client) {
this.mClientRef = client;
} @Override
public final void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
if(mClientRef != null){
try{
mClientRef.syncState(status, newState);
}catch (Exception e){
mClientRef.e(e);
}
}
} @Override
public final void onServicesDiscovered(BluetoothGatt gatt, int status) {
if(mClientRef != null){
try{
mClientRef.handleServicesDiscovered(status);
}catch (Exception e){
mClientRef.e(e);
}
}
}

在BaseBluetoothGattCallback中会回调AbsGattClientsyncStatehandleServicesDiscovered 方法。如果你要问我既然Android中已经维护了状态为什么我们的实现中还要自己去维护。我只能说因国内的Android机型太多系统大都是深度定制。比如说:你把设备电池下了或使信号丢失,正常情况下statusBluetoothGatt.GATT_SUCCESSnewState会是BluetoothProfile.STATE_DISCONNECTED然而有些手机并不会这样给你回调。

 public void syncState(int status, int newState) {
d(String.format("onConnectionStateChange: status->%d, newState->%d", status, newState));
/*
不应该依赖系统API去判断一个连接是成功还是失败,使用AbsGattClient内部维护的状态码来
决定操作
*/
if (status == BluetoothGatt.GATT_SUCCESS
&& newState == mTargetState) {
//是期望的状态
refreshGattClientState(mTargetState, mTargetState);
} else {
int currentState = mCurrentState; //保存当前状态
int targetState = mTargetState;
mError = ERROR_UNKNOWN;
if (currentState == STATE_CONNECTING) {
mError = ERROR_DEVICE_BUSY;
} else if (newState == STATE_DISCONNECTED) {
closeBluetoothGatt();
refreshGattClientState(STATE_DISCONNECTED, STATE_DISCONNECTED);
if ((targetState == currentState)
&& (/*currentState == STATE_CONNECTED || */currentState == STATE_SERVICES_DISCOVERED)) {
/*
本地记录是已连接状态但可能由于信号或设备休眠导致失去连接
*/
mError = ERROR_CONNECT_LOSE;
printError();
if (isAutoConnection()) {
reconnect();
}
}
}
} wakeup();
}

连接处理

我将设备的连接和其他行为设计成等待的机制如:设备连接时会首先调用mBluetoothDevice.connectGatt(mAppContext, false, mGattCallback);然后阻塞initLocalScheduler();中启动的线程同时等待syncState方法的的唤醒。当前也要有超时机制不然整个设备没法用了。

protected synchronized void initLocalScheduler() {
if (mLocalHandler != null) {
return;
} HandlerThread ht = new HandlerThread(getName() == null ? getAddress() : getName());
ht.setUncaughtExceptionHandler(this);
ht.start();
mLocalHandler = new GattClientHandler(this, ht.getLooper());
} protected void _connect() {
d("try to connect to " + getName());
mError = ERROR_NONE;
mNotify = false; refreshGattClientState(STATE_CONNECTING, STATE_CONNECTED);
//检测蓝牙是否可用
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
refreshGattClientState(STATE_DISCONNECTED, STATE_DISCONNECTED);
mError = ERROR_BLUETOOTH;
return;
} if (mBluetoothDevice == null) {
if (mDeviceAddress != null) {
mBluetoothDevice = mBluetoothAdapter.getRemoteDevice(mDeviceAddress);
} if (mBluetoothDevice == null) {
mError = ERROR_NOT_FOUND_DEVICE;
refreshGattClientState(STATE_DISCONNECTED, STATE_DISCONNECTED);
return;
}
}
mConnectingDevices.incrementAndGet();
mBluetoothGatt = mBluetoothDevice.connectGatt(mAppContext, false, mGattCallback);
if (mBluetoothGatt == null) {
mError = ERROR_BLUETOOTH;
refreshGattClientState(STATE_DISCONNECTED, STATE_DISCONNECTED);
} else {
waitForRemoteDevice(DEFAULT_CONNECT_TIMEOUT * mConnectingDevices.get());
if (mCurrentState == STATE_CONNECTED) {
_discoverServices();
if (mCurrentState == STATE_SERVICES_DISCOVERED) {
//连接成功后清除下Message
mLocalHandler.removeMessages(OP_CONNECT);
mLocalHandler.removeMessages(OP_RECONNECT);
mConnectingDevices.decrementAndGet();
return;
}
}
closeBluetoothGatt();
} mConnectingDevices.decrementAndGet();
refreshGattClientState(STATE_DISCONNECTED, STATE_DISCONNECTED);
} private void waitForRemoteDevice(int millis) {
d("waitForRemoteDevice: " + (millis));
try {
long start = SystemClock.uptimeMillis();
synchronized (mLock) { //如果返回太快,将会导致唤醒失败
if (!mNotify)
mLock.wait(millis); if (!mNotify) {
mError = ERROR_TIMEOUT;
} else {
//mCurrentState会在别处更新
}
}
d("waitForRemoteDevice return: " + (SystemClock.uptimeMillis() - start));
} catch (InterruptedException e) {
e.printStackTrace();
refreshGattClientState(STATE_DISCONNECTED, STATE_DISCONNECTED);
} }

抽象BEL设备

IGattClient抽象完成后接下来就是定义BLE设备。我只贴出AbsBleDevcie代码其中只是实现了一些蓝牙标准中的Service的处理。

public abstract class AbsBleDevice extends AbsGattClient implements IBleDevice {

    public static UUID CLIENT_CHARACTERISTIC_CONFIG2 = UUID.fromString(BLEAttributes.CLIENT_CHARACTERISTIC_CONFIG2);
public static UUID BLE_BATTERY_SERVICE = UUID.fromString(BLEAttributes.BLE_BATTERY_SERVICE);
public static UUID BLE_BATTERY_CHARACTERISTIC = UUID.fromString(BLEAttributes.BLE_BATTERY_CHARACTERISTIC);
public static UUID BLE_DEVICE_INFORMATION_SERVICE = UUID.fromString(BLEAttributes.BLE_DEVICE_INFORMATION_SERVICE);
public static UUID BLE_DEVICE_INFORMATION_FIRMWARE_CHARACTERISTIC = UUID.fromString(BLEAttributes.BLE_DEVICE_INFORMATION_FIRMWARE_CHARACTERISTIC); private static final int OP_READ_BATTERY = 0x1;
private static final int OP_READ_FIRMWARE = 0x2;
private static final int OP_SET_NOTIFICATION = 0x3; private Device mLocalDevice; public AbsBleDevice(Device localDevice){
this.mLocalDevice = localDevice;
} @CallSuper
@Override
protected void onStateChanged(int currentState, int targetState) {
if(currentState == STATE_SERVICES_DISCOVERED) {
onServicesDiscovered();
}else if(currentState == STATE_DISCONNECTED){
onDeviceDisconnected();
} }
protected void onServicesDiscovered(){ } protected void onDeviceDisconnected(){ } public Device getLocalDevice(){
return this.mLocalDevice;
} public int getDeviceType(){
return mLocalDevice != null ? mLocalDevice.getType(): Device.TYPE_UNKNOW;
} public boolean isServicesDiscovered( ){
return getConnectionState() == STATE_SERVICES_DISCOVERED;
} public void readFirmwareVersion(){
if(isServicesDiscovered()){
Message message = mLocalHandler.obtainMessage(OP_READ_FIRMWARE);
mLocalHandler.sendMessageDelayed(message, 500);
} } private void _readFirmwareVersion(){
BluetoothGattService deviceInfoService = mBluetoothGatt.getService(BLE_DEVICE_INFORMATION_SERVICE);
if(deviceInfoService != null){
BluetoothGattCharacteristic firmwarmCh = deviceInfoService.getCharacteristic(BLE_DEVICE_INFORMATION_FIRMWARE_CHARACTERISTIC);
mBluetoothGatt.readCharacteristic(firmwarmCh);
}
} private void _readBattery(){
BluetoothGattService batteryService = mBluetoothGatt.getService(BLE_BATTERY_SERVICE);
if(batteryService != null) {
BluetoothGattCharacteristic batteryCharacteristic = batteryService.getCharacteristic(BLE_BATTERY_CHARACTERISTIC);
mBluetoothGatt.readCharacteristic(batteryCharacteristic);
}
} public void readBattery(){
if(isServicesDiscovered()){
Message msg = mLocalHandler.obtainMessage(OP_READ_BATTERY);
mLocalHandler.sendMessageDelayed(msg, 300);
}
} protected void _setCharacteristicNotification2(BluetoothGattCharacteristic characteristic, boolean enabled) {
if(characteristic == null)
return; BluetoothGatt gatt = getBluetoothGatt();
gatt.setCharacteristicNotification(characteristic, enabled);
final BluetoothGattDescriptor descriptor = characteristic.getDescriptor(CLIENT_CHARACTERISTIC_CONFIG2);
if (descriptor != null) {
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor); }
} public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enabled) {
if(isServicesDiscovered()){
Message message = mLocalHandler.obtainMessage(OP_SET_NOTIFICATION, enabled ? 1: 0, 0, characteristic);
mLocalHandler.sendMessage(message);
}
} @Override
protected void handleMessage(Message message) {
super.handleMessage(message); switch (message.what){
case OP_READ_BATTERY:
_readBattery();
break;
case OP_READ_FIRMWARE:
_readFirmwareVersion();
break;
case OP_SET_NOTIFICATION:
_setCharacteristicNotification2((BluetoothGattCharacteristic) message.obj, message.arg1 != 0);
break;
}
} }

写在最后

其实整个实现过程是比较波折的,但是经过慢慢的摸索现在的我手上这个APP的BLE模块还是比较稳定的。市面上的大部分机型都可以正常工作除了一些相对来说比较老的设备有一些问题。



《架构文摘》每天一篇架构领域重磅好文,涉及一线互联网公司应用架构(高可用、高性 能、高稳定)、大数据、机器学习等各个热门领域。

Android蓝牙低功耗(BLE)模块设计的更多相关文章

  1. 【Android应用开发】Android 蓝牙低功耗 (BLE) ( 第一篇 . 概述 . 蓝牙低功耗文档 翻译)

    转载请注明出处 : http://blog.csdn.net/shulianghan/article/details/50515359 参考 :  -- 官方文档 : https://develope ...

  2. [nRF51822] 14、浅谈蓝牙低功耗(BLE)的几种常见的应用场景及架构(科普类干货)

    蓝牙在短距离无线通信领域占据举足轻重的地位—— 从手机.平板.PC到车载设备, 到耳机.游戏手柄.音响.电视, 再到手环.电子秤.智能医疗器械(血糖仪.数字血压计.血气计.数字脉搏/心率监视器.数字体 ...

  3. 浅谈蓝牙低功耗(BLE)的几种常见的应用场景及架构(转载)

    转载来至beautifulzzzz,网址http://www.cnblogs.com/zjutlitao/,推荐学习 蓝牙在短距离无线通信领域占据举足轻重的地位—— 从手机.平板.PC到车载设备, 到 ...

  4. 【蓝牙低功耗BLE】控制GPIO来点亮LED

    这节讲一下最简单的,也是最基础的东西.CC2540的IO操作,把PORT口当做GPIO来用,废话不多说,往下看. 1.硬件电路 硬件电路时最简单的,用一根GPIO去控制LED灯.因为GPIO作为out ...

  5. 低功耗蓝牙(BLE)透传模块 ——RF-BM-S01(BQB认证)

    本文来源深圳信驰达科技www.szrfstar.com,技术交流群336720020. 低功耗蓝牙(BLE)透传模块 ——RF-BM-S01(BQB认证) 深圳市信驰达科技有限公司 2013年3月18 ...

  6. 【原创】Android 5.0 BLE低功耗蓝牙从设备应用

    如果各位觉得有用,转载+个出处. 现如今安卓的低功耗蓝牙应用十分普遍了,智能手环.手表遍地都是,基本都是利用BLE通信来交互数据.BLE基本在安卓.IOS两大终端设备上都有很好支持,所以有很好发展前景 ...

  7. Android低功耗蓝牙(BLE)使用详解

    代码地址如下:http://www.demodashi.com/demo/13390.html 与普通蓝牙相比,低功耗蓝牙显著降低了能量消耗,允许Android应用程序与具有更严格电源要求的BLE设备 ...

  8. Android蓝牙BLE低功耗相关简单总结

    在看Android4.42的源代码时看到有加入对BLE设备的处理.看的一头雾水,多方百度,最终有种柳暗花明的感觉. 本文总结来源于百度多篇文章,欢迎转载.分享交流 BLE蓝牙概念 BLE:Blueto ...

  9. 物联网安全拔“牙”实战——低功耗蓝牙(BLE)初探

    物联网安全拔“牙”实战——低功耗蓝牙(BLE)初探 唐朝实验室 · 2015/10/30 10:22 Author: FengGou 0x00 目录 0x00 目录 0x01 前言 0x02 BLE概 ...

随机推荐

  1. Codeforces 255C

    题意略. 本题考查动态规划,顺便考查一下优化. 这个题目可以归约到最长递增子序列那一类,定义状态:dp[i][j] --- 当前以第i个数结尾,前一个数是第j个数的最长序列. if(a[i] == a ...

  2. 利用window对象下内置的子对象实现网页的刷新

    这里我们用到的window对象下内置的子对象有:     1.history对象:包含浏览器访问过的url.我们可以利用它的history.go(num);属性实现页面的刷新:           h ...

  3. Leetcode之二分法专题-1011. 在 D 天内送达包裹的能力(Capacity To Ship Packages Within D Days)

    Leetcode之二分法专题-1011. 在 D 天内送达包裹的能力(Capacity To Ship Packages Within D Days) 传送带上的包裹必须在 D 天内从一个港口运送到另 ...

  4. sql查询技巧指南

    传送门(牛客网我做过的每到题目答案以及解析) sql定义: 结构化查询语言(Structured Query Language)简称SQL,是一种特殊目的的编程语言,是一种数据库查询和程序设计语言,用 ...

  5. 【故障公告】阿里云 RDS 数据库服务器 CPU 100% 造成全站故障

    非常非常抱歉,今晚 19:34 ~ 21:16 园子所使用的阿里云 RDS 数据库服务器突然出现 CPU 100% 问题,造成全站无法正常访问,由此您带来了很大的麻烦,请您谅解. 故障经过是这样的.1 ...

  6. CodeForces 1042 F Leaf Sets 贪心

    Leaf Sets 题意:给你一棵树,树上有n个点,只有一条边的点叫做叶子,现在要求把所有的叶子分组,每个组内的所有叶子的距离都不能大于k. 题解: 我们可以随意找一个不是叶子的节点当做这颗树的根节点 ...

  7. hdu 3974 Assign the task(线段树)

    题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=3974 题意:给定一棵树,50000个节点,50000个操作,C x表示查询x节点的值,T x y表示更 ...

  8. CF991D Bishwock 第十七 贪心

    Bishwock time limit per test 1 second memory limit per test 256 megabytes input standard input outpu ...

  9. yzoj P2343 & 洛谷 P1437 [HNOI2004]敲砖块

    题意 在一个凹槽中放置了N层砖块,最上面的一层油N块砖,从上到下每层一次减少一块砖.每块砖都有一个分值,敲掉这块砖就能得到相应的分值,如图所示. 如果你想敲掉第i层的第j块砖的话,若i=1,你可以直接 ...

  10. 从一道看似简单的面试题重新理解JS执行机制与定时器

     壹 ❀ 引 最近在看前端进阶的系列专栏,碰巧看到了几篇关于JS事件执行机制的面试文章,因为我在之前一篇 JS执行机制详解,定时器时间间隔的真正含义 博文中也有记录JS执行机制,所以正好用于作为测试自 ...