在阅读这篇文章之前你应该对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. Unity之与Web的交互

    一.下载,安装,配置,启动Apache 1.进入官网下载Apache 2.解压到根目录 3.记事本打开如下配置文件 4.安装apache 5.出现错误:(该错误是由于端口被占用引起的) 6.修改配置文 ...

  2. 【Leetcode】【简单】【122. 买卖股票的最佳时机 II】【JavaScript】

    题目描述 122. 买卖股票的最佳时机 II 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格. 设计一个算法来计算你所能获取的最大利润.你可以尽可能地完成更多的交易(多次买卖一支股票) ...

  3. java实现查找PDF关键字所在页码及其坐标

    1.因为最近有这方面的需求,用过之后记录一下. 2.此功能跟PDF中Ctrl+F性质一样,如果PDF中为图片形式的不支持定位到关键字. import com.itextpdf.awt.geom.Rec ...

  4. Struts2:request & response

    整理自网上:  1.   获取Request和Response的方法  1.1. ServletActionContext的静态方法      HttpServletRequest request = ...

  5. Scrum的三个仪式:Sprint规划会,Scrum每日站会,Sprint评审会

    转自:http://blog.sina.com.cn/s/blog_6997f01501010m21.html Sprint Planning Meeting(Sprint规划会) 根据Product ...

  6. 2018CCPC 吉林现场赛 赛后总结

    一直以来都没有比赛完写总结的习惯,导致前面几次比赛都没有写过总结. 这是我写的第一场总结把,有时间有想法还记得细节的话再把前面几次比赛的总结给补上把. 热身赛: 热身赛的时候,写的比较急想着快点做出题 ...

  7. codeforces 509 E. Pretty Song(前缀和+前缀和的前缀和)

    题目链接:http://codeforces.com/problemset/problem/509/E 题意:给你一个字符串,求字串中包括子串中I, E, A, O, U, Y.所占的概率和. 题解: ...

  8. Java微服务(一):dubbo-admin控制台的使用

    1.环境准备 使用CentOS7+Docker+Zookeeper3.4.10搭建dubbo微服务 1.1.安装docker容器 (1).uname -r:docker要求CentOS的内核版本高于3 ...

  9. 让docker中的mysql启动时自动执行sql

    在用docker创建mysql容器的时,有时候我们期望容器启动后数据库和表已经自动建好,初始化数据也已自动录入,也就是说容器启动后我们就能直接连上容器中的数据库,使用其中的数据了. 其实mysql的官 ...

  10. POJ 2488 A Knight's Journey (DFS)

    poj-2488 题意:一个人要走遍一个不大于8*8的国际棋盘,他只能走日字,要输出一条字典序最小的路径 题解: (1)题目上说的"The knight can start and end ...