最近在公司做了个蓝牙项目,之前刚出道的时候做过个智能手环的项目,那时蓝牙模块是开发组长写的,我看的也是一知半解,很多不了解的东西现在看起来有些新心得,写下来记录下.
蓝牙4.0(BLE) 在安卓4.3(API 18)以上支持,相比传统的蓝牙,BLE更显著的特点是低功耗。是Android 蓝牙史上一大转折点
经过这次开发我把BLE使用只要分为4个步骤:
- 搜索蓝牙设备
- 连接设备/断开连接设备
- 设置服务的读/写/通知通道
- 操作写,读/接受通知
搜索蓝牙设备
首先要获取到手机蓝牙权限。
在应用程序manifest文件中添加如下代码,声明蓝牙权限。
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
但在android6.0以上做开发的同学需要注意在6.0以上还需加上位置的权限,否则会搜索不到任何设备。还需动态去获取权限(动态获取不详说)
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
2.判断手机是否支持蓝牙和蓝牙是否已打开,否跳转打开蓝牙设备
BluetoothAdapter=mBluetoothAdapte=BluetoothAdapter.getDefa ultAdapter();
if (mBluetoothAdapter == null) {
"此设备不支持蓝牙传输功能!"
}
else {
"此设备支持蓝牙传输功能!",
if (!mBluetoothAdapter.isEnabled())//判 断是否打开,否就跳转打开
{
Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
enableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
1).BluetoothAdapter 就代表本地蓝牙设备,用getDefaultAdapter()获取本地蓝牙设备,其返回值如果为空表示不存在蓝牙设备,否则就说明存在蓝牙设备
2).用isEnabled()方法来确定蓝牙设备是否打开,若没有打开返回值为false,需要重新调用startActivityForResult(enableIntent, REQUEST_ENABLE_BT);方法来打开蓝牙设备
3.扫描设备
主要通过获取本地的BluetoothAdapter 进行扫描和停止扫描,通过BluetoothAdapter 的回调返回扫描到的结果
回调
private BluetoothAdapter.LeScanCallback mLeScanCallback = new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
if (device != null){
Log.e("device.getName()",""+device.getName());//设备名称
Log.e("device.getName()",""+device.getAddress());//设备地址
};
扫描/停止扫描
/**
* 搜索蓝牙或停止搜索
* @param enable
*/
@SuppressLint("NewApi")
private void scanLeDevice(final boolean enable) {
if (enable) {
// Stops scanning after a pre-defined scan period.
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
}, SCAN_PERIOD);//SCAN_PERIOD=扫描时间
mScanning = true;
mBluetoothAdapter.startLeScan(mLeScanCallback);
} else {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
}
连接设备和断开连接
一般我会把连接设备/断开连接设备,设置服务的读/写/通知通道 操作读/写/接受通知, 操作读/写/接受通知这三个步骤放在Service,这样能更好的脱离界面做到随处可操作。
获取BluetoothAdapter 里的BluetoothDevice,通过BluetoothDevice和之前扫描得到的设备地址进行连接。通过BluetoothGatt回调返回结果,BluetoothGatt这个比较关键之后所有的操作都通过它返回结果
获取BluetoothAdapter
/**
* 初始化蓝牙服务
* @return
*/
public boolean initialize() {
// For API level 18 and above, get a reference to BluetoothAdapter
// through
// BluetoothManager.
if (mBluetoothManager == null) {
mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
if (mBluetoothManager == null) {
Log.e(TAG, "Unable to initialize BluetoothManager.");
return false;
}
}
mBluetoothAdapter = mBluetoothManager.getAdapter();
if (mBluetoothAdapter == null) {
Log.e(TAG, "Unable to obtain a BluetoothAdapter.");
return false;
}
return true;
}
获取BluetoothDevice并连接
/**
* 请求连接
*/
public boolean connect(final String address) {
if (mBluetoothAdapter == null || address == null) {
Log.e(TAG,"BluetoothAdapter not initialized or unspecified address.");
return false;
}
// Previously connected device. Try to reconnect. (先前连接的设备。 尝试重新连接)
if (mBluetoothDeviceAddress != null&& address.equals(mBluetoothDeviceAddress)
&& mBluetoothGatt != null) {
if (mBluetoothGatt.connect()) {
return true;
} else {
return false;
}
}
final BluetoothDevice device = mBluetoothAdapter
.getRemoteDevice(address);
if (device == null) {
Log.w(TAG, "Device not found. Unable to connect.");
return false;
}
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);
return true;
}
BluetoothGatt类
BluetoothGatt 是我们用的最多,她就像Android手机与BLE终端设备建立通信的一个大的管道,只有有了这个管道,我们才有了通信的前提所有的数据读写,通知等通道都是在这个管道传输。
BluetoothGatt 类
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status,int newState) {
if (newState == BluetoothProfile.STATE_CONNECTED) { //连接成功
mBluetoothGatt.discoverServices(); //连接成功后就去找出该设备中的服务(这方法比较重要必须调)
BleConnetInter.sendCallBack(1);
Log.e(TAG,"Ble connect suess");
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) { //连接失败
BleConnetInter.sendCallBack(3);
Log.e(TAG,"Ble connect fail");
}
}
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.e(TAG,"onServicesDiscovered sucess");
displayGattServices();//发现服务回调,在这设置服务,读,写,通知通道
} else {
}
Log.e(TAG, "onServicesDiscovered received: " + status);
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
System.out.println("onCharacteristicRead");
if (status == BluetoothGatt.GATT_SUCCESS) {
//读通道返回数据
}
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt,
BluetoothGattDescriptor descriptor, int status) {
//写通道
}
@SuppressLint("LongLogTag")
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
//
byte[] data = characteristic.getValue();//接受终端返回的通知数据
if (data != null && data.length>0){
ParaseBleData(data);//解析数据
}
}
@Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
System.out.println("rssi = " + rssi);
}
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
System.out.println("--------write success----- status:" + status);
};
};
设置服务的读写通知通道
首先了解些这些关键词,一个BLE终端可以包含多个Service, 一个Service可以包含多个Characteristic,每个Characteristic可以说是一个通道。Characteristic是比较重要的,也就是我们所说的通信通道,是手机与BLE终端交换数据的关键,读取设置数据等操作都是操作Characteristic的相关属性。
每个Service有一个唯一的UUID,每个Characteristic也都有一个唯一的UUID,我们就是通过硬件工程师提供的BLE终端的UUID确定我们需要的Service和Characteristic
当连接上设备室 BluetoothGatt 类会回到连接成功的通知,然后掉发现服务的方法BluetoothGatt.discoverServices(); (上面BluetoothGatt 例子里有代码提到),发现服务后在BluetoothGatt 里也有回调,在回调里获取和解析Service和Characteristic,这时我们需要根据提供的UUID去确定我们需要的Service,Characteristic
/**
*分析服务列表
*/
public void displayGattServices(){
List<BluetoothGattService> listServices = mBluetoothGatt.getServices()
for (BluetoothGattService service : listServices){
if (service != null){
Log.e(TAG,""+service.getUuid());//获取服务的UUID
if (service.getUuid().toString().equals(HANDBAND_SERVER)){//UUID比对
ArrayList<BluetoothGattCharacteristic> mGattCharacteristics =
(ArrayList<BluetoothGattCharacteristic>) service.getCharacteristics();//获取Characteristics
for (BluetoothGattCharacteristic characteristics : mGattCharacteristics){
if (characteristics != null){
if (HANDBAND_WRITE.equals(characteristics.getUuid().toString())){//比对写通道(characteristics)
// Log.e(TAG,"if characteristicsUUID:"+mGattCharacteristics.size());
writeBluetoothGattCharacteristic = characteristics;
}else if (HANDBAND_NOTIFICATION.equals(characteristics.getUuid().toString())){//比对通知通道(characteristics)
notifyBluetoothGattCharacteristic = characteristics;
//setCharacteristicNotification(true);
setNotify(notifyBluetoothGattCharacteristic);//设置通知通道
setEnableNotify(notifyBluetoothGattCharacteristic, true);设置通知通道
}
}
Log.e(TAG,"for characteristicsUUID:"+characteristics.getUuid());//service.getUuid();
}
}
}
}
}
设置通知通道
// 设置可通知
public boolean setNotify(BluetoothGattCharacteristic data_char) {
if (!mBluetoothAdapter.isEnabled()) { // 没有打开蓝牙
return false;
}
if (data_char == null) {
return false;
}
if (0 != (data_char.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY)) { // 查看是否带有可通知属性
mBluetoothGatt.setCharacteristicNotification(data_char, true);
BluetoothGattDescriptor descriptor = data_char.getDescriptor(UUID
.fromString(CLIENT_CHARACTERISTIC_CONFIG));
//
descriptor
.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
}else if (0 != (data_char.getProperties() & BluetoothGattCharacteristic.PROPERTY_INDICATE)){
mBluetoothGatt.setCharacteristicNotification(data_char, true);
BluetoothGattDescriptor descriptor = data_char.getDescriptor(UUID
.fromString(CLIENT_CHARACTERISTIC_CONFIG));
//
descriptor
.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
}
return true;
}
// 设置允许通知
public boolean setEnableNotify(BluetoothGattCharacteristic data_char,
boolean enable) {
if (!mBluetoothAdapter.isEnabled()) { // 没有打开蓝牙
return false;
}
if (data_char == null) {
return false;
}
mBluetoothGatt.setCharacteristicNotification(data_char, enable);
return true;
}
/**
* 设置可读
* @param characteristic
*/
public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
mBluetoothGatt.readCharacteristic(characteristic);
}
4. 操作读/写/接受通知
1.写操作
在发现和解析服务里我们获取到写的Characteristic,通过Characteristic进行对BLE终端设备写的操作
/**
* 写操作
* @param
*/
public synchronized void wirteCharacteristic(byte[] data) {
if (mBluetoothAdapter == null || mBluetoothGatt == null
|| writeBluetoothGattCharacteristic == null) {
Log.w(TAG, "BluetoothAdapter not initialized");
return;
}
writeBluetoothGattCharacteristic.setValue(data);
Log.e("wirteCharacteristic",""+writeBluetoothGattCharacteristic.getValue());
String dataStr = "";
for(int i =0;i<data.length;i++){
dataStr =dataStr+" , "+data[i];
}
Log.e(TAG,"data:"+dataStr);
mBluetoothGatt.writeCharacteristic(writeBluetoothGattCharacteristic);
}
2.读/接受通知的消息都是在BluetoothGatt类里返回。
到此Ble 的基本 操作已完成,蓝牙说白了也就是一个信息传输通道,只要掌握了这四个步骤也就可以对她进行操作,无非就是扫描,连接,向终端设备写指令,接受终端指令。
在这我也跟家分享下在开发过程中遇到小坑,我遇到一个坑就是硬件工程师提供的指令协议不正确,因为硬件方面我们是购买别人的成品,他们给我的协议没更新,我按协议给终端发送个设置信息的指令,终端返回成功,通过查询指令查出来结果也是跟我设置的结果一样,但设置就是无效,将协议跟指令来回比对也没发现问题,因为这个设置涉及到几个步骤又来回测试步骤,最后都没能成功,因为设备是购买的成品不能连调,,但他们有成品的APK,通过他们APk设置完成没问题,这是怀疑是不是协议出问题,但不能确定,购买的成品沟通起来也麻烦,最后把发的指令和返回的指令一一列出来,做出个文档让他们比对下最后发现问题,,协议更改了没跟新。其中不想求别人最后是用抓包工具,其中朋友推荐了几篇蓝牙抓包文章,还没研究好,研究好再写出来分享。