现在随着智能化潮流的到来,智能设备越来越多,而其中很多都使用的ble技术进行通讯,很多android开发人员会接触到ble开发。我是去年开始接触ble开发的,那时候百度基本没什么资料,苦逼的我只能上谷歌,踩了不少坑,所以现在就把我所学到的东西记录下来,方便以后查询。
现在手机APP连接ble设备基本使用的是主模式,即手机作为主机(中心),ble设备作为从机(外围),手机主动发起连接到ble设备,安卓5.0之后开始支持从模式,这篇文章的是主模式(中心),从模式(外围)
首先,ble设备与手机的通讯过程大概是:ble设备发出广播并提供用于通讯的服务和特征字-->手机开启蓝牙进行扫描扫描到ble设备之后发起连接-->连接完成之后通过ble设备提供的用于通讯的服务和特征字开始收发数据
今天先讲手机如何开启蓝牙进行扫描并连接。
第一步,先检查蓝牙是否打开,没有则打开
/**
* 检测蓝牙是否打开
*/
void openBluetoothScanDevice() {
if (!BluetoothAdapter.getDefaultAdapter().isEnabled()) {
//蓝牙没打开则去打开蓝牙
boolean openresult = toEnable(BluetoothAdapter.getDefaultAdapter());
if(!openresult){
Toast.makeText(MainActivity.this, "打开蓝牙失败,请检查是否禁用了蓝牙权限",Toast.LENGTH_LONG).show();
return;
}
//停个半秒再检查一次
SystemClock.sleep(500);
runOnUiThread(new Runnable() {
@Override
public void run() {
int i = 0;
while (!BluetoothAdapter.getDefaultAdapter().isEnabled()){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i >= 15){
Toast.makeText(MainActivity.this, "打开蓝牙失败,请检查是否禁用了蓝牙权限",Toast.LENGTH_LONG).show();
break;
}else{
i++;
}
}
//发现蓝牙打开了,则进行开启扫描的步骤
scanDevice();
}
});
} else {
//检查下当前是否在进行扫描 如果是则先停止
if (mBle != null && mScanning){
mBle.stopScan();
}
scanDevice();
}
}
前面的代码注释很齐全,就不多讲了,就是确定蓝牙是开启状态之后就去开启ble扫描,接下上开启扫描的代码,开关扫描是同一句方法scanLeDevice,由参数来决定是开还是关
@UiThread
void scanDevice() {
//如果此时发蓝牙工作还是不正常 则打开手机的蓝牙面板让用户开启
if (mBle != null && !mBle.adapterEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
myhandler.postDelayed(new Runnable() {
@Override
public void run() {
//检查一下去那些,如果没有则动态请求一下权限
requestPermission();
//开启扫描
scanLeDevice(true);
}
},500);
}
private void scanLeDevice(final boolean enable) {
//获取ble操作类
mBle = AppContext.getInstance().getmBle();
if (mBle == null) {
return;
}
if (enable) {
//开始扫描
if (mBle != null) {
boolean startscan = mBle.startScan(resultCallback);
if (!startscan){
Toast.makeText(MainActivity.this, "开启蓝牙扫描失败,请检查蓝牙是否正常工作!",Toast.LENGTH_LONG).show();
return;
}
mScanning = true;
//扫描一分钟后停止扫描
myhandler.postDelayed(stopRunnable,SCAN_PERIOD);
}
} else {
//停止扫描
mScanning = false;
if (mBle != null) {
mBle.stopScan();
myhandler.removeCallbacksAndMessages(null);
}
}
}
BleHelper)的stopScan、startScan、adapterEnabled方法和一个resultCallback(BleScanResultCallback),这是我自己封装的ble操作类和callback
public interface BleScanResultCallback {
void onSuccess();
void onFail();
void onFindDevice(BluetoothDevice device, int rssi,
byte[] scanRecord);
}
/**
* Ble操作类
* Created by xu on 2017/5/25.
*/
public class BleHelper {
private final static String TAG = "BleHelper";
/**
* 扫描结果的callback
*/
private BleScanResultCallback resultCallback;
/**
* 设备连接状态监听的callback
*/
private BleConnectionCallback connectionStateCallback;
/**
* 蓝牙的适配器
*/
private BluetoothAdapter mBtAdapter;
/**
* 上下文
*/
private Context context;
/**
* gatt的集合
*/
private Map<String, BluetoothGatt> mBluetoothGatts;
/**
* 重连的次数
*/
int reconnectCount = 0;
/**
* 是否进行重连
*/
private boolean canreconntect = false;
/**
* gatt连接callback
*/
private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(final BluetoothGatt gatt, int status,
int newState) {
super.onConnectionStateChange(gatt, status, newState);
Log.e(TAG, "onConnectionStateChange() status:" + status + " newState:" + newState);
String address = gatt.getDevice().getAddress();
if (status == BluetoothGatt.GATT_SUCCESS) {
//如果是主动断开的 则断开
if (newState == BluetoothProfile.STATE_CONNECTED) {
reconnectCount = 0;
Log.e(TAG, "连接成功");
if(connectionStateCallback != null){
connectionStateCallback.onConnectionStateChange(status,newState);
}
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
disconnect(address);
Log.e(TAG, "断开成功");
if(connectionStateCallback != null){
connectionStateCallback.onConnectionStateChange(status,newState);
}
}
}else{
//如果是收到莫名其妙的断开状态的话 则重连一次
if (newState != BluetoothProfile.STATE_CONNECTED && canreconntect) {
//先断开原有的连接
disconnect(address);
if(reconnectCount >= 2){
//重连三次不成功就失败
if(connectionStateCallback != null){
connectionStateCallback.onConnectionStateChange(status,newState);
}
reconnectCount = 0;
canreconntect = false;
return;
}
//再次重新连接
boolean connect_resule = requestConnect(address,connectionStateCallback,true);
reconnectCount++;
Log.e(TAG,"正在尝试重新连接:"+connect_resule);
}else{
if(connectionStateCallback != null){
connectionStateCallback.onConnectionStateChange(status,newState);
}
disconnect(address);
}
}
}
};
/**
* 扫描ble设备的callback
*/
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi,
byte[] scanRecord) {
if(resultCallback != null){
resultCallback.onFindDevice(device,rssi,scanRecord);
}
}
};
public BleHelper() {
}
/**
*初始化ble操作类
* @param context:上下文 建议用持久的
*/
public boolean init(Context context) {
this.context = context;
final BluetoothManager bluetoothManager = (BluetoothManager) context
.getSystemService(Context.BLUETOOTH_SERVICE);
mBtAdapter = bluetoothManager.getAdapter();
if (mBtAdapter == null) {
return false;
}
mBluetoothGatts = new HashMap<String, BluetoothGatt>();
return true;
}
/**
*开启扫描
* @param resultCallback:扫描结果的calllback
*/
public boolean startScan(BleScanResultCallback resultCallback) {
this.resultCallback = resultCallback;
try {
return mBtAdapter.startLeScan(mLeScanCallback);
} catch (Exception e) {
e.printStackTrace();
Log.d(TAG, "使用BLE扫描API开始扫描出错了 错误:" + e.getMessage());
return false;
}
}
/**
*停止扫描
*/
public void stopScan() {
try {
mBtAdapter.stopLeScan(mLeScanCallback);
resultCallback = null;
} catch (Exception e) {
e.printStackTrace();
Log.d(TAG, "使用的BLE扫描API停止扫描出错了 错误:" + e.getMessage());
}
}
/**
*断开连接
*/
public synchronized void disconnect(String address) {
if (mBluetoothGatts.containsKey(address)) {
BluetoothGatt gatt = mBluetoothGatts.remove(address);
if (gatt != null) {
gatt.disconnect();
gatt.close();
Log.e(TAG, "执行到了设备断开的指令");
}
}
}
/**
*发起连接(供外部调用)
* @param address:目标设备的address地址
* @param connectionStateCallback:设备连接状态的callback
* @param canreconntect:是否启用重连机制 true:重连三次 false:不进行重连
*/
public boolean requestConnect(String address,BleConnectionCallback connectionStateCallback,boolean canreconntect) {
this.connectionStateCallback = connectionStateCallback;
this.canreconntect = canreconntect;
BluetoothGatt gatt = mBluetoothGatts.get(address);
if (gatt != null && gatt.getServices().size() == 0) {
return false;
}
return connect(address);
}
/**
*连接到设备
* @param address:目标设备的address地址
*/
private boolean connect(String address) {
BluetoothDevice device = mBtAdapter.getRemoteDevice(address);
BluetoothGatt gatt = device.connectGatt(context, false, mGattCallback);
//BluetoothGatt gatt = device.connectGatt(mService, false, mGattCallback, BluetoothDevice.TRANSPORT_BREDR | BluetoothDevice.TRANSPORT_LE);
//为了防止重复的gatt,连接成功先检查是否有重复的,有则断开
BluetoothGatt last = mBluetoothGatts.remove(address);
if(last != null){
last.disconnect();
last.close();
}
if (gatt == null) {
mBluetoothGatts.remove(address);
return false;
} else {
// TODO: if state is 141, it can be connected again after about 15
// seconds
mBluetoothGatts.put(address, gatt);
return true;
}
}
/**
*蓝牙适配器是否正常
*/
public boolean adapterEnabled() {
if (mBtAdapter != null) {
return mBtAdapter.isEnabled();
}
return false;
}
}
6.0之后,要扫描到设备还需要定位权限,部分手机还需要打开GPS定位才能扫描到手机。
BluetoothDevice类的getAddress()方法得到目标设备的address,然后调用ble操作类的requestConnect方法建立连接(根据设备不同,有的设备连接时间会有那么一点点长),此处的BleConnectionCallback也是我自己封装的一个连接callback。
public interface BleConnectionCallback {
void onConnectionStateChange(int status, int newState);
void onFail(int errorCode);
}
到了这里,ble设备的扫描和连接就完成了。
通讯建立了,接下来讲讲如何进行数据的收发,传统蓝牙用的是socket来进行通讯,而ble则不大一样,使用的是characteristic。
characteristic的属性有三种:可读、可写、可通知。一个characteristic可以同时具备多种属性。如果你要发送数据,就只能通过拥有可写属性的characteristic发送出去。要接收数据记得通过拥有可读或可通知属性的的characteristic接收。
我们先讲如何发送数据,首先你得先得到用于发送数据的BluetoothGattCharacteristic,而BluetoothGattCharacteristic是属于ble的service(跟安卓的service不是一个概念)下的,你可以先通过service的UUID先去获取到service实例,再通过BluetoothGattCharacteristic的UUID去从service实例下获取对应的BluetoothGattCharacteristic。UUID这个设备帮都会主动告诉你的,如果没有,请去找他们要。
bleGattCharacteristicOthers =
mBle.getService(deviceAddress,
UUID.fromString(BleBase.BLE_SERVICE_UUID)).getCharacteristic(
UUID.fromString(BleBase.BLE_CHARACTERISTIC_UUID));
获取到了characteristic之后,把你要发送的数据通过BluetoothGattCharacteristic.setValue(byte[] data)方法填到characteristic里面,然后调用BluetoothGatt.writeCharacteristic(BluetoothGattCharacteristic mGattCharacteristicWrite)方法传输到设备。ble通讯传递的也是16进制数据。
/**
*写数据
* @param address:目标设备的address
* @param data:要发送的内容
*/
public boolean requestWriteCharacteristic(String address,byte[] data) {
BluetoothGatt gatt = mBluetoothGatts.get(address);
if (gatt == null || mGattCharacteristicWrite == null) {
if (gatt == null) {
Log.e(TAG, "发送BLE数据失败:gatt = null");
}
if (mGattCharacteristicWrite == null) {
Log.e(TAG, "发送BLE数据失败:characteristic = null");
}
return false;
}
Log.d(TAG, "data:" + CodeUtil.bytesToString(mGattCharacteristicWrite.getValue()));
boolean result = false;
try {
mGattCharacteristicWrite.setValue(data);
result = gatt.writeCharacteristic(mGattCharacteristicWrite);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return result;
}
发送成功与否会调用BluetoothGattCallback的onCharacteristicWrite返回。
@Override
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
super.onCharacteristicWrite(gatt, characteristic, status);
Log.e(TAG, "onCharacteristicWrite()");
if (status != BluetoothGatt.GATT_SUCCESS) {
Log.e(TAG, "onCharacteristicWrite()检测到不可以写数据 status:" + status);
return;
}
Log.e(TAG, "onCharacteristicWrite()检测到可以去写数据了");
Log.d("写出的数据:", CodeUtil.bytesToString(characteristic.getValue()));
}
此时我们数据发送出去,接下来讲一讲怎么接收设备端发送过来的数据。一般接收数据有两种发送,一是通过拥有可读属性的BluetoothGattCharacteristic去主动读取,二是通过拥有可通知属性的BluetoothGattCharacteristic去被动接收。现在基本使用的都是第二种,第一种使用场景有限。所以我们来讲讲第二种方式怎么做。
使用UUID获取到对应的service以及BluetoothGattCharacteristic前面已经讲了,那样要接收到拥有可通知属性发送过来的数据,首先先要订阅一下:BluetoothGatt.setCharacteristicNotification(BluetoothGattCharacteristic characteristic,boolean enable) 。enable表示是否接收此BluetoothGattCharacteristic的通知。
订阅完成之后还需要写一下描述者BluetoothGatt.writeDescriptor(BluetoothGattDescriptor descriptor),此时便可以接收到ble设备发送过来的数据了。
/**
* 订阅并写描述者 (指定UUID的service下的所有Notification的特征字)
*/
public boolean characteristicNotification(String uuid){
BluetoothGatt gatt = mBluetoothGatts.get(deviceAddress);
if (gatt == null) {
Log.e(TAG, "BluetoothAdapter not initialized");
return false;
}
BluetoothGattService service = getService(UUID.fromString(uuid));
if(service == null){
Log.e(TAG, "service is null");
return false;
}
List<BluetoothGattCharacteristic> gattCharacteristics = service.getCharacteristics();
for (int j = 0; j < gattCharacteristics.size(); j++) {
BluetoothGattCharacteristic chara = gattCharacteristics.get(j);
//判断是否有可通知的特征字,有则进行订阅写描述者
if ( (chara.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
boolean b = gatt.setCharacteristicNotification(chara, true);
BluetoothGattDescriptor descriptor = chara.getDescriptor(DESC_CCC);
Log.e(TAG, "订阅结果:" + b + " characteristic:" + chara.getUuid());
if (descriptor == null) {
return false;
}
byte[] val_set = null;
val_set = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE;
if (!descriptor.setValue(val_set)) {
Log.e(TAG, "descriptor.setValue失败" );
return false;
}
boolean f = gatt.writeDescriptor(descriptor);
Log.e(TAG, "写描述者结果:" + f + " characteristic:" + chara.getUuid());
}
try {
Thread.sleep(150);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return true;
}
ble设备发送过来的数据时,底层会调用BluetoothGattCallback的onCharacteristicChanged方法,我们可以在这里获取到ble设备发送过来的数据。
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
super.onCharacteristicChanged(gatt, characteristic);
Log.e(TAG, "onCharacteristicChanged()");
String address = gatt.getDevice().getAddress();
long starttime = System.currentTimeMillis();
byte[] bytes = characteristic.getValue();
if(dataCallback != null){
dataCallback.onGetData(characteristic.getUuid().toString(),bytes);
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
sb.append(String.format("%02x", bytes[i]));
Log.d(TAG," time :" + starttime + " bytes:" + i + " =" + Integer.toHexString(bytes[i]));
}
Log.d(TAG, "出来的数据:" + sb.toString());
}
好了,android的Ble通讯到这里就基本通了。就可以跟ble设备正常的收发数据了
BleHelper类里面了,其他项目要用的话直接拿去用就好了,本来打算整理成一个框架的,现在想想其实也没不要了。