这是目录
Android进行低功耗蓝牙基本操作流程
这是个人整理的应对一般低功耗蓝牙扫描与连接设备的文章与demo。部分代码直接使用了Nordic的蓝牙库
蓝牙一般流程概览
app与蓝牙设备建立连接进行通讯的一般流程如下:
这将产生一个流程图。:
蓝牙扫描部分
蓝牙使用前的准备工作:权限与开关
App调用蓝牙操作之前得先确保所需权限都已经授权,所需功能开关已经开启
1.权限添加与监听
静态添加权限
AndroidManifest.xml需要添加以下权限:
<uses-permission android:name="android.permission.BLUETOOTH"/>//蓝牙操作必须的蓝牙权限
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>//蓝牙可能用到的蓝牙权限
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>//android6.0以上的蓝牙扫描需要用到定位权限
动态添加与检测权限:
仅仅静态权限写完还不没完成任务,Android 6.0(API 23)开始,定位权限需要动态申请授权。
检测权限是否已经申请,其他需要申请的权限最好也在这部分完成:
private boolean isLocationPermissionsGranted(Context context){
return (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION)
== PackageManager.PERMISSION_GRANTED);
}
若还没获取授权,则在Activity动态获取授权:
//假如有其他需要申请的权限最好在此时一起进行动态申请
ActivityCompat.requestPermissions(
this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
REQUEST_ACCESS_FINE_LOCATION);
在Activity监听授权情况:
@Override
public void onRequestPermissionsResult(final int requestCode,
@NonNull final String[] permissions,
@NonNull final int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(requestCode==REQUEST_ACCESS_FINE_LOCATION){
boolean isAllGranted = true;
for (int grant : grantResults) {
if (grant != PackageManager.PERMISSION_GRANTED) {
isAllGranted = false;
break;
}
}
if (isAllGranted) {
// TODO: 如果所有的权限都授予了, 则进行下一步
}
}
}
2.开关检测与监听
开关的检测
蓝牙开关检测:
/**
* 蓝牙开关检测
*/
public static boolean isBleEnabled() {
final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
return adapter != null && adapter.isEnabled();
}
Android6.0以上大部分手机蓝牙扫描都要求定位服务开启:
/**
* 定位服务开关检测
*/
public static boolean isLocationEnabled(@NonNull final Context context) {
int locationMode = Settings.Secure.LOCATION_MODE_OFF;
try {
locationMode = Settings.Secure.getInt(context.getContentResolver(),
Settings.Secure.LOCATION_MODE);
} catch (final Settings.SettingNotFoundException e) {
// do nothing
}
return locationMode != Settings.Secure.LOCATION_MODE_OFF;
}
开关的监听
以上只是主动获取开关状态,假如我们想监听状态变化则需要使用Receiver:
/**
* 蓝牙开关监听
*/
private final BroadcastReceiver bluetoothStateBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF);
final int previousState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, BluetoothAdapter.STATE_OFF);
switch (state) {
case BluetoothAdapter.STATE_ON:
//蓝牙开关开启
break;
case BluetoothAdapter.STATE_TURNING_OFF:
case BluetoothAdapter.STATE_OFF:
if (previousState != BluetoothAdapter.STATE_TURNING_OFF && previousState != BluetoothAdapter.STATE_OFF) {
//蓝牙开关关闭
}
break;
}
}
};
/**
* 定位服务开关监听
* */
private final BroadcastReceiver locationProviderChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
final boolean enabled = Utils.isLocationEnabled(context);//定位开关
}
};
/**
* 广播接受者注册
* */
public void registerBroadcastReceivers(@NonNull final Application application) {
application.registerReceiver(bluetoothStateBroadcastReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
application.registerReceiver(locationProviderChangedReceiver, new IntentFilter(LocationManager.MODE_CHANGED_ACTION));
}
/**
* 广播接受者注销
* */
public void unregisterBroadcastReceivers(@NonNull final Application application){
application.unregisterReceiver(bluetoothStateBroadcastReceiver);
application.unregisterReceiver(locationProviderChangedReceiver);
}
引导用户开启开关
有时候我们还需要引导用户去开启开关:
/**
* 蓝牙开启请求
* */
private void requestBluetoothOn(){
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivity(enableBtIntent);
}
/**
* 定位设置跳转
* */
private void requestLocation(){
Intent enableBtIntent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
startActivity(enableBtIntent);
}
开启/停止蓝牙扫描
所有前置工作完成之后我们可以进行蓝牙扫描操作了。
使用系统蓝牙类进行蓝牙扫描
这是直接调用系统蓝牙类进行扫描的方法:
ScanCallback scanCallback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
//设备扫描回调
super.onScanResult(callbackType, result);
}
};
/**
* 开启扫描
* */
private void startScan(){
BluetoothLeScanner bluetoothLeScanner = BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner();
bluetoothLeScanner.startScan(scanCallback);
}
/**
* 停止扫描
* */
private void stopScan(){
BluetoothLeScanner bluetoothLeScanner = BluetoothAdapter.getDefaultAdapter().getBluetoothLeScanner();
bluetoothLeScanner.stopScan(scanCallback);
}
使用BLE库进行蓝牙扫描
但是系统的蓝牙扫描往往不能满足我们的需求,甚至可能碰到不少问题,既然有问题,那么就自然会有解决方法:
(1)自己调整蓝牙扫描逻辑
(2)直接使用成熟的第三方库
方法1就不详谈了(有兴趣的自己研究方法2的开源库代码),这里直接上方法2。
build.gradle 添加引用库:
implementation 'no.nordicsemi.android.support.v18:scanner:1.4.3'
代码调用:
/**
* 开始扫描.
*/
public void startScan() {
if (isScanning) {
return;
}
// Scanning settings
final ScanSettings settings = new ScanSettings.Builder()
.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)//扫描模式,这里使用使用最高占空比扫描,其他扫描模式自己看描述
.setReportDelay(500)//扫描结果回调延迟的时间,单位ms
.setUseHardwareBatchingIfSupported(false)//自己点进去看描述
.build();
final BluetoothLeScannerCompat scanner = BluetoothLeScannerCompat.getScanner();
ArrayList<ScanFilter> sc = new ArrayList<>();//扫描过滤条件列表
sc.add(new ScanFilter.Builder().setDeviceName("MYDEVICE").build());//以设备名为条件进行过滤,也可以使用UUID等过滤方式进行过滤
scanner.startScan(sc, settings, scanCallback);
scannerStateLiveData.scanningStarted();
}
/**
* 停止扫描
*/
public void stopScan() {
if (isScanning) {
final BluetoothLeScannerCompat scanner = BluetoothLeScannerCompat.getScanner();
scanner.stopScan(scanCallback);
}
}
/**
* 扫描回调
*/
private final ScanCallback scanCallback = new ScanCallback() {
@Override
public void onScanResult(final int callbackType, @NonNull final ScanResult result) {
//TODO 单个设备回调
}
@Override
public void onBatchScanResults(@NonNull final List<ScanResult> results) {
//TODO 多个设备回调
}
@Override
public void onScanFailed(final int errorCode) {
// TODO This should be handled
}
};
蓝牙连接部分
搜到我们的蓝牙设备之后就是发起连接了,这里直接介绍使用BLE库进行连接,想了解更多的请去看开源库里面的代码。
在build.gradle引用库:
implementation 'no.nordicsemi.android:ble:2.2.4'
App对蓝牙设备的相关操作主要是利用库里的蓝牙管理类完成,这里只列举流程相关的代码。
1.首先我们创建MyBleManager:
public class MyBleManager extends BleManager {
public MyBleManager(@NonNull final Context context){
super(context);
}
}
2.连接搜索到的设备。
这里利用BleManager 里的connect发起连接。BluetoothDevice对象可以在搜索回调返回的ScanResult 通过 getDevice()获得。
public class MyBleManager extends BleManager {
public MyBleManager(@NonNull final Context context){
super(context);
}
public void connect(BluetoothDevice device) {
if (device != null) {
connect(device)
.retry(3, 200)//连接不成功时候进行重连的次数与发动重连的延迟时间
.useAutoConnect(false)//是否添加到设备名单使用自动连接
.enqueue()
}
}
}
3.接下来我们需要重写getGattCallback()与通知监听
通过监听gatt回调来接管设备发起连接后的操作,这里用拥有读写与通知功能的蓝牙设备进行举例。
public class MyBleManager extends BleManager {
public MyBleManager(@NonNull final Context context){
super(context);
}
public void connect(BluetoothDevice device) {
if (device != null) {
connect(device)
.retry(BleConfig.connectRetryCount, BleConfig.connectRetryDelay)
.useAutoConnect(BleConfig.autoConnect)
.enqueue()
}
}
//服务UUI
public final static UUID SERVICE_UUID = UUID.fromString("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX");
//读写UUI
public final static UUID WRITE_CHAR = UUID.fromString("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX");
//通知UUI
public final static UUID NOTIFICATION_CHAR = UUID.fromString("XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX");
private BluetoothGattCharacteristic writeCharacteristic, notificationCharacteristic;
/**
* 设备是否支持
* */
private boolean supported;
@NonNull
@Override
protected BleManagerGattCallback getGattCallback() {
return new MyBleManagerGattCallback();
}
/**
* Gatt回调类,蓝牙连接过程中的操作一般都是通过此类来完成
*/
private class MyBleManagerGattCallback extends BleManagerGattCallback {
@Override
public boolean isRequiredServiceSupported(@NonNull final BluetoothGatt gatt) {
//此段代码是进行校验设备是否拥有我们所需的服务与特征
final BluetoothGattService service = gatt.getService(SERVICE_UUID);
if (service != null) {
//读写特征
writeCharacteristic = service.getCharacteristic(WRITE_CHAR);
//通知特征
notificationCharacteristic = service.getCharacteristic(NOTIFICATION_CHAR);
}
boolean writeRequest = false;
//此处校验读写特征是否拥有写入数据的权限
if (writeCharacteristic != null) {
final int rxProperties = writeCharacteristic.getProperties();
writeRequest = (rxProperties & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0;
}
supported = writeCharacteristic != null && notificationCharacteristic != null && writeRequest;
return supported;
}
@Override
protected void initialize() {
//通知使能,通过notificationDataCallback进行通知监听
setNotificationCallback(notificationCharacteristic).with(notificationDataCallback);
enableNotifications(notificationCharacteristic).enqueue();
//假如我们的写入指令还带有读取功能,我们可以在这里使能
//readCharacteristic(writeCharacteristic).with(readCallback).enqueue();
}
@Override
protected void onDeviceDisconnected() {
writeCharacteristic = null;
notificationCharacteristic = null;
}
}
@Override
protected boolean shouldClearCacheWhenDisconnected() {
return !supported;
}
}
notificationDataCallback 是对设备向app通知的监听回调:
public final NotificationDataCallback notificationDataCallback = new NotificationDataCallback() {
@Override
public void onStateChanged(@NonNull @NotNull BluetoothDevice device, int head, Data data) {
}
};
4.连接状态的监听
由上述代码可见,在连接过程中主要是进行服务的校验与特征的获取,以及使能通知。至于我们想知道连接状态,可以使用BleManager里的setConnectionObserver进行监听。例如:
public void listConnectState(){
myBleManager.setConnectionObserver(new ConnectionObserver() {
@Override
public void onDeviceConnecting(@NonNull @NotNull BluetoothDevice device) {
}
@Override
public void onDeviceConnected(@NonNull @NotNull BluetoothDevice device) {
}
@Override
public void onDeviceFailedToConnect(@NonNull @NotNull BluetoothDevice device, int reason) {
}
@Override
public void onDeviceReady(@NonNull @NotNull BluetoothDevice device) {
}
@Override
public void onDeviceDisconnecting(@NonNull @NotNull BluetoothDevice device) {
}
@Override
public void onDeviceDisconnected(@NonNull @NotNull BluetoothDevice device, int reason) {
}
});
}
在监听到onDeviceReady的时候代表连接操作已经完成,我们已经可以通过写入数据来控制设备。
5.写入数据与监听
对设备进行数据传输一般就是通过写入数据来进行控制,我们只需要实现BleManager 的writeCharacteristic就可以实现数据传输。例如:
public void writeData(){
byte[] bytes= new byte[]{0x01,0x02};//通讯数据一般使用byte数组
data = new Data(bytes);//写入的数据对象
if(writeCharacteristic!=null){
writeCharacteristic(writeCharacteristic,data).with(writeDataCallback).enqueue();
}
}
writeDataCallback是对写入操作的监听回调:
public final WriteDataCallback writeDataCallback = new WriteDataCallback() {
@Override
public void onDataReceived(@NonNull BluetoothDevice device, @NonNull Data data) {
super.onDataReceived(device, data);
}
@Override
public void onDataSent(@NonNull BluetoothDevice device, @NonNull Data data) {
super.onDataSent(device, data);;
}
@Override
public void onInvalidDataReceived(@NonNull BluetoothDevice device, @NonNull Data data) {
}
};
6.主动断开连接
我们app想主动断开与蓝牙设备的连接,可以直接调用BleManager的disconnect来断开,例如:
public void disconnectDevice() {
disconnect().enqueue();
}
App对蓝牙的操作就写到这里,更多的可以参考我的BleDemoApp。