从公司里面接过BLE蓝牙项目已经有一段时间了,虽然这个项目的复杂度不高,但是对于我这种第一次开发蓝牙的来说,发现里面有不少的坑,特此在这里进行记录,本来一直想总结的,但是手头上的东西太多了,就留到节假日了。
安卓4.3(API 18)为BLE的核心功能提供平台支持和API,App可以利用它来发现设备、查询服务和读写特性。相比传统的蓝牙,BLE更显著的特点是低功耗。这一优点使Android App可以与具有低功耗要求的BLE设备通信,如近距离传感器、心脏速率监视器、健身设备。
闲话不多说,让我总结一下,蓝牙的工作流程和使用。
蓝牙编程中,我们可以将蓝牙分为以下几个步骤:
- 搜索蓝牙
- 蓝牙搜索成功后,根据UUID,或者自己的选择进行连接。
- 蓝牙连接成功之后,我们可以开始传输数据。
这一章内容先总结蓝牙搜索时候,需要注意的问题以及初始的步骤。
在这之前我先要总结一下,蓝牙接下来会用到的一些术语:
- Generic Attribute Profile(GATT)—GATT配置文件是一个通用规范,用于在BLE链路上发送和接收被称为“属性”的数据块。目前所有的BLE应用都基于GATT。 蓝牙SIG规定了许多低功耗设备的配置文件。配置文件是设备如何在特定的应用程序中工作的规格说明。注意一个设备可以实现多个配置文件。例如,一个设备可能包括心率监测仪和电量检测。
- Attribute Protocol(ATT)—GATT在ATT协议基础上建立,也被称为GATT/ATT。ATT对在BLE设备上运行进行了优化,为此,它使用了尽可能少的字节。每个属性通过一个唯一的的统一标识符(UUID)来标识,每个String类型UUID使用128 bit标准格式。属性通过ATT被格式化为characteristics和services。
- Characteristic 一个characteristic包括一个单一变量和0-n个用来描述characteristic变量的descriptor,characteristic可以被认为是一个类型,类似于类。
- Descriptor Descriptor用来描述characteristic变量的属性。例如,一个descriptor可以规定一个可读的描述,或者一个characteristic变量可接受的范围,或者一个characteristic变量特定的测量单位。
- Service service是characteristic的集合。例如,你可能有一个叫“Heart Rate Monitor(心率监测仪)”的service,它包括了很多characteristics,如“heart rate measurement(心率测量)”等。你可以在bluetooth.org 找到一个目前支持的基于GATT的配置文件和服务列表。
看不懂没关系。实际上,我们在做项目的时候,硬件提供方会提供两种字符串,ServiceUUID,以及characteristic。我们是通过这个ServiceUUID进行寻找连接设备以及以及进行连接这两个步骤。而后,根据硬件提供方的characteristic和蓝牙协议,如开机是0X01之类的16进制代码(或者其他的要看厂家给啥),通过蓝牙发送的函数或者读取的函数,写入serviceuuid,characteristic,协议上的代码一起发送过去就完成蓝牙通信流程打,当然在其中有许许多多的点和坑需要注意。
正文
蓝牙搜索第一步:
先检测蓝牙是否开启,并且授权蓝牙权限:
获得权限:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
初始化:
private BluetoothManager mBluetoothManager;
private BluetoothAdapter mBluetoothAdapter;
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;
}
注意从android API18 ble编程开始,我们开始使用BlueAdapter这个类里面获取蓝牙行为的函数。
再判断蓝牙时候开启:
public boolean enableBluetooth(boolean enable) {
if (enable) {
if (!mBluetoothAdapter.isEnabled()) {
mBluetoothAdapter.enable();
}
return true;
} else {
if (mBluetoothAdapter.isEnabled()) {
mBluetoothAdapter.disable();
}
return false;
}
}
if (mBleService.initialize()) {
if (mBleService.enableBluetooth(true)) {
// mBleService.scanLeDevice(true);
Toast.makeText(MainActivity.this, "Bluetooth was opened", Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(MainActivity.this, "not support Bluetooth", Toast.LENGTH_SHORT).show();
}
注意:在这里我们最好把蓝牙写成一个Service,将整个蓝牙的动作丢到后台进行工作。我们再到Activity中启动service的同时,启动蓝牙。
这里就完成了第一步,蓝牙的初始化。
蓝牙搜索第二步:
初始结束之后,开始搜索蓝牙:
我们开始调用调用两个函数:startLeScan(ScanCallback mScanCallback)以及 startLeScan(UUID[] uuids,ScanCallback mScanCallback)。
第一个函数是指搜索附近所有的蓝牙设备,第二个函数是指搜索指定serviceuuid的蓝牙设备。
public void scanLeDevice(final boolean enable, long scanPeriod) {
Log.i(TAG, "scanLeDevice: Build.VERSION.SDK_INT = " + Build.VERSION.SDK_INT);
if (enable) {
if (isScanning) return;
//Stop scanning after a predefined scan period.
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
isScanning = false;
Log.e(TAG, "scanLeDevice---->扫描结束");
if (Build.VERSION.SDK_INT >= 21) {
mBluetoothAdapter.getBluetoothLeScanner().stopScan(mScanCallback);
} else {
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
// broadcastUpdate(ACTION_SCAN_FINISHED);
/* if (mScanList != null) {
mScanList.clear();
mScanList = null;
}*/
// mBluetoothAdapter.getBluetoothLeScanner().stopScan(mLeScanCallback);
}
}, scanPeriod);
if (mScanList == null) {
mScanList = new ArrayList<>();
}
mScanList.clear();
isScanning = true;
if (Build.VERSION.SDK_INT >= 21) {
mBluetoothAdapter.getBluetoothLeScanner().startScan(mScanCallback);
} else {
mBluetoothAdapter.startLeScan(mLeScanCallback);
}
} else {
isScanning = false;
if (Build.VERSION.SDK_INT >= 21) {
mBluetoothAdapter.getBluetoothLeScanner().stopScan(mScanCallback);
} else {
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
// broadcastUpdate(ACTION_SCAN_FINISHED);
/* if (mScanList != null) {
mScanList.clear();
mScanList = null;
}*/
}
}
停止搜索:
public void stopScanLe(){
if (Build.VERSION.SDK_INT >= 21) {
mBluetoothAdapter.getBluetoothLeScanner().stopScan(mScanCallback);
} else {
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
}
我们在API21之后,
搜索蓝牙的时候
startScan(final ScanCallback callback)`
搜索指定uuid的蓝牙设备的时候可以使用这个函数:startScan(List<ScanFilter> filters, ScanSettings settings,
final ScanCallback callback)
这个函数的第一个参数是指筛选条件,第二个参数是指设置。这样就很灵活的设置了我们搜索蓝牙的条件,但是当时我大致搜索了一下这个函数的两个函数究竟怎么用的时候,发现资料很少。还好我发现官方为了支持低版本,在API21之上也有startLeScan(UUID[] uuids,ScanCallback mScanCallback)函数,地城是用startScan实现的。看看官方怎么写的我们也知道如何完成的。
@Deprecated
public boolean startLeScan(final UUID[] serviceUuids, final LeScanCallback callback) {
if (DBG) Log.d(TAG, "startLeScan(): " + serviceUuids);
if (callback == null) {
if (DBG) Log.e(TAG, "startLeScan: null callback");
return false;
}
BluetoothLeScanner scanner = getBluetoothLeScanner();
if (scanner == null) {
if (DBG) Log.e(TAG, "startLeScan: cannot get BluetoothLeScanner");
return false;
}
synchronized(mLeScanClients) {
if (mLeScanClients.containsKey(callback)) {
if (DBG) Log.e(TAG, "LE Scan has already started");
return false;
}
try {
IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();
if (iGatt == null) {
// BLE is not supported
return false;
}
ScanCallback scanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
if (callbackType != ScanSettings.CALLBACK_TYPE_ALL_MATCHES) {
// Should not happen.
Log.e(TAG, "LE Scan has already started");
return;
}
ScanRecord scanRecord = result.getScanRecord();
if (scanRecord == null) {
return;
}
if (serviceUuids != null) {
List<ParcelUuid> uuids = new ArrayList<ParcelUuid>();
for (UUID uuid : serviceUuids) {
uuids.add(new ParcelUuid(uuid));
}
List<ParcelUuid> scanServiceUuids = scanRecord.getServiceUuids();
if (scanServiceUuids == null || !scanServiceUuids.containsAll(uuids)) {
if (DBG) Log.d(TAG, "uuids does not match");
return;
}
}
callback.onLeScan(result.getDevice(), result.getRssi(),
scanRecord.getBytes());
}
};
ScanSettings settings = new ScanSettings.Builder()
.setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
List<ScanFilter> filters = new ArrayList<ScanFilter>();
if (serviceUuids != null && serviceUuids.length > 0) {
// Note scan filter does not support matching an UUID array so we put one
// UUID to hardware and match the whole array in callback.
ScanFilter filter = new ScanFilter.Builder().setServiceUuid(
new ParcelUuid(serviceUuids[0])).build();
filters.add(filter);
}
scanner.startScan(filters, settings, scanCallback);
mLeScanClients.put(callback, scanCallback);
return true;
} catch (RemoteException e) {
Log.e(TAG,"",e);
}
}
return false;
}
到这里就完成了蓝牙的搜索的动作,但是我们还需要实现对蓝牙的扫描后状态监听的回调
protected ScanCallback mScanCallback;
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mScanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
String deviceName = (TextUtils.isEmpty(result.getDevice().getName())) ? "No Name" : result.getDevice().getName();
BlueToothModel blueToothModel = new BlueToothModel(MaxReConnectNum, result.getDevice().getAddress(), deviceName);
if (isContainBleDevice(blueToothModel)) return;
mScanList.add(blueToothModel);
if (mOnLeScanListener != null) {
mOnLeScanListener.onLeScan(result.getDevice(), result.getRssi(), result.getScanRecord().getBytes());
}
// broadcastUpdate(ACTION_BLUETOOTH_DEVICE, result.getDevice());
Log.e(TAG, "onScanResult: name: " + result.getDevice().getName() +
", address: " + result.getDevice().getAddress() +
", rssi: " + result.getRssi() + ", scanRecord: " + result.getScanRecord());
}
}
};
}
}
我们可以在自己编写的Service添加一个回调函数:
public void setOnLeScanListener(OnLeScanListener l) {
mOnLeScanListener = l;
}
在自己的Activity中完成对回调函数重写:
mBleService.setOnLeScanListener(new BleService.OnLeScanListener() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
runOnUiThread(new Runnable() {
@Override
public void run() {
/扫描完成后的动作,如添加到列表
}
}
}
});
}
});
至此就完成了蓝牙的扫描动作,记住回调里面的动作最好丢到Handler中异步完成,因为蓝牙本身这个动作有时间限制,如果超出了这个时间的话,怎么样不会走超出时间的部分,当时这里卡了我很久。
到这里蓝牙BLE搜索篇就大致上完成,后续还有蓝牙连接和数据通信。