Flutter 混合开发(Android)Flutter跟Native相互通信

2022-01-10 12:02:52 浏览数 (1)

前言

Flutter 作为混合开发,跟native端做一些交互在所难免,比如说调用原生系统传感器、原生端的网络框架进行数据请求就会用到 Flutter 调用android 及android 原生调用 Flutter的方法,这里就涉及到Platform Channels(平台通道)

Platform Channels (平台通道)

Flutter 通过Channel 与客户端之间传递消息,如图:

图中就是通过MethodChannel的方式实现Flutter 与客户端之间的消息传递。MethodChannel是Platform Channels中的一种,Flutter有三种通信类型:

代码语言:javascript复制
BasicMessageChannel:用于传递字符串和半结构化的信息

MethodChannel:用于传递方法调用(method invocation)通常用来调用native中某个方法

EventChannel: 用于数据流(event streams)的通信。有监听功能,比如电量变化之后直接推送数据给flutter端。

为了保证UI的响应,通过Platform Channels传递的消息都是异步的。

更多关于channel原理可以去看这篇文章:channel原理篇

Platform Channels 使用

1.MethodChannel的使用

原生客户端写法(以Android 为例)

首先定义一个获取手机电量方法

代码语言:javascript复制
private int getBatteryLevel() {
        return 90;
    }

这函数是要给Flutter 调用的方法,此时就需要通过 MethodChannel 来建立这个通道了。

首先新增一个初始化 MethodChannel 的方法

代码语言:javascript复制
private String METHOD_CHANNEL = "common.flutter/battery";
private String GET_BATTERY_LEVEL = "getBatteryLevel";
private MethodChannel methodChannel;

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        GeneratedPluginRegistrant.registerWith(this);
        initMethodChannel();
        getFlutterView().postDelayed(() ->
            methodChannel.invokeMethod("get_message", null, new MethodChannel.Result() {
                @Override
                public void success(@Nullable Object o) {
                    Log.d(TAG, "get_message:"   o.toString());
                }

                @Override
                public void error(String s, @Nullable String s1, @Nullable Object o) {

                }

                @Override
                public void notImplemented() {

                }
            }), 5000);

    }

    private void initMethodChannel() {
        methodChannel = new MethodChannel(getFlutterView(), METHOD_CHANNEL);
        methodChannel.setMethodCallHandler(
                (methodCall, result) -> {
                    if (methodCall.method.equals(GET_BATTERY_LEVEL)) {
                        int batteryLevel = getBatteryLevel();

                        if (batteryLevel != -1) {
                            result.success(batteryLevel);
                        } else {
                            result.error("UNAVAILABLE", "Battery level not available.", null);
                        }
                    } else {
                        result.notImplemented();
                    }
                });


    }

    private int getBatteryLevel() {
        return 90;
    }

METHOD_CHANNEL 用于和flutter交互的标识,由于一般情况下会有多个channel,在app里面需要保持唯一性

MethodChannel 都是保存在以通道名为Key的Map中。所以要是设了两个名字一样的channel,只有后设置的那个会生效。

onMethodCall 有两个参数,onMethodCall 里包含要调用的方法名称和参数。Result是给Flutter的返回值。方法名是客户端与Flutter统一设定。通过if/switch语句判断 MethodCall.method 来区分不同的方法,在我们的例子里面我们只会处理名为“getBatteryLevel”的调用。在调用本地方法获取到电量以后通过 result.success(batteryLevel) 调用把电量值返回给Flutter。

MethodChannel-Flutter 端

直接先看一下Flutter端的代码

代码语言:javascript复制
class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  static const platform = const MethodChannel('common.flutter/battery');

  void _incrementCounter() {
    setState(() {
      _counter  ;
      _getBatteryLevel();
    });
  }

  @override
  Widget build(BuildContext context) {
    platform.setMethodCallHandler(platformCallHandler);
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.display1,
            ),
            Text('$_batteryLevel'),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), 
    );
  }

  String _batteryLevel = 'Unknown battery level.';

  Future<Null> _getBatteryLevel() async {
    String batteryLevel;
    try {
      final int result = await platform.invokeMethod('getBatteryLevel');
      batteryLevel = 'Battery level at $result % .';
    } on PlatformException catch (e) {
      batteryLevel = "Failed to get battery level: '${e.message}'.";
    }

    setState(() {
      _batteryLevel = batteryLevel;
    });
  }

 //客户端调用
  Future<dynamic> platformCallHandler(MethodCall call) async {
    switch (call.method) {
      case "get_message":
        return "Hello from Flutter";
        break;
    }
  }
}

上面代码解析: 首先,定义一个常量result.success(platform),和Android客户端定义的channel一致; 接下来定义一个 result.success(_getBatteryLevel())方法,用来调用Android 端的方法,result.success(final int result = await platform.invokeMethod('getBatteryLevel');) 这行代码就是通过通道来调用Native(Android)方法了。因为MethodChannel是异步调用的,所以这里必须要使用await关键字。

在上面Android代码中我们把获取到的电量通过result.success(batteryLevel);返回给Flutter。这里await表达式执行完成以后电量就直接赋值给result变量了。然后通过result.success(setState); 去改变Text显示值。到这里为止,是通过Flutter端调用原生客户端方法。

MethodChannel 其实是一个可以双向调用的方法,在上面的代码中,其实我们也体现了,通过原生客户端调用Flutter的方法。

在原生端通过 methodChannel.invokeMethod 的方法调用

代码语言:javascript复制
methodChannel.invokeMethod("get_message", null, new MethodChannel.Result() {
                @Override
                public void success(@Nullable Object o) {
                    Log.d(TAG, "get_message:"   o.toString());
                }

                @Override
                public void error(String s, @Nullable String s1, @Nullable Object o) {

                }

                @Override
                public void notImplemented() {

                }
            });

在Flutter端就需要给MethodChannel设置一个MethodCallHandler

代码语言:javascript复制
static const platform = const MethodChannel('common.flutter/battery');
platform.setMethodCallHandler(platformCallHandler);
Future<dynamic> platformCallHandler(MethodCall call) async {
    switch (call.method) {
      case "get_message":
        return "Hello from Flutter";
        break;
    }
  }

以上就是MethodChannel的相关用法了。

EventChannel

将数据推送给Flutter端,类似我们常用的推送功能,有需要就推送给Flutter端,是否需要去处理这个推送由Flutter那边决定。相对于MethodChannel是主动获取,EventChannel则是被动推送。

EventChannel 原生客户端写法

代码语言:javascript复制
private String EVENT_CHANNEL = "common.flutter/message";
private int count = 0;
private Timer timer;

private void initEventChannel() {
        new EventChannel(getFlutterView(), EVENT_CHANNEL).setStreamHandler(new EventChannel.StreamHandler() {
            @Override
            public void onListen(Object arguments, EventChannel.EventSink events) {
                timer.schedule(new TimerTask() {
                    @Override
                    public void run() {
                        if (count < 10) {
                            count  ;
                            events.success("当前时间:"   System.currentTimeMillis());
                        } else {
                            timer.cancel();
                        }
                    }
                }, 1000, 1000);
            }

            @Override
            public void onCancel(Object o) {

            }
        });
    }

在上面的代码中,我们做了一个定时器,每秒向Flutter推送一个消息,告诉Flutter我们当前时间。为了防止一直倒计时,我这边做了个计数,超过10次就停止发送。

EventChannel Flutter端

代码语言:javascript复制
String message = "not message";
static const eventChannel = const EventChannel('common.flutter/message');
@override
  void initState() {
    super.initState();
    eventChannel.receiveBroadcastStream().listen(_onEvent, onError: _onError);
  }

void _onEvent(Object event) {
    setState(() {
      message =
      "message: $event";
    });
  }

  void _onError(Object error) {
    setState(() {
      message = 'message: unknown.';
    });
  }

上面的代码就是Flutter端接收原生客户端数据,通过_onEvent 来接收数据,将数据显示Text。这个实现相对简单,如果要达到业务分类,需要将数据封装成json,通过json数据包装一些对应业务标识和数据来做区分。

BasicMessageChannel

BasicMessageChannel (主要是传递字符串和一些半结构体的数据)

BasicMessageChannel Android端

代码语言:javascript复制
private void initBasicMessageChannel() {
        BasicMessageChannel<Object> basicMessageChannel = new BasicMessageChannel<>(getFlutterView(), BASIC_CHANNEL, StandardMessageCodec.INSTANCE);
        //主动发送消息到flutter 并接收flutter消息回复
            basicMessageChannel.send("send basic message", (object)-> {
                Log.e(TAG, "receive reply msg from flutter:"   object.toString());
            });

        //接收flutter消息 并发送回复
        basicMessageChannel.setMessageHandler((object, reply)-> {
            Log.e(TAG, "receive msg from flutter:"   object.toString());
            reply.reply("reply:got your message");

        });

    }

BasicMessageChannel Flutter端

代码语言:javascript复制
  static const basicChannel = const BasicMessageChannel('common.flutter/basic', StandardMessageCodec());
//发送消息到原生客户端 并且接收到原生客户端的回复
  Future<String> sendMessage() async {
    String reply = await basicChannel.send('this is flutter');
    print("receive reply msg from native:$reply");
    return reply;
  }

  //接收原生消息 并发送回复
  void receiveMessage() async {
    basicChannel.setMessageHandler((msg) async {
      print("receive from Android:$msg");
      return "get native message";
    });

上面例子中用到的编解码器为StandardMessageCodec ,例子中通信都是String,用StringCodec也可以。

以上就是Flutter提供三种platform和dart端的消息通信方式。

0 人点赞