安卓蓝牙BLE设备开发
前段时间做了一个有关于安卓蓝牙BLE设备的开发项目,主要的功能包括了搜索蓝牙ble设备和ble设备的数据读写等等,本篇博客用于记录安卓蓝牙ble设备的通信的细节。
其实关于BLE设备的通信在API中已经讲地比较清楚了,这里只是做一个总结,如果要进行BLE设备的开发,首先可以阅读API.
BLE有关API
BLE设备的定义和特点
BLE-维基百科
对于BLE设备的定义,我们只需要看维基百科的说明就好了,简单的说明一下BLE的原理。
首先对于BLE硬件来说,它能够决定什么时候去发出请求和外接的设备进行连接,安卓官方称之为advertisement,市面上见到的BLE设备,比如蓝牙音响,一打开就能够被手机检测到,原因是其硬件上设定了会一直发出advertisement,直到连接上设备为止。但是实际上BLE设备本身是可以控制自己要不要发出advertisement的,如果BLE设备没有发出这个advertisement,那么我们是搜索不到它的。
对于BLE设备还有一个坑,那就是BLE设备只能连接一个设备,也就是说,一旦当BLE设备连接了一台手机后,其他的手机上就无法搜索到这台BLE设备了。我猜测应该是BLE设备连接之后就不会发出advertisement了,导致其他设备搜索不到。
安卓上进行BLE开发的步骤
1、AndroidManifest中声明权限
对于BLE开发我们需要声明两个权限
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
其中第一个权限是只要我们进行与蓝牙相关的操作都需要声明的,比如你的APP只是希望能够被别人检测到,然后通过蓝牙交换数据,就需要声明这一个。
第二个权限是如果你的设备需要主动搜索蓝牙设备,或者是对蓝牙的某些设置进行更改就需要这个权限,一般来说我们在进行BLE开发的时候会同时声明这两个权限。
因为不是所有的手机上都有蓝牙BLE功能,需要API18(Android4.3)以上才能进行BLE的开发,所以我们可以声明features过滤掉不满足要求的设备
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
2、获取BluetoothAdapter
BluetoothAdapter是用来获取可以连接的BLE设备列表的一个类,想要和BLE设备通信当然需要先能够搜索到可用的BLE设备。安卓获取BluetoothAdapter的实例的方式很简单。首先通过调用系统服务获得BluetoothManager的实例,然后调用BluetoothManager的getAdapter()方法就可以了,代码如下:
final BluetoothManager bluetoothManager =(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
当然,这里的mBluetoothAdapter我们最好保存为类的数据成员,因为之后我们还会用到它去获取当前可连接的BLE设备的列表。
3、开启蓝牙
得到了BluetoothAdapter的实例之后我们就可以用其中的方法去搜索设备了,但是在此之前,我们首先要打开手机上的蓝牙功能,这个可以通过用户自己找到蓝牙去打开,但是这样很明显会影响用户体验。没有人想在使用APP的时候还需要去找到系统的蓝牙并打开。安卓API为我们提供了在APP中开启系统蓝牙的方法:即调用BluetoothAdapter中的enable()方法。
为了严谨,我们需要判断上一步获得的mBluetoothAdapter是否为空(只有当设备不支持BLE的时候这个实例才会为null)
public void enable() {if(mBluetoothAdapter == null || ! mBluetoothAdapter.isEnabled()) {mBluetoothAdapter.enable();}
}
这里我们调用了mBluetoothAdapter.isEnabled(),避免重复打开蓝牙。
4、搜索BLE设备
打开蓝牙之后,我们终于可以搜索BLE设备了。这个部分需要注意的问题主要是搜索时间的问题,因为搜索BLE设备是一个很费电的过程,所以搜索的时间不应该设置太长。在开始搜索的同时,我们应该设置一个Handler,然后调用Handler的postDelayed(Runnable r)方法去设定在搜索一定时间后停止搜索。
将搜索BLE设备的过程独立写成一个方法,这样如果需要多次搜索的话只需要再次调用此方法即可。另外应该设立一个标志位mScanning,在调用搜索方法的时候应该判断标志位,如果现在正在搜索就不应该再次进行搜索。
public void scanLeDevice(boolean enable) {if(enable) {mHandler.postDelayed(new Runnable() {@Overridepublic void run() {mScanning = false;mBluetoothAdapter.stopLeScan(mLeScanCallback);mBLEListActivity.setTextInfo();}}, SCAN_PERIOD);mScanning = true;mBluetoothAdapter.startLeScan(mLeScanCallback);} else {mScanning = false;mBluetoothAdapter.stopLeScan(mLeScanCallback);}
}
这段方法的核心主要是两个方法:mBluetoothAdapter.startLeScan(mLeScanCallback)和
mBluetoothAdapter.stopLeScan(mLeScanCallback)。分别对应开始BLE设备的搜索和停止BLE设备的搜索。需要注意的是这两个方法只针对于BLE设备,对于普通的蓝牙设备,调用这个方法是搜索不到的。
但是你可能要问了,我们怎么才能得到搜索的结果呢?请注意,在这两个方法中都有一个共同的参数mLeScanCallback,这个mLeScanCallback就是我们获取搜索结果的载体。
让我们一起来看看mLeScanCallback的定义
private BluetoothAdapter.LeScanCallback mLeScanCallback =new BluetoothAdapter.LeScanCallback() {@Overridepublic void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {if(!mBLEListActivity.mBLEArrayList.contains(device)) {mBLEListActivity.addBluetoothItem(device);mBLEListActivity.runOnUiThread(new Runnable() {@Overridepublic void run() { mBLEListActivity.mBLEAdapter.notifyDataSetChanged();mBLEListActivity.mListView.setAdapter(mBLEListActivity.mBLEAdapter);} });}}};
可以看到mLeScanCallback实际上是一个BluetoothAdapter.LeScanCallback的实例。LeScanCallback中包含了一个回调方法onLeScan(),我们通过重写这个方法去进行一系列操作。
到这里我们基本上就可以理解了,实际上BLE的搜索的实现是通过回调方法完成的,当检测到BLE设备的时候,就调用一次onLeScan()方法,然后我们应该在里面去保存搜索到的device,比如放在一个List中。一般的处理方法是将搜索到的device放在ListView中进行显示,然后响应用户的点击进行连接。这不属于BLE开发的范畴,所以暂不赘述。
5、与指定BLE设备进行通信
通过第4步我们能够搜索到我们想要的device,现在应该开始通信了。通信的第一步就是和BLE设备建立连接。安卓用BluetoothGatt类来管理这种连接,首先我们需要获得一个BluetoothGatt实例,只需要调用BluetoothDevice的connectGatt()方法即可。
if(mBLEDevice != null) {Log.d(TAG, "建立连接");mBluetoothGatt = mBLEDevice.connectGatt(mBLEService, true, mGattCallback);
} else {Log.e(TAG, "没找到特定的BLE设备,连接无法建立");
}
这里有三个参数,重点说一下第三个参数mGattCallback,这时连接过程中最为重要的一个参数,其定义如下(此部分为安卓官方的代码)
// A service that interacts with the BLE device via the Android BLE API.
public class BluetoothLeService extends Service {private final static String TAG = BluetoothLeService.class.getSimpleName();private BluetoothManager mBluetoothManager;private BluetoothAdapter mBluetoothAdapter;private String mBluetoothDeviceAddress;private BluetoothGatt mBluetoothGatt;private int mConnectionState = STATE_DISCONNECTED;private static final int STATE_DISCONNECTED = 0;private static final int STATE_CONNECTING = 1;private static final int STATE_CONNECTED = 2;public final static String ACTION_GATT_CONNECTED ="com.example.bluetooth.le.ACTION_GATT_CONNECTED";public final static String ACTION_GATT_DISCONNECTED ="com.example.bluetooth.le.ACTION_GATT_DISCONNECTED";public final static String ACTION_GATT_SERVICES_DISCOVERED ="com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";public final static String ACTION_DATA_AVAILABLE ="com.example.bluetooth.le.ACTION_DATA_AVAILABLE";public final static String EXTRA_DATA ="com.example.bluetooth.le.EXTRA_DATA";public final static UUID UUID_HEART_RATE_MEASUREMENT =UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT);// Various callback methods defined by the BLE API.private final BluetoothGattCallback mGattCallback =new BluetoothGattCallback() {@Overridepublic void onConnectionStateChange(BluetoothGatt gatt, int status,int newState) {String intentAction;if (newState == BluetoothProfile.STATE_CONNECTED) {intentAction = ACTION_GATT_CONNECTED;mConnectionState = STATE_CONNECTED;broadcastUpdate(intentAction);Log.i(TAG, "Connected to GATT server.");Log.i(TAG, "Attempting to start service discovery:" +mBluetoothGatt.discoverServices());} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {intentAction = ACTION_GATT_DISCONNECTED;mConnectionState = STATE_DISCONNECTED;Log.i(TAG, "Disconnected from GATT server.");broadcastUpdate(intentAction);}}@Override// New services discoveredpublic void onServicesDiscovered(BluetoothGatt gatt, int status) {if (status == BluetoothGatt.GATT_SUCCESS) {broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);} else {Log.w(TAG, "onServicesDiscovered received: " + status);}}@Override// Result of a characteristic read operationpublic void onCharacteristicRead(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic,int status) {if (status == BluetoothGatt.GATT_SUCCESS) {broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);}}...};
...
}
代码比较繁杂,不需要仔细看。我们只需要知道mGattCallback是一个BluetoothGattCallback的实例,里面有很多的回调方法就够了。下面我会说到一些比较重要的回调方法,这些回调方法主要是用于检测通信过程中状态的改变的。
连接的过程中我们传入了mGattCallback,然后mGattCallback中的回调方法是需要我们自己去重写的,在对应不同状态的时候去进行不同的动作。比较重要的方法有以下几个:
onConnectionStateChange()
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {super.onConnectionStateChange(gatt, status, newState);if(newState == BluetoothProfile.STATE_CONNECTED) {mBluetoothGatt = gatt;if(mBluetoothGatt != null) {mBluetoothGatt.discoverServices();} else {Log.d(TAG, "mBluetoothGatt为空");}} else if(newState == BluetoothProfile.STATE_DISCONNECTED) {mActivity.runOnUiThread(new Runnable() {@Overridepublic void run() {Toast.makeText(mActivity, "连接断开,请检查设备", Toast.LENGTH_LONG).show();} });}
}
这个方法主要检测的是设备的连接状态的改变,每当设备连接状态发生改变的时候,即从连接到断开或者从断开到连接的时候都会调用此方法。我们应该判断newState的值,然后在连接成功的时候去调用mBluetoothGatt.discoverServices()(之后我会说明这个方法的作用),在连接断开的时候进行清理,并且告知用户连接断开了。
onServiceDiscovered()
public void onServicesDiscovered(BluetoothGatt gatt, int status) {super.onServicesDiscovered(gatt, status);if(status == BluetoothGatt.GATT_SUCCESS) {mActivity.runOnUiThread(new Runnable() {@Overridepublic void run() {Toast.makeText(mActivity, "已连接", Toast.LENGTH_LONG).show();} });try {getBtgService();getBtgCharacteristic();mBLEAudioTrack.play();if(mReadCharacteristic != null) {mBluetoothGatt.setCharacteristicNotification(mReadCharacteristic, true);}}catch(Exception e) {e.printStackTrace();}} else {Log.e("TAG", "连接失败");}
}
刚才我们提到了在连接成功时候需要调用mBluetoothGatt.discoverServices(),为什么呢?这就涉及到蓝牙BLE通信上的一个设计,蓝牙的数据传输并不是一位一位传过来的,而是通过Characteristic进行封装后传过来的,一个Characteristic中包装了一组数据。在Characteristic上层,还有Service,封装了多个Charateristic。在实际的通信中,我们需要首先获取BluetoothGattService,然后通过BluetoothGattService的实例去获取BluetoothGattCharacteristic。
而在获取BluetoothGattService之前,我们需要先获取服务,这个获取服务相当于是告诉蓝牙设备我要开始进行数据交换了,请你准备好,mBluetoothGatt.discoverServices()就是完成这个功能的。当找到了服务之后,安卓就会自动调用上面的onServicesDiscovered()方法,然后我们就可以在里面去获取BluetoothGattService和BluetoothGattCharacteristic。
怎么获取Service和Characteristic呢?
这里用到了UUID,关于UUID的基本知识请参照维基百科-UUID
在Java中UUID通过java.util.UUID.fromString(String str)来产生的,在这一部分需要和BLE硬件相适应,确定BLE设备上发送数据和接收数据的Service和Characteristic的UUID号。以下是我所使用的BLE设备的Service和Characteristic的获取方式
首先用UUID号得到UUID对象
private static final String SERVICE_NAME = "0000FFF0-0000-1000-8000-00805F9B34FB";
private static final UUID SERVICE_UUID = UUID.fromString(SERVICE_NAME);
private static final String CHARC_NAME = "0000FFF6-0000-1000-8000-00805F9B34FB";
private static final UUID CHARC_UUID = UUID.fromString(CHARC_NAME);
获取Service实例
public void getBtgService() {mBluetoothGattService = mBluetoothGatt.getService(SERVICE_UUID);Log.i(TAG, "get service"+mBluetoothGattService.toString());
}
获取Characteristic实例
public void getBtgCharacteristic() {mReadCharacteristic = mBluetoothGattService.getCharacteristic(CHARC_UUID);mWriteCharacteristic = mBluetoothGattService.getCharacteristic(CHARC_UUID);Log.i(TAG, "get characteristic"+mReadCharacteristic.toString());List<BluetoothGattDescriptor> descriptors = mReadCharacteristic.getDescriptors();for (BluetoothGattDescriptor bgp : descriptors) {Log.i(TAG, "setCharacteristicNotification: " + bgp);bgp.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);mBluetoothGatt.writeDescriptor(bgp);}
}
在获取mReadCharacteristic的时候需要注意,我们通过mReadCharacteristic.getDescriptors()获取了它的描述符,然后对下面的所有描述符都设置了属性,这样做的原因是因为之后我们需要回调方法onCharacteristicChanged(),如果不这样设置,那么当读数据成功后不会进入onCharacteristicChanged()这个回调方法中,这样我们就无法获得之后的数据了。所以为了让读操作能够一直持续,我们需要设置其Descriptor。
注意,在获取了Service和Characteristic之后实际上我们就可以开始进行数据通信了,我在这里调用了方法
if(mReadCharacteristic != null) {mBluetoothGatt.setCharacteristicNotification(mReadCharacteristic, true);}
通过这个方法,现在就能监听安卓设备是否收到BLE设备发来的数据,如果收到了发来的数据,会调用方法onCharacteristicRead(),下面介绍这个方法。
onCharacteristicRead()
public void onCharacteristicRead(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic,int status) {if (status == BluetoothGatt.GATT_SUCCESS) {readCharacteristicValue(mReadCharacteristic);mBluetoothGatt.setCharacteristicNotification(mReadCharacteristic, true);}}
onCharacteristicRead()这个方法会在接收数据的时候被调用,在这里面我们可以去得到BLE设备发过来的数据,通过characteristic.getValue()即可。这里对数据的操作被封装在readCharacteristicValue(mReadCharacteristic)中了。
注意,在这个方法中,一定要再次调用mBluetoothGatt.setCharacteristicNotification(mReadCharacteristic, true);这样才能保证下一次接收到数据时又能进入此回调方法。这样就能保证连续地接收BLE设备的数据了。
onCharacteristicWrite()
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic,int status) {switch(status) {case BluetoothGatt.GATT_SUCCESS:Log.d(TAG, "write data success");break;case BluetoothGatt.GATT_FAILURE:Log.d(TAG, "write data failed");case BluetoothGatt.GATT_WRITE_NOT_PERMITTED:Log.d(TAG, "write not permitted");}super.onCharacteristicWrite(gatt, characteristic, status);
}
如果需要向BLE设备写数据其实是与读数据相似的处理,就不赘述了。
6、结束通信时,释放资源
这一部分比较简单,只需要调用如下的close()方法即可
public void close() {if (mBluetoothGatt == null) {return;}mBluetoothGatt.close();mBluetoothGatt = null;
}
结语
这篇博客作为我博客生涯的一个开始,以后我会在学习到新的知识的时候尽量多地去总结,会保持博客的更新。
安卓蓝牙BLE设备开发相关推荐
- 二、搜索蓝牙并连接(安卓蓝牙ble教程)
1.MainActivity.java 注:如果复制代码进项目时显示红色,请按ALT+ENTER键导包(import class) package club.stm32;import android. ...
- 安卓连接ble蓝牙设备教程(目录)
安卓连接ble蓝牙设备教程(目录) 零.新建android工程(安卓蓝牙ble教程) 一.权限和build.gradle配置并开启蓝牙(安卓蓝牙ble教程) 二.搜索蓝牙并连接(安卓蓝牙ble教程) ...
- 安卓基于BLE的蓝牙开发入门
BLE蓝牙开发简单入门 BLE背景介绍 引言 BLE简介 Gatt协议以及必备知识 蓝牙开发涉及的API介绍 BLE实战准备 真机调试 权限准备 写两个简单的页面 扫描设备主界面 扫描设备信息界面 实 ...
- 安卓读取蓝牙BLE设备信息
安卓读取蓝牙BLE设备信息 简介 轮询方式代码实现 监听广播方式代码实现 简介 目前,许多项目都会涉及与BLE设备进行交互的功能,接下来说一下读取BLE设备信息的具体实现流程.安卓BLE相关接口介绍详 ...
- 基于intel芯片的安卓蓝牙4.0 BLE通信总结
基于intel芯片的安卓蓝牙4.0 BLE通信问题总结 使用设备: 台电 x98 air 3G 系统:安卓4.4.4 系统搭建: 1.安装ADT驱动,可以搜索intel_mobile_usb_driv ...
- BLE蓝牙HID设备开发(BL602)
总述 BLE蓝牙在物联网中使用很广泛,许多WiFi物联网芯片都带有BLE蓝牙,天猫精灵找队友也是有使用BLE蓝牙广播实现的.BLE蓝牙缺点是不适合大数据传输的场景. BLE蓝牙 BLE蓝牙主机开发基本 ...
- 蓝牙BLE(协议栈、OSAL、蓝牙APP工具)
目录 蓝牙配对和绑定 蓝牙4.0 BLE 信道(RF Channel) BLE协议栈分层 PHY层(Physical layer 物理层) LL层(Link Layer 链路层) HCI层(Host ...
- 安卓蓝牙实现即时通讯功能
安卓蓝牙实现即时通讯功能 本demo是<Android智能穿戴设备开发指南>书中的一块内容,实现了两台手机基于蓝牙进行即时通讯的功能. demo演示如下: 结构图 主要代码 MainAct ...
- 安卓蓝牙开发的几个版本区别
不得不说,这有时候真的坑爹. 你 搜索安卓蓝牙开发,有的大谈4.0结果还是什么socket那些,这是安卓4.0,不是蓝牙4.0啊 安卓4.2以及以下才是这种 4.3以后就可以ble了,低功耗,更碉堡 ...
最新文章
- 超越英伟达的,不会是另一款GPU!中国公司发布首款数据流AI芯片
- 输入一个正整数,求它各位数的数字之和
- 最大玻尔兹曼分布的mASK信号在AWGN信道下的容量计算
- python查找指定文件夹_python实现在目录中查找指定文件的方法
- jeecms 代码生成 Tools
- css不继承上级样式_这个笔记《CSS基本概念》,让菜鸟轻松学会给网页穿外衣
- SPD软件(医用耗材管理系统)应用效果分析
- 课程思政与c语言程序设计,C语言程序设计课程思政教学改革教学设计.doc
- 2021年起,WPS Office纳入全国计算机等级考试二级考试软件
- 80c51流水灯程序汇编语言,stc89c51单片机流水灯程序.doc
- MPU6050 加速计滤波
- 王道考研操作系统复习笔记
- ThinkPad笔记本无法调节亮度
- hive 中创建表的三种方式
- android声音播放函数双声道合并,Android音频编辑之音频合成功能
- 全国青少年编程等级考试python一级真题2022年3月(含题库答题软件账号)
- 蓝桥杯——算法基础 逗志芃的暴走PYTHON
- 如何实现游戏中的段位排行榜?
- 家庭nas方案_openmediavault入门:家庭NAS解决方案
- 装的系统没有截图和计算机工具栏,win10百度浏览器工具栏找不到“截图”按钮怎么办...