Flutter蓝牙框架-flutter_blue_plus使用及源码解析
前言
前段时间有朋友拜托我研究下flutter利用蓝牙与硬件交互的功能,我查阅了很多资料,目前市面上比较流行的第三方库有两个,一个是flutter_blue_plus,一个是flutter_reactive_ble,前一个比较轻量级,能满足大部分场景,后一个比较复杂,支持多个蓝牙设备同时连接。那么这一次我们先来研究下flutter_blue_plus,剩下的flutter_reactive_ble下次有机会再来看。
低功耗蓝牙(BLE)原理
博主好几年前还做Android原生开发时就接触并研究过BLE在Android平台上的使用与原理,写过一篇文章,大家感兴趣可以去看看。本次主要研究flutter_blue_plus(v1.6.1),对BLE原理不做过多描述。
使用及源码解析
要搞清楚如何使用flutter_blue_plus,最好的办法就是查阅文档或者查看flutter_reactive_ble的代码。这一次,我们就从flutter_reactive_ble库中example目录下的示例代码开始,一步一步看看如何使用flutter_blue_plus。
- 首先,我们打开main.dart文件。能够看到runApp里创建了我们示例的根组件-FlutterBlueApp。
runApp(const FlutterBlueApp());
我们来看看FlutterBlueApp是怎么写的:
class FlutterBlueApp extends StatelessWidget {
const FlutterBlueApp({
Key? key}) : super(key: key);
Widget build(BuildContext context) {
return MaterialApp(
color: Colors.lightBlue,
home: StreamBuilder<BluetoothState>(
stream: FlutterBluePlus.instance.state,
initialData: BluetoothState.unknown,
builder: (c, snapshot) {
final state = snapshot.data;
if (state == BluetoothState.on) {
return const FindDevicesScreen();
}
return BluetoothOffScreen(state: state);
}),
);
}
}
我们看到,这里利用了一个StreamBuilder去监听Stream的变化,主要是BluetoothState。蓝牙设备的状态,然后根据实时状态去变化展示的内容。
BluetoothState是一个枚举类,定义了几种可能的状态:
/// State of the bluetooth adapter.
enum BluetoothState
{
unknown,
unavailable,
unauthorized,
turningOn,
on,
turningOff,
off
}
initialData是StreamBuilder中绘制第一帧的数据,由于是BluetoothState.unknown,所以第一帧应该显示BluetoothOffScreen。之后的状态由stream中的异步数据提供,即FlutterBluePlus.instance.state,我们看看FlutterBluePlus这个类:
class FlutterBluePlus
{
static final FlutterBluePlus _instance = FlutterBluePlus._();
static FlutterBluePlus get instance => _instance;
....
/// Singleton boilerplate
FlutterBluePlus._()
{
....
}
....
}
可以看到,FlutterBluePlus是一个利用dart getter操作符实现的一个单例类,通过FlutterBluePlus.instance获取全局唯一的一个实例。
接着我们看下FlutterBluePlus.instance.state,这个state也是一个getter方法:
/// Gets the current state of the Bluetooth module
Stream<BluetoothState> get state async*
{
BluetoothState initialState = await _channel
.invokeMethod('state')
.then((buffer) => protos.BluetoothState.fromBuffer(buffer))
.then((s) => BluetoothState.values[s.state.value]);
yield initialState;
_stateStream ??= _stateChannel
.receiveBroadcastStream()
.map((buffer) => protos.BluetoothState.fromBuffer(buffer))
.map((s) => BluetoothState.values[s.state.value])
.doOnCancel(() => _stateStream = null);
yield* _stateStream!;
}
可以看到,由于蓝牙涉及到原生操作系统底层的功能,所以需要利用平台通道(platform channel)机制,实现 Dart 代码与原生代码的交互,间接调用Android/IOS SDK的Api。
final MethodChannel _channel = const MethodChannel('flutter_blue_plus/methods');
final EventChannel _stateChannel = const EventChannel('flutter_blue_plus/state');
在FlutterBluePlus这个类中,首先构造一个方法通道(method channel)与一个事件通道(event channel),通道的客户端(flutter方)和宿主端(原生方)通过传递给通道构造函数的通道名称进行连接,这个名称必须是唯一的。之后就可以通过_channel.invokeMethod调用原生的方法了,当然前提是原生平台有对应的实现。接下来,我们看下state这个方法,原生端是如何实现的(以Android为例):
在flutter_blue_plus库的android目录下,能看到一个FlutterBluePlusPlugin.java文件:
public class FlutterBluePlusPlugin implements
FlutterPlugin,
MethodCallHandler,
....
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding)
{
setup(pluginBinding.getBinaryMessenger(),
(Application) pluginBinding.getApplicationContext());
}
....
private void setup(final BinaryMessenger messenger,
final Application application)
{
....
channel = new MethodChannel(messenger, NAMESPACE + "/methods");
channel.setMethodCallHandler(this);
stateChannel = new EventChannel(messenger, NAMESPACE + "/state");
stateChannel.setStreamHandler(stateHandler);
....
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding)
{
....
tearDown();
}
private void tearDown()
{
....
channel.setMethodCallHandler(null);
channel = null;
stateChannel.setStreamHandler(null);
stateChannel = null;
....
}
}
可以看到,FlutterBluePlusPlugin实现了FlutterPlugin与MethodCallHandler两个接口,实现FlutterPlugin的onAttachedToEngine与onDetachedFromEngine两个方法后,就可以将插件与flutter的engine关联起来。在这两个方法中,主要是构造了MethodChannel与EventChannel并在最后置为空,作用是在一开始注册通道并在最后销毁掉。
而实现MethodCallHandler的onMethodCall方法,即在原生端实现相应的功能方便flutter通道调用:
@Override
public void onMethodCall(@NonNull MethodCall call,
@NonNull Result result)
{
....
switch (call.method) {
....
case "state":
{
try {
// get state, if we can
int state = -1;
try {
state = mBluetoothAdapter.getState();
} catch (Exception e) {
}
// convert to protobuf enum
Protos.BluetoothState.State pbs;
switch(state) {
case BluetoothAdapter.STATE_OFF: pbs = Protos.BluetoothState.State.OFF; break;
case BluetoothAdapter.STATE_ON: pbs = Protos.BluetoothState.State.ON; break;
case BluetoothAdapter.STATE_TURNING_OFF: pbs = Protos.BluetoothState.State.TURNING_OFF; break;
case BluetoothAdapter.STATE_TURNING_ON: pbs = Protos.BluetoothState.State.TURNING_ON; break;
default: pbs = Protos.BluetoothState.State.UNKNOWN; break;
}
Protos.BluetoothState.Builder p = Protos.BluetoothState.newBuilder();
p.setState(pbs);
result.success(p.build().toByteArray());
} catch(Exception e) {
result.error("state", e.getMessage(), e);
}
break;
}
....
可以看到,Android端拿到蓝牙状态后通过result.success返回结果。
state方法只能获取初始状态,后面状态的变化我们看到是通过EventChannel监听广播获取的,我们看看在原生端是怎么处理的。
在创建EventChannel时,首先将它的StreamHandler设置为我们自定义的StreamHandler函数:
private class MyStreamHandler implements StreamHandler {
private final int STATE_UNAUTHORIZED = -1;
private EventSink sink;
public EventSink getSink() {
return sink;
}
private int cachedBluetoothState;
public void setCachedBluetoothState(int value) {
cachedBluetoothState = value;
}
public void setCachedBluetoothStateUnauthorized() {
cachedBluetoothState = STATE_UNAUTHORIZED;
}
@Override
public void onListen(Object o, EventChannel.EventSink eventSink) {
sink = eventSink;
if (cachedBluetoothState != 0) {
// convert to Protobuf enum
Protos.BluetoothState.State pbs;
switch (cachedBluetoothState) {
case BluetoothAdapter.STATE_OFF: pbs = Protos.BluetoothState.State.OFF; break;
case BluetoothAdapter.STATE_ON: pbs = Protos.BluetoothState.State.ON; break;
case BluetoothAdapter.STATE_TURNING_OFF: pbs = Protos.BluetoothState.State.TURNING_OFF; break;
case BluetoothAdapter.STATE_TURNING_ON: pbs = Protos.BluetoothState.State.TURNING_ON; break;
case STATE_UNAUTHORIZED: pbs = Protos.BluetoothState.State.OFF; break;
default: pbs = Protos.BluetoothState.State.UNKNOWN; break;
}
Protos.BluetoothState.Builder p = Protos.BluetoothState.newBuilder();
p.setState(pbs);
sink.success(p.build().toByteArray());
}
}
@Override
public void onCancel(Object o) {
sink = null;
}
};
在MyStreamHandler的onListen方法里,我们拿到EventSink引用并保存,并查看是否有缓存未发送的蓝牙状态,有的话就利用EventSink发送给Stream。
之后,我们注册一个监听蓝牙状态变化的广播,将当前蓝牙状态设置为MyStreamHandler的缓存状态cachedBluetoothState:
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
context.registerReceiver(mBluetoothStateReceiver, filter);
try {
stateHandler.setCachedBluetoothState(mBluetoothAdapter.getState());
} catch (SecurityException e) {
stateHandler.setCachedBluetoothStateUnauthorized();
}
注册的广播代码如下:
private final BroadcastReceiver mBluetoothStateReceiver = new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
// no change?
if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action) == false) {
return;
}
final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
EventSink sink = stateHandler.getSink();
if (sink == null) {
stateHandler.setCachedBluetoothState(state);
return;
}
// convert to Protobuf enum
Protos.BluetoothState.State pbs;
switch (state) {
case BluetoothAdapter.STATE_OFF: pbs = Protos.BluetoothState.State.OFF; break;
case BluetoothAdapter.STATE_ON: pbs = Protos.BluetoothState.State.ON; break;
case BluetoothAdapter.STATE_TURNING_OFF: pbs = Protos.BluetoothState.State.TURNING_OFF; break;
case BluetoothAdapter.STATE_TURNING_ON: pbs = Protos.BluetoothState.State.TURNING_ON; break;
default: pbs = Protos.BluetoothState.State.UNKNOWN; break;
}
Protos.BluetoothState.Builder p = Protos.BluetoothState.newBuilder();
p.setState(pbs);
sink.success(p);
}
};
广播接收到蓝牙状态变化后,根据是否能获取到EventSink,看是缓存还是发送。
至此,蓝牙状态相关代码就分析完了。
- 之后,我们来看下BluetoothOffScreen,这个界面比较简单,除了展示蓝牙的状态之外,还提供了一个打开蓝牙的开关(只针对Android)。
onPressed: Platform.isAndroid
? () => FlutterBluePlus.instance.turnOn()
:

本文详细介绍了Flutter插件Flutter_blue_plus如何实现与蓝牙设备的交互,包括蓝牙状态监听、设备扫描、连接、服务发现以及读写操作。通过平台通道(PlatformChannel)机制,Flutter代码与原生Android/iOS代码进行通信,实现蓝牙功能。文章还分析了源码中的关键部分,如StreamBuilder、MethodChannel、EventChannel的使用,以及Android端的蓝牙API调用和状态广播监听。
最低0.47元/天 解锁文章
4042






