Flutter基础系列之混合开发(二)

1.混合开发的场景

1.1作为独立页面加入

这是以页面级作为独立的模块加入,而不是页面的某个元素。

  • 原生页面可以打开Flutter页面
  • Flutter页面可以打开原生页面

1.2作为页面的一部分嵌入

比如说原生页面中只有某一个item是Flutter;

  • Flutter页面中只有某一部分是原生视图

2.Flutter混合开发的集成步骤

2.1创建Flutter Module

在做混合开发之前,我们首先需要创建一个Flutter Module。

这里建议Flutter Module的创建目录和原生工程的目录同级。假设Native的目录是这样的:xxx/Native项目。

cd xxx/
flutter create -t module flutter_module

可以看到生成的flutter_module目录下有这些文件:

README.md                  pubspec.lock
flutter_module.iml         pubspec.yaml
flutter_module_android.iml test
.android         lib               .ios

上面的.android和.ios目录,是隐藏文件, 也是这个flutter_module的宿主工程。因为有宿主工程的存在,这个flutter_module在不添加额外配置的情况下是可以独立运行的:

  • .android:flutter_module的Android宿主工程;
  • .ios:flutter_module的iOS宿主工程;
  • lib:flutter_module的Dart部分的代码;
  • pubspec.yaml:flutter_module的项目依赖配置文件。

2.2添加Flutter Module依赖:为原生项目添加Flutter的依赖

官方解决方案:https://github.com/flutter/flutter/wiki/Add-Flutter-to-existing-apps

2.2.1为已存在的iOS原生项目添加Flutter Module依赖

【说明】:在原生项目中添加Flutter Module,需要配置好CocoaPods到工程中。如果没有使用CocoaPods的,可以参考https://www.cnblogs.com/LeeGof/p/5737551.html进行配置。

第一步:在Podfile文件中添加依赖:

flutter_application_path = "../flutter_module"
eval(File.read(File.join(flutter_application_path, ‘.ios‘, ‘Flutter‘, ‘podhelper.rb‘)), binding)

第二步:安装依赖:

在项目的根目录中,执行如下指令:

pod install

第三步:禁用Bitcode:

目前Flutter还不支持Bitcode,所以集成了Flutter的iOS项目需要禁用Bitcode。

第四步:添加Build Phase来构建Dart代码。

"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed

添加完之后,要将这个Run Script拖动到Target Dependencies phase下面,接下来就可以运行项目了。

2.2.2为已存在的Android应用添加Flutter Module依赖

第一步:配置Android项目的Flutter Module依赖:打开Android项目的settings.gradle添加如下代码。这段脚本的作用是让Flutter作为一个单独的模块打包进来。

//HybridAndroid/settings.gradle
setBinding(new Binding([gradle: this]))
evaluate(new File(
        settingsDir.parentFile,
        ‘flutter_module/.android/include_flutter.groovy‘
))

setBinding和evaluate允许Flutter模块包括它自己在内的任何Flutter插件,在settings.gradle中以类似::flutter、package_info、:video_player的方式存在。

第二步:添加:flutter依赖。

//app/build.gradle
//...
dependencies {
   //...
    implementation project(‘:flutter‘)
}

在build.gradle中配置的时候,有两个地方要特别注意:

    compileOptions {  //编译需要设置成JAVA8
        sourceCompatibility 1.8
        targetCompatibility 1.8
    }
    defaultConfig {
        minSdkVersion 16  //Flutter中要求最低SDK版本为16
        //...
    }

2.3在Java/OC中调用Flutter Module

2.3.1OC中调用Flutter Module

在OC中调用Flutter Module有两种方式:

  • 直接使用FlutterViewController的方式;
  • 使用FlutterEngine的方式。

下面我们分别来看一下这两种方式。

2.3.1.1直接使用FlutterViewController

    FlutterViewController* flutterViewController = [[FlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil];
    [GeneratedPluginRegistrant registerWithRegistry:flutterViewController];  //如果使用了插件
    [flutterViewController setInitialRoute:@"myApp"];
    [self.navigationController pushViewController:flutterViewController animated:YES];

通过上面的代码,我们可以看到setInitialRoute方法传递了参数“myApp”,该参数用于告诉Dart代码显示哪个Flutter视图。在Flutter Module的main.dart文件中,需要通过window.defaultRouteName来获取Native指定要显示的路由名,以确定要创建哪个窗口小部件并传递给runApp:

void main() => runApp(_widgetForRoute(window.defaultRouteName));

Widget _widgetForRoute(String route) {
  switch (route) {
    case ‘myApp‘:
      return MyApp();
    default:
      return MaterialApp(
        home: Center(
          child: Text(‘没找到‘),
        ),
      );
  }
}

2.3.1.2使用FlutterEngine的方式

第一步:需要AppDelegate继承自FlutterAppDelegate

//AppDelegate.h
#import <UIKit/UIKit.h>
#import <Flutter/Flutter.h>

@interface AppDelegate : FlutterAppDelegate

@property (strong, nonatomic) FlutterEngine *flutterEngine;

@end

//AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    //FlutterEngine初始化
    self.flutterEngine = [[FlutterEngine alloc] initWithName:@"io.flutter" project:nil];
    [self.flutterEngine runWithEntrypoint:nil];
    [GeneratedPluginRegistrant registerWithRegistry:self.flutterEngine];  //有插件
    //设置RootVC
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    UIViewController *vc = [[ViewController alloc] init];
    UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
    self.window.rootViewController = nav;
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

第二步:通过FlutterEngine来初始化FlutterViewController。

    FlutterEngine *flutterEngine = [(AppDelegate *)[[UIApplication sharedApplication] delegate] flutterEngine];
    FlutterViewController *flutterViewController = [[FlutterViewController alloc] initWithEngine:flutterEngine nibName:nil bundle:nil];
    [self.navigationController pushViewController:flutterViewController animated:YES];

因为在AppDelegate中,我们已经提前初始化了FlutterEngine,所以这种方式打开一个Flutter模块的速度,比第一种方式要快一些。

【注意】:使用FlutterEngine方式,调用 setInitialRoute 方法会无效,在Flutter端拿到的永远是“I”,这是Flutter SDK的一个BUG,因此如果必须依赖 setInitialRoute 参数,那么只能使用方式一进行赋值。

2.3.2Java中调用Flutter Module

在Java中调用Flutter Module有两种方式:

  • 使用Flutter.createView API的方式;
  • 使用FlutterFragment的方式。

2.3.2.1使用Flutter.createView API的方式

fab.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View view) {
    View flutterView = Flutter.createView(
      MainActivity.this,
      getLifecycle(),
      "myApp"
    );
    FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(600, 800);
    layout.leftMargin = 100;
    layout.topMargin = 200;
    addContentView(flutterView, layout);
  }
});

2.3.2.2FlutterFragment的方式

fab.setOnClickListener(new View.OnClickListener() {
  @Override
  public void onClick(View view) {
    FragmentTransaction tx = getSupportFragmentManager().beginTransaction();
    tx.replace(R.id.someContainer, Flutter.createFragment("myApp"));
    tx.commit();
  }
});

上面都使用了字符串“myApp”来告诉Dart代码,在Flutter视图中显示哪个widget。在Flutter项目中可以通过 window.defaultRouteName 来获取Native传过来的“myApp”字符串,以确定要创建哪个widget并传递给runApp。

2.4编写Dart代码

import ‘package:flutter/material.dart‘;
import ‘dart:ui‘;
import ‘package:flutter/services.dart‘;

void main() => runApp(_widgetForRoute(window.defaultRouteName));

Widget _widgetForRoute(String route) {
  switch (route) {
    case ‘myApp‘:
      return new MyApp();
    default:
      return Center(
        child: Text(‘Unknown route: $route‘, textDirection: TextDirection.ltr),
      );
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: ‘Flutter Demo‘,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: ‘Flutter 混合开发‘),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  static const platform = const MethodChannel(‘gof.flutter.io/battery‘);
  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;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: <Widget>[
              RaisedButton(
                child: Text(‘Get Battery Level‘),
                onPressed: _getBatteryLevel,
              ),
              Text(_batteryLevel)
            ],
          ),
        )
    );
  }
}

2.5运行项目

代码编写完之后,就可以运行项目了。

2.6热重启/重新加载

我们知道,在做纯Flutter开发的时候,它带有热重启/重新加载的功能。但是,在混合开发中,是在原生工程中集成了Flutter项目,这时热重启/重新加载的功能失效了,那么我们怎样启用混合开发中的热重启/重新加载功能呢?

  • 第一步:打开一个模拟器,或者连接一个设备到电脑上;
  • 第二步:关闭我们的app,然后运行指令 “flutter attach”。

【注意】:执行“flutter attach”指令,有可能遇到如下报错:

NoSuchMethodError: NoSuchMethodError: The getter ‘port‘ was called on null.
Receiver: null
Tried calling: port

【解决方案】:https://github.com/flutter/flutter/issues/32471

flutter channel master
flutter upgrade

如果有多个模拟器或设备,那么需要使用如下指令来使用热重启/重新加载:

flutter attach -d BD1389B9-FF73-4114-96E9-4EE9A572A2AE

2.7调试Dart代码

上一节讲了在混合工程中使用热重启/重新加载,同样的,我们是否能够调试我们的Dart代码呢? 答案是肯定的,只需要如下两个步骤:

  • 第一步:关闭我们的app;
  • 第二步:点击Android Studio的Flutter Attach按钮(需要安装flutter和dart插件);
  • 第三步:启动我们的app。

接下来就可以像调试普通Flutter项目一样来调试混合开发模式的Dart代码了。

2.8发布应用

2.8.1发布iOS应用

发布iOS应用,首先需要一个99美元的个人开发者账号用于将app上传到App Store,或者是299美元的企业级账号用于将App发布到公司自己的服务器或者第三方服务器上。

接下来,大概就是这几个步骤:申请AppID -> 在iTunes Connect创建应用 -> 打包程序 -> 将应用提交到App Store。

2.8.2发布Android应用

发布Android应用,主要有两大步骤:签名打包 -> 发布到各个Store。

那么如何签名打包一个Flutter开发的App呢?

第一步:生成Android签名证书。签名APK需要一个证书用于为APP签名,生成签名证书可以在Android Studio中,以可视化的方式生成,也可以使用终端命令的方式生成。

第二步:设置gradle变量。

  • 将你的签名证书拷贝到android/app目录下;
  • 编辑~/.gradle/gradle.properties或../android/gradle.properties(一个是全局gradle.properties,一个是项目中的gradle.properties),加入如下代码:

  • 在gradle配置文件中添加签名配置。编辑android/app/build.gradle文件,添加如下代码:

  • 签名打包APK。终端进入android目录,运行如下代码:
./gradlew assembleRelease

3.Flutter与Native通信机制

在讲解Flutter与Native之间是如何传递数据之前,我们先来了解一下它们的通信机制,Flutter与Native的通信是通过Channel来完成的。

消息使用Channel(平台通道)在Flutter和Native之间传递,如下图所示:

平台所支持的数据类型如下表所示:

Flutter定义了三种不同类型的Channel:

  • BasicMessageChannel:用于传递字符串和半结构化的信息,持续通信,收到消息后可以回复此次消息。例如:Native将遍历到的文件信息陆续传递到Dart;Flutter将服务端获取的数据交给Native加工,Native处理完之后返回。
  • MethodChannel:用于传递方法调用,一次性通信。例如:Flutter调用Native拍照。
  • EventChannel:用于数据流的通信,持续通信,收到消息后无法回复此次消息,通常用于Native向Dart的通信。例如:手机电量变化,网络连接变化,陀螺仪,传感器等。

这三种类型的Channel都是全双工通信,即A <=> B,Dart可以主动发送消息到Native端,并且Native接收消息后可以做出回应。同样地,Native端也可以主动发送消息到Dart端,Dart端接受消息后返回给Native端。

3.1Flutter与iOS通信开发指南

现在我们来看看上面三种类型的Channel,在iOS端是怎么实现的。

3.1.1BasicMessageChannel

我们先看一下iOS端该Channel的构造函数:

+ (instancetype)messageChannelWithName:(NSString*)name
                       binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger
                                 codec:(NSObject<FlutterMessageCodec>*)codec;
  • name:channel的名称,也是唯一标识符;
  • messenger:消息信使,是消息发送和接收的工具;
  • codec:消息的编解码器,它有几种不同类型的实现:
    • BinaryCodec:最为简单的一种Codec,因为其返回值类型和入参的类型相同,都是二进制格式(Android平台为ByteBuffer,iOS平台为NSData)。实际上,FlutterBinaryCodec在编解码过程中什么都没做,只是原封不动将二进制数据消息返回。可以在这种情况下使用:传递内存数据块时,在编解码阶段免于内存拷贝。
    • FlutterBinaryCodec:是FlutterBinaryMessenger的默认编解码器,其支持基础数据类型、二进制数据、列表、字典。
    • FlutterStringCodec:用于字符串和二进制数据之间的编解码,其编码格式为UTF-8;
    • FlutterJSONMessageCodec:用于基础数据与二进制数据之间的编解码,其支持基础数据类型以及列表、字典。其在iOS端使用了NSJSONSerialization作为序列化的工具,而在Android端则使用了其自定义的JSONUtil和StringCodec作为序列化工具。

在创建好BasicMessageChannel之后,如果要让其接收到来自Dart的消息,则需要设置它的setMessageHandler方法为其设置一个消息处理器:

- (void)setMessageHandler:(FlutterMessageHandler _Nullable)handler;
  • handler:消息处理器,配合BinaryMessenger完成消息的处理。

FlutterMessageHandler的定义如下:

//message:消息内容
//callback:回复消息的回调函数
typedef void (^FlutterMessageHandler)(id _Nullable message, FlutterReply callback);

如果要给Dart发送消息,可以调用如下方法:

- (void)sendMessage:(id _Nullable)message;
- (void)sendMessage:(id _Nullable)message
              reply:(FlutterReply _Nullable)callback;
  • message:要传递给Dart的信息;
  • callback:消息发出去后,收到Dart回复的回调函数。

主动发送数据到Dart和接收来自Dart的消息的代码,可以参考:

- (void)initMessageChannel{
    self.messageChannel = [FlutterBasicMessageChannel messageChannelWithName:@"BasicMessageChannelPlugin" binaryMessenger:self.flutterViewController codec:[FlutterStringCodec sharedInstance]];
    __weak typeof(self)weakSelf = self;
    //设置消息处理器,处理来自Dart的消息
    [self.messageChannel setMessageHandler:^(NSString* message, FlutterReply reply) {
        reply([NSString stringWithFormat:@"BasicMessageChannel收到:%@",message]);
        [weakSelf sendShow:message];
    }];
}

//使用FlutterBasicMessageChannel发送数据
[self.messageChannel sendMessage:self.tfInput.text reply:^(id  _Nullable reply) {
    if (reply != nil) {
        [self sendShow:reply];
    }
}];    

接下来我们看一下Dart端的实现:

还是先看构造函数:

const BasicMessageChannel(this.name, this.codec);
  • name:Channel的名字,要和Native端保持一致;
  • codec:消息的编解码器,要和Native端保持一致,有四种类型的实现。

创建好BasicMessageChannel之后,如果要让其接收来自Native的消息,则需要调用它的setMessageHandler方法为其设置一个消息处理器。

void setMessageHandler(Future<T> handler(T message)) 
  • handler:消息处理器,配合BinaryMessenger完成消息的处理。

如果要主动给Native发送消息,可以调用send方法:

Future<T> send(T message)
  • message:要传递给Native的消息;
  • 返回值:消息发出去后,收到Native回复的回调函数。

主动发送数据到Native和接收来自Native的消息的代码,可以参考:

static const BasicMessageChannel<String> _basicMessageChannel = const BasicMessageChannel(‘BasicMessageChannelPlugin‘, StringCodec());
//使用BasicMessageChannel接收来自Native的消息,并向Native回复
_basicMessageChannel.setMessageHandler((String message) => Future<String>(() {
    setState(() {
        showMessage = ‘BasicMessageChannel:‘+message;
    });
    return "收到Native的消息:" + message;
}));

//使用BasicMessageChannel向Native发送消息,并接收Native的回复
String response;
try {
    if (_isMethodChannelPlugin) {
        response = await _basicMessageChannel.send(value);
    }
} on PlatformException catch (e) {
    print(e);
}

3.1.2FlutterMethodChannel

我们还是先从iOS端的构造函数看起:

//创建FlutterStandardMethodCodec类型的codec
+ (instancetype)methodChannelWithName:(NSString*)name
                      binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger;

+ (instancetype)methodChannelWithName:(NSString*)name
                      binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger
                                codec:(NSObject<FlutterMethodCodec>*)codec;
  • name:Channel名字,也是唯一标识符;
  • messenger:消息信使,消息发送和接收的工具;
  • codec:用作MethodChannel的编解码器。

在创建好MethodChannel之后,需要设置一个消息处理器,以便能够接收来自Dart的消息:

- (void)setMethodCallHandler:(FlutterMethodCallHandler _Nullable)handler;
  • handler:消息处理器,配合BinaryMessenger完成消息的处理。

FlutterMethodCallHandler的定义如下:

typedef void (^FlutterMethodCallHandler)(FlutterMethodCall* call, FlutterResult result);
  • call:消息内容,它有两个成员变量:字符串类型的call.method表示调用的方法名;id类型的call.arguments表示调用方法所传的参数。
  • result:回复此消息的回调函数。

iOS端的具体使用:

- (void)initMethodChannel{
    self.methodChannel = [FlutterMethodChannel methodChannelWithName:@"MethodChannelPlugin" binaryMessenger:self.flutterViewController];
    __weak typeof(self)weakSelf = self;
    [self.methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
        if ([@"send" isEqualToString:call.method]) {
            result([NSString stringWithFormat:@"MethodChannelPlugin收到:%@",call.arguments]);//返回结果给Dart);
            [weakSelf sendShow:call.arguments];
        }
    }];
}

接下来看一下Dart端的实现:

MethodChannel的构造函数:

const MethodChannel(this.name, [this.codec = const StandardMethodCodec(), this.binaryMessenger = defaultBinaryMessenger ])
  • name:Channel的名字,和Native端保持一致;
  • codec:消息的编解码器,默认是StandardMethodCodec,要和Native端保持一致;

3.1.3EventChannel

先看iOS端的构造函数:

//创建一个FlutterStandardMethodCodec类型的EventChannel
+ (instancetype)eventChannelWithName:(NSString*)name
                     binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger;

+ (instancetype)eventChannelWithName:(NSString*)name
                     binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger
                               codec:(NSObject<FlutterMethodCodec>*)codec;
  • name:Channel名称,也是唯一标识符;
  • messenger:消息信使,消息发送和接收的工具;
  • codec:EventChannel的编解码器。

在创建好EventChannel之后,如果要让其接收Dart发来的消息,需要调用如下方法来设置一个消息处理器:

- (void)setStreamHandler:(NSObject<FlutterStreamHandler>* _Nullable)handler;
  • handler:消息处理器,是一个协议,配合BinaryMessenger完成消息的处理。
@protocol FlutterStreamHandler
//Native监听事件时调用
//arguments:传递的参数
//events:Native回调Dart时的回调函数,它提供success、error、endOfStream三个回调方法分别对应事件的不同状态
- (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments
                                       eventSink:(FlutterEventSink)events;
//Flutter取消监听时调用
- (FlutterError* _Nullable)onCancelWithArguments:(id _Nullable)arguments;
@end

iOS的具体使用:

- (void)initEventChannel{
    self.eventChannel = [FlutterEventChannel eventChannelWithName:@"EventChannelPlugin" binaryMessenger:self.flutterViewController];

    //设置消息处理器,处理来自Dart的消息
    [self.eventChannel setStreamHandler:self];
}

#pragma mark - <FlutterStreamHandler>

//这个onListen是Flutter端开始监听这个channel时的回调,第二个参数 EventSink是用来传数据的载体
- (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(FlutterEventSink)eventSink {
    //arguments flutter给native的参数
    //回调给flutter,建议使用实例指向,因为该block可以使用多次
    self.eventSink = eventSink;
    return nil;
}

//flutter不再接收
- (FlutterError* _Nullable)onCancelWithArguments:(id _Nullable)arguments {
    // arguments flutter给native的参数
    self.eventSink = nil;
    return nil;
}

//使用EventChannel发送数据
if (self.eventSink != nil) {
    self.eventSink(message);
} 

3.2Flutter与Android通信开发指南

略。

4.混合开发实战

4.1初始化时Native向Dart传递数据

Flutter允许我们在初始化Flutter页面时,向Flutter传递一个string类型的 initialRoute 参数,从这个名字可以看出,它是用作路由名称的。既然是string类型的,那么我们在初始化Flutter时,就可以很灵活的去应用了,比如说传一个json格式的字符串,传递更多参数给Flutter端。示例如下:

- (FlutterViewController *)flutterViewController {
    if (nil == _flutterViewController) {
        _flutterViewController = [[FlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil];
        [_flutterViewController setInitialRoute:@"{‘name‘:‘myApp‘,‘data‘:{‘userId‘:‘00001‘,‘userName‘:‘LeeGof‘}}"];
    }
    return _flutterViewController;
}

然后在Dart端通过如下方式接收参数:

void main() {
  String initParams = window.defaultRouteName;
  runApp(_widgetForRoute(initParams));
}

4.2Native到Dart的通信(Native发送数据到Dart)

在Flutter中,Native向Dart传递消息可以通过 BasicMessageChannel 或 EventChannel 来实现。

首先我们看一下 BasicMessageChannel 方式的实现。

- (void)initMessageChannel{
    self.messageChannel = [FlutterBasicMessageChannel messageChannelWithName:@"BasicMessageChannelPlugin" binaryMessenger:self.flutterViewController codec:[FlutterStringCodec sharedInstance]];
    __weak typeof(self)weakSelf = self;
    //设置消息处理器,处理来自Dart的消息
    [self.messageChannel setMessageHandler:^(NSString* message, FlutterReply reply) {
        reply([NSString stringWithFormat:@"BasicMessageChannel收到:%@",message]);
        [weakSelf sendShow:message];
    }];
}

- (void)btnSend:(id)sender {
    [self.view endEditing:YES];
    NSString *message = self.tfInput.text;
    if (message && message.length > 0) {
        if (self.isEventChannel) {
            //使用EventChannel发送数据
            if (self.eventSink != nil) {
                self.eventSink(message);
            }
        }
        else {
            //使用FlutterBasicMessageChannel发送数据
            [self.messageChannel sendMessage:message reply:^(id  _Nullable reply) {
                if (reply != nil) {
                    [self sendShow:reply];
                }
            }];
        }
    }
}

EventChannel 方式和 BasicMessageChannel 方式类似:

- (void)initEventChannel{
    self.eventChannel = [FlutterEventChannel eventChannelWithName:@"EventChannelPlugin" binaryMessenger:self.flutterViewController];

    //设置消息处理器,处理来自Dart的消息
    [self.eventChannel setStreamHandler:self];
}

#pragma mark - <FlutterStreamHandler>

//这个onListen是Flutter端开始监听这个channel时的回调,第二个参数 EventSink是用来传数据的载体
- (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(FlutterEventSink)eventSink {
    //arguments flutter给native的参数
    //回调给flutter,建议使用实例指向,因为该block可以使用多次
    self.eventSink = eventSink;
    return nil;
}

//flutter不再接收
- (FlutterError* _Nullable)onCancelWithArguments:(id _Nullable)arguments {
    // arguments flutter给native的参数
    self.eventSink = nil;
    return nil;
}

Dart端通过如下方式接收数据:

  static const BasicMessageChannel<String> _basicMessageChannel =
      const BasicMessageChannel(‘BasicMessageChannelPlugin‘, StringCodec());

    //使用BasicMessageChannel接受来自Native的消息,并向Native回复
    _basicMessageChannel
        .setMessageHandler((String message) => Future<String>(() {
              setState(() {
                showMessage = ‘BasicMessageChannel:‘ + message;
              });
              return "收到Native的消息:" + message;
            }));

  static const EventChannel _eventChannelPlugin =
      EventChannel(‘EventChannelPlugin‘);

  void _onToDart(message) {
    setState(() {
      showMessage = ‘EventChannel:‘ + message;
    });
  }

  void _onToDartError(error) {
    print(error);
  }

4.3Dart到Native的通信(Dart发送数据到Native)

在Flutter中,Dart向Native传递消息可以通过 BasicMessageChannel 或 MethodChannel 来实现。

首先看一下Dart发送的相关代码:

    String response;
    try {
      if (_isMethodChannelPlugin) {
        //使用BasicMessageChannel向Native发送消息,并接受Native的回复
        response = await _methodChannelPlugin.invokeMethod(‘send‘, value);
      } else {
        response = await _basicMessageChannel.send(value);
      }
    } on PlatformException catch (e) {
      print(e);
    }
    setState(() {
      showMessage = response ?? "";
    });

Native端接收:

- (void)initMessageChannel{
    self.messageChannel = [FlutterBasicMessageChannel messageChannelWithName:@"BasicMessageChannelPlugin" binaryMessenger:self.flutterViewController codec:[FlutterStringCodec sharedInstance]];
    __weak typeof(self)weakSelf = self;
    //设置消息处理器,处理来自Dart的消息
    [self.messageChannel setMessageHandler:^(NSString* message, FlutterReply reply) {
        reply([NSString stringWithFormat:@"BasicMessageChannel收到:%@",message]);
        [weakSelf sendShow:message];
    }];
}

- (void)initMethodChannel{
    self.methodChannel = [FlutterMethodChannel methodChannelWithName:@"MethodChannelPlugin" binaryMessenger:self.flutterViewController];
    __weak typeof(self)weakSelf = self;
    [self.methodChannel setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
        if ([@"send" isEqualToString:call.method]) {
            result([NSString stringWithFormat:@"MethodChannelPlugin收到:%@",call.arguments]);//返回结果给Dart);
            [weakSelf sendShow:call.arguments];
        }
    }];
}

原文地址:https://www.cnblogs.com/LeeGof/p/10925672.html

时间: 2024-10-11 12:21:07

Flutter基础系列之混合开发(二)的相关文章

Flutter基础系列之入门(一)

1.Flutter是什么? 官方介绍:Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面. Flutter可以与现有的代码一起工作.在全世界,Flutter正在被越来越多的开发者和组织使用,并且Flutter是完全免费.开源的. 从官方介绍可以看到,Flutter有如下特点: 跨平台:现在Flutter至少可以跨4种平台,甚至支持嵌入式开发.我们常用的有Linux.Android.IOS,甚至可以在谷歌最新的操作系统上Fuchsia进行运行,经过第三方

H5混合开发二维码扫描以及调用本地摄像头

今天主管给了我个需求,说要用混合开发,用H5调用本地摄像头进行扫描二维码,我之前有做过原生安卓的二维码扫一扫,主要是通过调用zxing插件进行操作的,其中还弄了个闪光灯.但是纯H5的没接触过,心里没底,于是晚上回家开始网上各处找方案.以下是我对于H5扫描二维码以及调用本地摄像头的理解以及代码. 科普网址: H5如何生成安卓组件对象 H5调用安卓本地摄像头api 在线二维码图片生成器 二维码扫描:(使用的是mui的框架,下面是html代码) <!doctype html> <html>

Python服务器开发二:Python网络基础

Python服务器开发二:Python网络基础 网络由下往上分为物理层.数据链路层.网络层.传输层.会话层.表示层和应用层. HTTP是高层协议,而TCP/IP是个协议集,包过许多的子协议.包括:传输层的 FTP,UDP,TCP协议等,网络层的ip协议等,高层协议如HTTP,telnet协议等,HTTP是TCP/IP的一个子协议. socket是对TCP/IP协议的封装和应用(程序员层面上).也可以说,TPC/IP协议是传输层协议,主要解决数据如何在网络中传输,而HTTP是应用层协议,主要解决如

C#基础系列:开发自己的窗体设计器(PropertyGrid显示中文属性名)

既然是一个窗体设计器,那就应该能够设置控件的属性,设置属性最好的当然是PropertyGrid了,我们仅仅需要使用一个PropertyGrid.SelectedObject = Control就可以搞定,让PropertyGrid显示Control的所有属性.可是这里显示的属性名是英文的.对于我们开发人员来说这无可厚非,我们也乐于接受.并且让PropertyGrid显示中文属性名,这对于我们开发人员的使用来说显得多此一举.可是,对于我这种类型的一个应用工具,英文属性名对于很多客户来说可能就很难懂

J2EE开发实战基础系列之开卷有益

时隔七年再次接触培训有关的事情,是兴奋,更多的是恐惧,不知该如何下手. 本系列针对有Java语法基础的开发者或者爱好者,从工作开发角度出发讲解,不同于其他视频,一切皆以实用为主,过程中如有疑问,请提问于我,回答将发布在教程中添加提问部分,提问者越多,教程覆盖越全面,以实际问题为主. ----------------------------------------------------------------------------------------- 首先介绍下目前J2EE方面培训的入门

C++重点知识点(基础系列二)

C++重点知识点基类 C++重点知识点(基础系列二),布布扣,bubuko.com

J2EE开发实战基础系列一 HelloWorld

开始咱们的第一个程序,首先是配置环境,按照上一章所描述的方式下载开发工具,然后配置Java环境变量,给大家看下具体的结构: 环境变量配置OK的提示,如上图. Eclipse和Tomcat的文件目录位置,本系列采用的都是绿色版本,如上图. 启动Eclipse.exe,Workspace路径的配置,下面的复选框表示选中后就默认一直使用该工作空间,不选择每次启动都出出现该提示框,如上图. 在这里讲解下Workspace的概念,这里目录存储项目程序段的,假如你在别的目录创建一个Java的项目,那么在Wo

J2EE开发实战基础系列一 HelloWorld【转】

开始咱们的第一个程序,首先是配置环境,按照上一章所描述的方式下载开发工具,然后配置Java环境变量,给大家看下具体的结构: 环境变量配置OK的提示,如上图. Eclipse和Tomcat的文件目录位置,本系列采用的都是绿色版本,如上图. 启动Eclipse.exe,Workspace路径的配置,下面的复选框表示选中后就默认一直使用该工作空间,不选择每次启动都出出现该提示框,如上图. 在这里讲解下Workspace的概念,这里目录存储项目程序段的,假如你在别的目录创建一个Java的项目,那么在Wo

Python 迭代器&amp;生成器,装饰器,递归,算法基础:二分查找、二维数组转换,正则表达式,作业:计算器开发

本节大纲 迭代器&生成器 装饰器  基本装饰器 多参数装饰器 递归 算法基础:二分查找.二维数组转换 正则表达式 常用模块学习 作业:计算器开发 实现加减乘除及拓号优先级解析 用户输入 1 - 2 * ( (60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) - (-4*3)/ (16-3*2) )等类似公式后,必须自己解析里面的(),+,-,*,/符号和公式,运算后得出结果,结果必须与真实的计算器所得出的结果一致 迭代器&