Flutter蓝牙框架-flutter_blue_plus使用及源码解析

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

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。

  1. 首先,我们打开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,看是缓存还是发送。
至此,蓝牙状态相关代码就分析完了。

  1. 之后,我们来看下BluetoothOffScreen,这个界面比较简单,除了展示蓝牙的状态之外,还提供了一个打开蓝牙的开关(只针对Android)。
onPressed: Platform.isAndroid
                  ? () => FlutterBluePlus.instance.turnOn()
                  :
本文章已经生成可运行项目
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值