Android基本蓝牙操作流程与Demo

Android进行低功耗蓝牙基本操作流程

这是个人整理的应对一般低功耗蓝牙扫描与连接设备的文章与demo。部分代码直接使用了Nordic的蓝牙库

蓝牙一般流程概览

app与蓝牙设备建立连接进行通讯的一般流程如下:

设备 周围环境 APP 蓝牙广播 扫描蓝牙对象 获取蓝牙广播包列表 发起蓝牙连接 蓝牙状态回调 通过蓝牙接口返回gatt对象 根据gatt对象进行服务与特征码校验 使能notification 数据读写操作 数据返回与通知 设备 周围环境 APP

这将产生一个流程图。:

1.蓝牙广播
1.蓝牙扫描
2.扫描回调
3.发起连接流程
4.连接结果返回
蓝牙设备
周边环境
APP
5.是否连接成功
6.与设备进行数据通讯操作
1.重新扫描

蓝牙扫描部分

蓝牙使用前的准备工作:权限与开关

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

  • 1
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值