iOS 与 Flutter 通信之 BasicMessageChannel

1,593 阅读3分钟

1. BasicMessageChannel 简介

BasicMessageChannel 是以发送消息的形式传递数据, 类似于 iOS 的通知, channel 一旦建立, 消息的监听处理是持续性的, 可以随时收发, 属于双向通信, 可以 iOS 端主动调用,也可以 Flutter 主动调用, 并且自在发送消息的同时还可以设置一个回调监听, 收到消息之后通过回调给消息发送方一个结果(返回值).

本文的代码是在上一篇文章iOS 与 Flutter 通信之 MethodChannel的基础上做了相应的修改, 去掉一部分多余的, 加上最新的 BasicMessageChannel 相关的代码, 具体完整代码在最后已经给出.

2. 使用场景

2.1 iOS 发送 Message

2.1.1 场景描述

Flutter 模块初始化之后, 点击屏幕, 给 flutter 端发送一个自增的数字. flutter 收到数据之后, 在控制台输出收到的数字, 并通过回调函数返回给 iOS 一个信息, iOS 端在控制台输出 flutter回传的信息.

2.1.1 场景效果

QQ20210929-125056-HD.gif

2.2 Flutter 发送 Message

2.2.1 场景描述

弹出 flutter 页面, 在 flutter 页面的输入框中输入内容, 在输入框内容发生变化的监听中, 给 iOS 端发送现有内容, iOS 端收到数据之后, 在控制台输出收到的消息, 并通过回调函数返回给 flutter 一个信息, flutter 端在控制台输出 iOS回传的信息.

2.2.1 场景效果

QQ20210929-125255-HD.gif

3. 使用方法

FlutteriOS 对消息的发送监听是对应关系, iOS 发送一个 Message 同时可以设置一个等待返回结果的 block, 也可以不设置,根据自己的具体需求而定, Flutter 端在收到消息之后, 对消息进行相应的处理, 同时可以通过回调给 iOS 端一个反馈或者返回一些数据; 反过来也是一样的, 下面让我们来看具体的对应关系.

iOS 的回调监听, 对应 Flutter 的返回值, 没有具体对应要求, Flutter 没有给返回值, iOS 监听了, 那就是无用功, 反之亦然, 所以还要根据我的们具体需求去设计.

3.1 BasicMessageChannel 初始化

保证两端 channel 名称一致. Flutter 端需要设置一个消息解码器参数.

3.1.1 iOS 初始化

self.msgChannel = [FlutterBasicMessageChannel messageChannelWithName:@"messageCahnnel" binaryMessenger:self.flutterVC.binaryMessenger];

3.1.2 flutter 初始化

final BasicMessageChannel _messageChannel = const BasicMessageChannel('messageCahnnel', StandardMessageCodec());

3.2 iOS 发送 Message

3.2.1 iOS 发送 Message

  • Message: id 类型, 要发送出去的消息.
  • reply : block, 监听回调.
    • reply: id 类型, block 回调参数, flutter 传回来的消息.

没有回调监听的

static int a = 0;

[self.msgChannel sendMessage:[NSString stringWithFormat:@"%d", a++]];

有回调监听的

static int a = 0;

[self.msgChannel sendMessage:[NSString stringWithFormat:@"%d", a++] reply:^(id  _Nullable reply) {

    NSLog(@"%@", reply);
}];

3.2.2 Flutter 接收 Message

  • handler: 逆名函数
    • message: 要发送出去的消息.
    • 返回值: 需要给 iOS 回调数据就给一个返回值, 不需要就不给返回值, 但是要加上 async, 具体位置看代码.

不给 iOS 端返回值的

 _messageChannel.setMessageHandler((message) async {
    print('收到了来自 ios 的 message: $message');
});

给 iOS 端返回值的

_messageChannel.setMessageHandler((message) {
    print('收到了来自 ios 的 message: $message');
    return Future.value('flutter 已经收到了 $message');
});

3.3 Flutter 发送 Message

3.3.1 Flutter 发送 Message

  • onChanged: 是输入框的监听方法, 输入框内容发生变化时调用.
  • str : 要发送的消息
  • result : 返回值, iOS 收到消息后, 回调回来的值.

没有回调监听的

onChanged: (String str) {
  _messageChannel.send(str);
},

有回调监听的

onChanged: (String str) async  {
  var result = await _messageChannel.send(str);
  print(result);
},

3.3.2 iOS 接收 Message

没有回调监听的

[self.msgChannel setMessageHandler:^(id  _Nullable message, FlutterReply  _Nonnull callback) {

    NSLog(@"收到了来自 flutter 的 message: %@", message);
}];

有回调监听的

[self.msgChannel setMessageHandler:^(id  _Nullable message, FlutterReply  _Nonnull callback) {

    NSLog(@"收到了来自 flutter 的 message: %@", message);
    
    callback(@"ios 已经收到了 flutter 输入的内容");
}];

4. 总结

BasicMessageChannel 通信特点:

  1. 用于二者互相发送消息, 可接收返回值.
  2. 双向通信
  3. 可持续性通信

5. 完整代码

5.1 iOS 原生代码

#import "ViewController.h"
#import <Flutter/Flutter.h>


@interface ViewController ()

@property (nonatomic, strong) FlutterEngine *flutterEngine;
@property (nonatomic, strong) FlutterViewController *flutterVC;
@property (nonatomic, strong) FlutterBasicMessageChannel *msgChannel;

@end

@implementation ViewController


- (void)viewDidLoad {
    [super viewDidLoad];
    
    FlutterViewController *vc = [[FlutterViewController alloc] initWithEngine:self.flutterEngine nibName:nil bundle:nil];

    // 全屏时 flutter 页面就需要写回调来告诉原生 dismiss
    vc.modalPresentationStyle = UIModalPresentationFullScreen;
    
    self.flutterVC = vc;
    
    self.msgChannel = [FlutterBasicMessageChannel messageChannelWithName:@"messageCahnnel" binaryMessenger:self.flutterVC.binaryMessenger];
    
    [self.msgChannel setMessageHandler:^(id  _Nullable message, FlutterReply  _Nonnull callback) {
            
        NSLog(@"收到了来自 flutter 的 message: %@", message);
        
        callback(@"ios 已经收到了 flutter 输入的内容");
        
    }];
}

- (FlutterEngine *)flutterEngine {
    if (!_flutterEngine) {
        FlutterEngine *flutterEngine = [[FlutterEngine alloc] initWithName:@"andy"];

        if ([flutterEngine run]) {
            _flutterEngine = flutterEngine;
        }
    }
    return _flutterEngine;
}

// one page
- (IBAction)pushFlutter:(id)sender {
    
    // 初始化 FlutterMethodChannel
    FlutterMethodChannel *methodChannel = [FlutterMethodChannel methodChannelWithName:@"channel_1" binaryMessenger:self.flutterVC.binaryMessenger];
    
    [methodChannel invokeMethod:@"one" arguments: nil];
    
    [self presentViewController:self.flutterVC animated:YES completion:nil];
    
    [methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {

        if ([call.method isEqual:@"dismiss"]) {
            
            [self dismissViewControllerAnimated:YES completion:nil];
        }
    }];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    static int a = 0;
    
    [self.msgChannel sendMessage:[NSString stringWithFormat:@"%d", a++] reply:^(id  _Nullable reply) {
        
        NSLog(@"%@", reply);
    }];
}

@end

5.2 Flutter 代码

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';


void main() => runApp(const MyApp());

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

  String pageIndex = '';

  final MethodChannel _channel_1 = const MethodChannel('channel_1');
  final BasicMessageChannel _messageChannel = const BasicMessageChannel('messageCahnnel', StandardMessageCodec());

  @override
  void initState() {
    // TODO: implement initState
    super.initState();

    _messageChannel.setMessageHandler((message) {

      print('收到了来自 ios 的 message: $message');

      return Future.value('flutter 已经收到了 $message');
    });

    _channel_1.setMethodCallHandler((call) async {

      setState(() {
        if (call.method == 'one') {
          pageIndex = call.method;
        }
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Module',
      theme: ThemeData(
        primarySwatch: Colors.blue
      ),
      home: _rootPage(pageIndex),
    );
  }

  Widget _rootPage(String pageIndex) {

    switch(pageIndex) {
      case 'one':
        return Scaffold(
          appBar: AppBar(
            title: Text(pageIndex),
          ),
          body: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ElevatedButton(
                child: Text(pageIndex),
                onPressed: () {
                  _channel_1.invokeMethod('dismiss');
                },
              ),
              TextField(
                onChanged: (String str) async  {

                  var result = await _messageChannel.send(str);

                  print(result);
                },
              )
            ],
          )
        );
    }

    return Scaffold(
      appBar: AppBar(
        title: const Text('hello flutter'),
      ),
      body: const Center(
        child: Text('hello flutter'),
      ),
    );
  }
}