Android开发——蓝牙

---恢复内容开始---

## 前言
孤芳自赏,一揽芳华;
人情冷暖,自在人心;
登高远眺,望步止前;
喜笑言开,欺人骗己。
上篇文章介绍了基本的蓝牙使用,书写的demo也不是很完善,希望各位大神能够改正。这两周由于公司开发的进度,还有个人的一些原因,没有及时写这篇文章来介绍我写的这个demo。这是对自己的一种不负责。这两周,boss让我按照OCP原则重构代码,改了一遍有一遍,到后来还是boss给我弄了个demo参照着并帮助我把代码重构完毕。对于面向对象的三大特征,六大原则,二十三种设计模式,这里我就不显丑了,等我把书看完在一 一介绍。

正文

一、打开蓝牙并扫描蓝牙设备

private void initBluetoothAdapter() {
if (null == mBluetoothManager) {
mBluetoothManager = (BluetoothManager) getSystemService(BLUETOOTH_SERVICE);// 获取系统服务
if (null != mBluetoothManager) {
mBluetoothAdapter = mBluetoothManager.getAdapter();//获得蓝牙适配器(Adapter)
}
} if (null == mBluetoothAdapter) {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();//以防Manager为null,再次获取蓝牙适配器
if (null == mBluetoothAdapter) {
Toast.makeText(this, R.string.noBluetooth, Toast.LENGTH_SHORT).show();
return;
}
}
//查看手机是否打开了蓝牙,如果没有打开,提示用户打开蓝牙
if (!mBluetoothAdapter.isEnabled()) {
if (!mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivity(enableBtIntent);
}
}
}

蓝牙打开后就是扫描蓝牙设备,发现蓝牙了

if (null != mBluetoothAdapter)
mBluetoothAdapter.startDiscovery();//扫描蓝牙

这时需要注册下Receiver,接收发现蓝牙的信息

private void register() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothDevice.ACTION_FOUND);//扫描到蓝牙设备注册的action
intentFilter.addAction(BluetoothConstant.ACTION_DATA_AVAILABLE);//蓝牙启动后,返回数据时的监听
intentFilter.addAction(BluetoothConstant.ACTION_DATA_COMPLETE);//蓝牙数据返回完成后,最后返回的数据
registerReceiver(mBroadcastReceiver, intentFilter);
}
  • 对扫描到的蓝牙进行处理,把需要的蓝牙的名字保存下来,并链接Service,在Service中对蓝牙进行连接,并把一些给蓝牙写入命令,处理蓝牙回调的都放到Service中,减小Activity页面的开销。

二、链接蓝牙设备

在蓝牙中有一个至关重要的类——BluetoothGatt,他是对蓝牙进行操作的类,给蓝牙写入Write命令,Read命令,都需要用到它,他的初始化适合蓝牙链接密不可分的。

在Service中封装的方法,有一个是用来链接蓝牙的,并在这个方法中初始化BluetoothGatt。

/**
* 链接蓝牙
*
* @param address 蓝牙地址
* @return 链接是否成功
*/
public boolean connect(BluetoothAdapter bluetoothAdapter,String address) {
this.mBluetoothAdapter = bluetoothAdapter;
if (null == address || null == mBluetoothAdapter) {
Log.e(TAG, "connect Bluetooth address or BluetoothAdapter is null that not initial");
return false;
} if (mDeviceAddress != null && address.equals(mDeviceAddress)
&& mBluetoothGatt != null) {
//链接过的设备再次链接
Log.i(TAG, "connect existing device");
return mBluetoothGatt.connect();
}
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
if (null != device) {
mBluetoothGatt = device.connectGatt(this, false, mGattCallback);//第一次链接蓝牙并初始化BluetoothGatt
mDeviceAddress = address;
this.mDeviceName = device.getName();
}else {
Log.i(TAG,"device is null");
}
return true;
}
  • 在链接蓝牙中又个坑,这个坑也不是每次出现,也不是每天出现,而是偶尔出现几次,那就是链接过的蓝牙,当你断开再次链接会链接不上,有时会显示扫描不到所有蓝牙设备,我没有很好的解决办法,临时办法就是关闭蓝牙,重新在进行蓝牙扫描并链接。

三、注册通道

对于蓝牙回调的一些方法和回调处理在上篇文章中已经大致写了一些,这里就不再详细的介绍了。

当蓝牙链接成功就需要扫描蓝牙的服务通道

mBluetoothGatt.discoverServices();

扫描到蓝牙服务后,遍历服务中的所有通道并选择你需要的通道进行注册。

/**
* 获取特征值(特征UUID) 用来做与蓝牙通讯的唯一通道
*
* @param gattServices BluetoothGattService
*/
private void displayGattServices(List<BluetoothGattService> gattServices) {
if (gattServices == null)
return;
String uuid;
/**
*这里说一下,names,uuids,receiveUUIDs必须一 一对应(示例在下方),如果有不需要的用设备名称补气,如若为"" 程序可能出错,通道注册失败。
*/
String[] names = BluetoothConstant.getInstance().getDeviceNames();
String[] uuids = BluetoothConstant.getInstance().getNotificationUUIDs();
String[] receiveUUIDs = BluetoothConstant.getInstance().getReceiveNotificationUUIDs();
//从所有服务里面,找到能够和蓝牙通信的有效服务UUID
for (BluetoothGattService gattService : gattServices) {
List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics();
//从服务中找到自己所需的特征值BluetoothGattCharacteristic
for (BluetoothGattCharacteristic characteristic : gattCharacteristics) {
uuid = characteristic.getUuid().toString();
String device = mBluetoothGatt.getDevice().getName();
for (int i = 0; i < names.length; i++) {
if (uuid.contains(uuids[i]) && device.equals(names[i])) {
//这里就是需要的特征值了
Log.i(TAG,">>>><<<<< \n name ="+names[i]+"\n"+"uuid = "+uuid+"\n"+"receiveUUID = "+receiveUUIDs[i]);
if (!uuid.contains(receiveUUIDs[i])) {
this.mCharacteristic = characteristic;//这里为什么要加一个if,是因为有的蓝牙数据返回和写入命令是一个通道;有的数据返回是一个通道,写入命令是另一个通道;当用返回数据的通道写入命令是,命令是写不进去的。所以,当不是蓝牙返回数据的通道时,把特征值全局化。
}
notification(characteristic);
}
}
}
}
}
//这里说一下,为什么这么写,刚开始注册通道的时候只写了mBluetoothGatt.setCharacteristicNotification(gattCharacteristic, true)一句,然而并不是每次都能注册成功的,在网上查找了几个方法,只有这个方法比较用,这样,通道就不会注册错误了。
private void notification(BluetoothGattCharacteristic gattCharacteristic) {
boolean success = mBluetoothGatt.setCharacteristicNotification(gattCharacteristic, true); Log.i(TAG,"notification characteristic permissions = "+gattCharacteristic.getPermissions()+"\n"
+"characteristic properties = "+ gattCharacteristic.getProperties()+"\n"+
"characteristic uuid = "+gattCharacteristic.getUuid());
if (success) {
for (BluetoothGattDescriptor dp : gattCharacteristic.getDescriptors()) {
if (dp != null) {
if ((gattCharacteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_NOTIFY) != 0) {
dp.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
}
mBluetoothGatt.writeDescriptor(dp);
try {
Thread.sleep(200);//这里为什么会休眠200ms,是因为,注册速度太快,并不是所有的通道都能注册成功,加个延迟,会好些
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}

下面是蓝牙的一些基本设置,写在了BluetoothConstant类中,这个类主要是统一写一些蓝牙相关的东西,例如名称,注册通道,接受数据的通道,命令等。

//设备名称
private final String[] deviceNames = new String[]{
DEVICE_NAME_ONE,
DEVICE_NAME_TWO,
DEVICE_NAME_TWO,
DEVICE_NAME_THREE
};
//需要注册蓝牙Characteristic的通道(读,写,通知)
private final String[] notificationUUIDs = new String[]{
"0001",
"0002",
"0003",
"0004"
};
//只接收数据的通道,不进行读写,不是UUID的是占位,占位的也可以是其他字符窜,只要不是""就可以。
private final String[] receiveNotificationUUIDs = new String[]{
"DEVICE_NAME_ONE",
"DEVICE_NAME_TWO",
"0000fff4-0000-1000-8000-00805f9b34fb",
"DEVICE_NAME_THREE"
};

对于隔了这么长时间才写这篇文章,我深感抱歉。在蓝牙开发中,最可恶的是蓝牙提供商,给了协议,但是没有具体命令,这需要我们自己去拼写命令,然而在命令的最后一位是校验位,有奇偶检验,有异或校验,有CRC8校验,有CRC16校验,这里我给大家提供福利,把这异或校验和CRC8校验记录下来:

/**
* 异或校验位计算
* 去掉起起始位,从第二位开始算
*/
private void getXOR(int[] hexs){
int xor = 0;
for (int i = 0;i<hexs.length;i++){
xor = xor^hexs[i];
Log.i(TAG,"XOR ="+xor);
}
Log.i(TAG,"Hex XOR = "+xor);
} // CRC8校验
private static byte[] crc8_tab = { (byte) 0, (byte) 94, (byte) 188, (byte) 226, (byte) 97, (byte) 63, (byte) 221, (byte) 131, (byte) 194, (byte) 156, (byte) 126, (byte) 32, (byte) 163, (byte) 253, (byte) 31, (byte) 65, (byte) 157, (byte) 195, (byte) 33, (byte) 127, (byte) 252, (byte) 162, (byte) 64, (byte) 30, (byte) 95, (byte) 1, (byte) 227, (byte) 189, (byte) 62, (byte) 96, (byte) 130, (byte) 220, (byte) 35, (byte) 125, (byte) 159, (byte) 193, (byte) 66, (byte) 28, (byte) 254, (byte) 160, (byte) 225, (byte) 191, (byte) 93, (byte) 3, (byte) 128, (byte) 222, (byte) 60, (byte) 98, (byte) 190, (byte) 224, (byte) 2, (byte) 92, (byte) 223, (byte) 129, (byte) 99, (byte) 61, (byte) 124, (byte) 34, (byte) 192, (byte) 158, (byte) 29, (byte) 67, (byte) 161, (byte) 255, (byte) 70, (byte) 24,
(byte) 250, (byte) 164, (byte) 39, (byte) 121, (byte) 155, (byte) 197, (byte) 132, (byte) 218, (byte) 56, (byte) 102, (byte) 229, (byte) 187, (byte) 89, (byte) 7, (byte) 219, (byte) 133, (byte) 103, (byte) 57, (byte) 186, (byte) 228, (byte) 6, (byte) 88, (byte) 25, (byte) 71, (byte) 165, (byte) 251, (byte) 120, (byte) 38, (byte) 196, (byte) 154, (byte) 101, (byte) 59, (byte) 217, (byte) 135, (byte) 4, (byte) 90, (byte) 184, (byte) 230, (byte) 167, (byte) 249, (byte) 27, (byte) 69, (byte) 198, (byte) 152, (byte) 122, (byte) 36, (byte) 248, (byte) 166, (byte) 68, (byte) 26, (byte) 153, (byte) 199, (byte) 37, (byte) 123, (byte) 58, (byte) 100, (byte) 134, (byte) 216, (byte) 91, (byte) 5, (byte) 231, (byte) 185, (byte) 140, (byte) 210, (byte) 48, (byte) 110, (byte) 237,
(byte) 179, (byte) 81, (byte) 15, (byte) 78, (byte) 16, (byte) 242, (byte) 172, (byte) 47, (byte) 113, (byte) 147, (byte) 205, (byte) 17, (byte) 79, (byte) 173, (byte) 243, (byte) 112, (byte) 46, (byte) 204, (byte) 146, (byte) 211, (byte) 141, (byte) 111, (byte) 49, (byte) 178, (byte) 236, (byte) 14, (byte) 80, (byte) 175, (byte) 241, (byte) 19, (byte) 77, (byte) 206, (byte) 144, (byte) 114, (byte) 44, (byte) 109, (byte) 51, (byte) 209, (byte) 143, (byte) 12, (byte) 82, (byte) 176, (byte) 238, (byte) 50, (byte) 108, (byte) 142, (byte) 208, (byte) 83, (byte) 13, (byte) 239, (byte) 177, (byte) 240, (byte) 174, (byte) 76, (byte) 18, (byte) 145, (byte) 207, (byte) 45, (byte) 115, (byte) 202, (byte) 148, (byte) 118, (byte) 40, (byte) 171, (byte) 245, (byte) 23, (byte) 73, (byte) 8,
(byte) 86, (byte) 180, (byte) 234, (byte) 105, (byte) 55, (byte) 213, (byte) 139, (byte) 87, (byte) 9, (byte) 235, (byte) 181, (byte) 54, (byte) 104, (byte) 138, (byte) 212, (byte) 149, (byte) 203, (byte) 41, (byte) 119, (byte) 244, (byte) 170, (byte) 72, (byte) 22, (byte) 233, (byte) 183, (byte) 85, (byte) 11, (byte) 136, (byte) 214, (byte) 52, (byte) 106, (byte) 43, (byte) 117, (byte) 151, (byte) 201, (byte) 74, (byte) 20, (byte) 246, (byte) 168, (byte) 116, (byte) 42, (byte) 200, (byte) 150, (byte) 21, (byte) 75, (byte) 169, (byte) 247, (byte) 182, (byte) 232, (byte) 10, (byte) 84, (byte) 215, (byte) 137, (byte) 107, 53 }; /**
* 计算数组的CRC8校验值
*
* @param data
* 需要计算的数组
* @return CRC8校验值
*/
public static byte calcCrc8(byte[] data) {
return calcCrc8(data, 0, data.length, (byte) 0);
} /**
* 计算CRC8校验值
*
* @param data
* 数据
* @param offset
* 起始位置
* @param len
* 长度
* @param preval
* 之前的校验值
* @return 校验值
*/
public static byte calcCrc8(byte[] data, int offset, int len, byte preval) {
byte ret = preval;
for (int i = offset; i < (offset + len); ++i) {
ret = crc8_tab[(0x00ff & (ret ^ data[i]))];
}
return ret;
}

关于蓝牙开发的一些知识内容基本就介绍完了,如果有问题的同学,可以加QQ群:177694195 我们一起讨论蓝牙的开发。

---恢复内容结束---

上一篇:Git 简单入门(一)


下一篇:ABAP 程序/接口调用其他程序的数据