阅读 767

iOS 与 Flutter 通信之 MethodChannel

iOS 与 Flutter 通信之 MethodChannel

1. MethodChannel 简介

MethodChannel 是 iOS 与 Flutter 通信的一种方式, 是一种双向通信, 主要用来互相调用方法, 同时可以传递参数, 也可以接收返回的数据, 是一次性的通信, 一旦通信结束, 想要再次通信, 需要重新调用对应的方法.

本文正好也解决一下 iOS 嵌入 Flutter 这篇文章中, 加载 flutter 页面卡顿的情况. 其实也比较简单, 就是 FlutterEngine 和 FlutterViewController 都用单例来管理, 提前初始化, 在程序中只保存一份, 也不需要销毁. 这样做有两个好处, 一个是解决了第一次加载卡顿问题, 另一个是内存泄漏问题.

MethodChannel 使用类似 与 JS 交互用的 WebViewJavascriptBridge.

2. MethodChannel 使用场景

假设项目是 iOS 原生嵌入 Flutter 这么一个场景, iOS 原生嵌入 Flutter 的配置流程可以看篇文章 iOS 嵌入 Flutter

2.1 iOS 调用 flutter

flutter_module 中根据 iOS 调用不同的 method, 展示不同的页面. flutter_module 中监听到 iOS 原生调用之后, 加载页面, 然后再给 iOS 端返回一些数据.

2.2 flutter 调用 iOS

点击 flutter_module 页面中的按钮, 调用 iOS 的method, iOS 端收到调用后 dismiss 掉当前页面, 再通过 block 给 flutter_module 页面回调一引起数据.

注意: 两端各自收到调用之后的回调数据, 可以有, 也可以没有, 都有对应的 API, 根据自己具体需求确定, 因为本文是在讲述用法, 就全写上了.

2.3 场景效果

  • 操作效果

QQ20210928-193505-HD.gif

  • 控制台输出

    image.png

3. 使用方法

3.0 Flutter 引擎初始化

首先要说的是, 这只是测试 Demo, 并没有封装成单例, 但是也把 FlutterEngine 和 FlutterViewController 做为 ViewController 的属性, 在 viewDidLoad 中初始化 FlutterEngine 和FlutterViewController, 并启动 FlutterEngine. 效果与单例类似, 实际开发中, 大家可以根据自己的情况来灵活运用, 理解万岁 ????????????.

完整代码放在最后

@interface ViewController ()

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

@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;
}

- (FlutterEngine *)flutterEngine {
    if (!_flutterEngine) {
        FlutterEngine *flutterEngine = [[FlutterEngine alloc] initWithName:@"andy"];
        // FlutterEngine 需要 run 起来, 有可能失败.
        // 所以只有 run 成功的时候才赋值.
        if ([flutterEngine run]) {
            _flutterEngine = flutterEngine;
        }
    }
    return _flutterEngine;
}

@end复制代码

3.1 MethodChannel 初始化

3.1.1 iOS 初始化

此处的 FlutterMethodChannel 也可以做为属性记录下来, 大家可以自己尝试一下.

FlutterMethodChannel *methodChannel = [FlutterMethodChannel methodChannelWithName:@"channel_1" binaryMessenger:self.flutterVC.binaryMessenger];复制代码

3.1.2 flutter 初始化

final MethodChannel _channel_1 = const MethodChannel('channel_1');复制代码

3.2 iOS 调用 flutter

3.2.1 iOS 中的调用

iOS 中对 flutter 的调用, 对应的是 flutter 中的监听. 当一个 method 调用过去的时候, flutter 根据 method name 做出不同的响应, 完成不同的任务, 在完成任务后, 还可以返回给 iOS 端数据.

  • 参数1NSString 类型, 是 method name, 用来表示我们要做什么事情

  • 参数2id 类型, 是传递给 flutter 端的参数,

  • 参数3: FlutterResult 类型, 是一个 block. 用来接收 flutter 传回 iOS 的数据.

    • FlutterResult : 参数 result 是 id 类型.

      typedef void (^FlutterResult)(id _Nullable result);复制代码

没有回调的

```
[methodChannel invokeMethod:@"one" arguments:@"来自 ios channel_1 的参数"];
```复制代码

有回调的

```
// 调用 flutter
// Method : Method 名称
// arguments : 传递给 flutter 的数据
// result : 是 flutter 回调回来的数据
[methodChannel invokeMethod:@"one" arguments:@"来自 ios channel_1 的参数" result:^(id  _Nullable result) {
    NSLog(@"ios: %@", result);
}];
```复制代码

3.2.2 flutter 中的监听

  • handler: 逆名函数,

    • call: MethodCall 类型参数 call, 具体在下面单独讲解.

    • 返回值: 需要给 iOS 回调数据就给一个返回值, 不需要就不给返回值, 但是要加上 async, 具体位置看代码.

  • call: 有两个属性, method 和 arguments

    • methodString 类型, iOS 调用 flutter 时传递的 method name. 可以判断 method 来完成不同的任务.

    • argumentsdynamic 类型, iOS 调用 flutter 时传递的参数, 根据对应method的参数约定类型解析即可.

没有给 iOS 回传数据的

_channel_1.setMethodCallHandler((call) async {
  setState(() {
    // 此处一个 channel 可以调用很多 method,
    if (call.method == 'two') {
      pageIndex = call.method;
    }
  });
});复制代码

有给 iOS 回传数据的

// 接收 ios _channel_1 的调用, 并返回给 ios 一个数据回调
_channel_1.setMethodCallHandler((call) {

  setState(() {
    // 此处一个 channel 可以调用很多 method,
    // if (call.method == 'two') {
      pageIndex = call.method;
    // }
  });

  return Future.value('_channel_1: flutter 响应 ios 后, 回调给 ios 的数据 111');
});复制代码

3.3 flutter 调用 iOS

3.3.1 flutter 中的调用

此处涉及到 async 所以我把按钮的点击方法参数onPressed也带过来了,

  • 参数1 : String 类型, method name, 对应 iOS 端监听中 call 的 method 属性, 以此判断需要完成的任务.

  • 参数2 : 动态类型, 调用 method 同时传递给 iOS 中的参数, 对应 iOS 端监听中 call 的 arguments 属性.

不接收 iOS 回传数据

onPressed: () {

    _channel_1.invokeMethod('dismiss', 'one: 来自 flutter 的参数 hello one');
},复制代码

接收 iOS 回传数据

onPressed: () async {

    var result = await _channel_1.invokeMethod('dismiss', 'one: 来自 flutter 的参数 hello one');

    print(result.toString());
},复制代码

3.3.2 iOS 中的监听

  • Handler: block, 两个参数 call 和 result, 具体下面讲.

  • callFlutterMethodCall 类型, 两个属性

    • method: NSString 类型, method name, 对应 flutter 调用时的method, 以此来判断需要执行的任务.

    • argumentsmethod 对应的参数. id 类型.

  • resultFlutterResult 类型, 用来给 flutter 返回数据, 是一个 block, 需要返回就调用, 不需要就不调用, 参数是 id 类型.

    typedef void (^FlutterResult)(id _Nullable result);复制代码

不给 flutter 调用返回数据的

// flutter 调用 ios
// call : method 名称和 参数
//      method -> string
//      arguments -> id
// result : 给 flutter 回调的数据, 参数是 id 类型
[methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {

    if ([call.method isEqual:@"dismiss"]) {

        NSLog(@"%@", call.arguments);

        [self dismissViewControllerAnimated:YES completion:nil];
    }
}];复制代码

给 flutter 调用返回数据的

// flutter 调用 ios
// call : method 名称和 参数
//      method -> string
//      arguments -> id
// result : 给 flutter 回调的数据, 参数是 id 类型
[methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {

    if ([call.method isEqual:@"dismiss"]) {

        NSLog(@"%@", call.arguments);

        [self dismissViewControllerAnimated:YES completion:nil];
    }

    result(@"channel_1: ios 响应 flutter 调用 dismiss 方法后返回给 flutter 页面的数据");
}];复制代码

3.4 数据传递建议

二者相互传递数据的类型建议用 json 格式字符串, 这样方便封装数据解析, 在传递数据前, 将模型数据转换成 json字符串; 在收到数据之后, 解析成模型, 这些操作大家肯定也都熟悉的很, 可以完美对接我们现在的工具链.

4. 总结

MethodChannel 通信特点:

  1. 用于二者互相调用, 可带参数, 可接收返回值.

  2. 双向通信

  3. 一次性通信

  4. iOS 端调用要在主线程

5. 完整代码

5.1 iOS 原生代码

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


@interface ViewController ()

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

@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;
}

- (FlutterEngine *)flutterEngine {
    if (!_flutterEngine) {
        FlutterEngine *flutterEngine = [[FlutterEngine alloc] initWithName:@"andy"];
        // FlutterEngine 需要 run 起来, 有可能失败.
        // 所以只有 run 成功的时候才赋值.
        if ([flutterEngine run]) {
            _flutterEngine = flutterEngine;
        }
    }
    return _flutterEngine;
}

// one page
- (IBAction)pushFlutter:(id)sender {
    
    // 初始化 FlutterMethodChannel
    FlutterMethodChannel *methodChannel = [FlutterMethodChannel methodChannelWithName:@"channel_1" binaryMessenger:self.flutterVC.binaryMessenger];
    
    // 调用 flutter
    // Method : Method 名称
    // arguments : 传递给 flutter 的数据
    // result : 是 flutter 回调回来的数据
    [methodChannel invokeMethod:@"one" arguments:@"来自 ios channel_1 的参数" result:^(id  _Nullable result) {
        NSLog(@"ios: %@", result);
    }];
    
    [self presentViewController:self.flutterVC animated:YES completion:nil];
    
    // flutter 调用 ios
    // call : method 名称和 参数
    //      method -> string
    //      arguments -> id
    // result : 给 flutter 回调的数据, 参数是 id 类型
    [methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {

        if ([call.method isEqual:@"dismiss"]) {
            NSLog(@"%@", call.arguments);
            [self dismissViewControllerAnimated:YES completion:nil];
        }
        
        result(@"channel_1: ios 响应 flutter 调用 dismiss 方法后返回给 flutter 页面的数据");
    }];
}

// two page
- (IBAction)pushFlutter_2:(id)sender {
    
    // 传递参数
    FlutterMethodChannel *methodChannel = [FlutterMethodChannel methodChannelWithName:@"channel_2" binaryMessenger:self.flutterVC.binaryMessenger];
    
    [methodChannel invokeMethod:@"two" arguments:@"来自 ios channel_2 的参数" result:^(id  _Nullable result) {
        NSLog(@"ios: %@", result);
    }];
    
    [self presentViewController:self.flutterVC animated:YES completion:nil];
    
     // flutter 调用 ios 的响应处理 并通过 result 回调返回给 flutter 一个数据
    [methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {

        if ([call.method isEqual:@"dismiss"]) {
        
             NSLog(@"%@", call.arguments);
            [self dismissViewControllerAnimated:YES completion:nil];
        }
        
        result(@"channel_2: ios 响应 flutter 调用 dismiss 方法后返回给 flutter 页面的数据");
    }];
}

@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 MethodChannel _channel_2 = const MethodChannel('channel_2');

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

    // 接收 ios _channel_1 的调用, 并返回给 ios 一个数据回调
    _channel_1.setMethodCallHandler((call) {

      setState(() {
        // 此处一个 channel 可以调用很多 method,
        if (call.method == 'one') {
          print(call.arguments);
          pageIndex = call.method;
        }
      });

      return Future.value('_channel_1: flutter 响应 ios 后, 回调给 ios 的数据 111');
    });


    // 接收 ios _channel_2 的调用, 并返回给 ios 一个数据回调
    _channel_2.setMethodCallHandler((call) {

      setState(() {
        // 此处一个 channel 可以调用很多 method,
        if (call.method == 'two') {
          print(call.arguments);
          pageIndex = call.method;
        }
      });

      return Future.value('_channel_2: flutter 响应 ios 后, 回调给 ios 的数据 222');
    });
  }

  @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: Center(
            child: ElevatedButton(
              child: Text(pageIndex),
              onPressed: () async {

                var result = await _channel_1.invokeMethod('dismiss', 'one: 来自 flutter 的参数 hello one');

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

      case 'two':
        return Scaffold(
          appBar: AppBar(
            title: Text(pageIndex),
          ),
          body: Center(
            child: ElevatedButton(
              child: Text(pageIndex),
              onPressed: () async {

                var result = await _channel_2.invokeMethod('dismiss', 'two: 来自 flutter 的参数 hello two');
                
                print(result);
              },
            ),
          ),
        );
    }

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


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐