在阅读这篇文章之前你应该对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_数据结构(常见数据结构及适用场景)

    常见的数据结构: 1.Array: 最简单的数据结构 特点:数组存储在连续的内存上.数组的内容都是相同类型.数组可以直接通过下标访问.优点:由于是在连续内存上存储的,所以它的索引速度非常快,访问一个元 ...

  2. Unity之Update与FixedUpdate区别

    下面这段代码演示游戏暂停 using UnityEngine; using System.Collections; public class GamePauseTest : MonoBehaviour ...

  3. 理解Java反射机制

    理解Java反射机制 转载请注明出处,谢谢! 一.Java反射简介 什么是反射? Java的反射机制是Java特性之一,反射机制是构建框架技术的基础所在.灵活掌握Java反射机制,对学习框架技术有很大 ...

  4. WebGL简易教程(三):绘制一个三角形(缓冲区对象)

    目录 1. 概述 2. 示例:绘制三角形 1) HelloTriangle.html 2) HelloTriangle.js 3) 缓冲区对象 (1) 创建缓冲区对象(gl.createBuffer( ...

  5. MySQL5.7.27报错[Err] 1055 - Expression #1 of ORDER BY clause is not in GROUP BY clause and contains nonaggregated

    mysql5.7.27在运行更新语句时出现如下情况,mysql5.6之前没有这种情况出现. of ORDER BY clause is not in GROUP BY clause and conta ...

  6. lightoj 1097 - Lucky Number(线段树)

    Lucky numbers are defined by a variation of the well-known sieve of Eratosthenes. Beginning with the ...

  7. Atcoder C - Closed Rooms(思维+bfs)

    题目链接:http://agc014.contest.atcoder.jp/tasks/agc014_c 题意:略. 题解:第一遍bfs找到所有可以走的点并标记步数,看一下最少几步到达所有没锁的点,然 ...

  8. windows系统下安装JDK8

    学习JAVA,必须得安装一下JDK(java development kit java开发工具包),配置一下环境就可以学习JAVA了,下面是下载和安装JDK的教程: 一.下载 1.JDK下载地址: h ...

  9. Java 13 明天发布,最新最全新特性解读

    2017年8月,JCP执行委员会提出将Java的发布频率改为每六个月一次,新的发布周期严格遵循时间点,将在每年的3月份和9月份发布. 目前,JDK官网上已经可以看到JDK 13的进展,最新版的JDK ...

  10. 1512: [POI2006]Pro-Professor Szu

    首先把边反向, 问题转化成求从主建筑楼走向各个点的方案数. 然后缩点,块中的方案数可以直接算. 设f[i]表示走到第i个点的方案数.显然f[i]=∑f[j](存在newedge(j,i))初始时,f[ ...