zoukankan      html  css  js  c++  java
  • 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)
                ],
              ),
            )
        );
      }
    }
    View Code

    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

    [注意]:热重启/重新加载,需要原生工程是debug模式

    2.7调试Dart代码

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

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

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

    [注意]:调试Dart代码,需要原生工程是debug模式

    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 {
        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端保持一致;

    创建好MethodChannel之后,如果要主动给Native端发送消息,可以调用 invokeMethod 方法:

    Future<T> invokeMethod<T>(String method, [ dynamic arguments ])
    • method:调用的方法名称,Native端要做对应;
    • arguments:传递参数。

    主动发送数据到Native的代码,可以参考:

    //初始化
    static const MethodChannel _methodChannelPlugin =
          const MethodChannel('MethodChannelPlugin');
    
    //主动发送数据到Native
    String response;
    try {
        response = await _methodChannelPlugin.invokeMethod('send', value);
    } on PlatformException catch (e) {
          print(e);
    }    

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

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

    dart端主要是实现一个事件的监听,来接听来自Native端主动发送的数据,代码如下:

    //初始化
    static const EventChannel _eventChannelPlugin =
          EventChannel('EventChannelPlugin');
    
    StreamSubscription _streamSubscription = _eventChannelPlugin
            .receiveBroadcastStream('123')
            .listen(_onToDart, onError: _onToDartError);
    
    //消息监听
    void _onToDart(message) {
      setState(() {
        showMessage = message;
      });
    }
    
    //错误
    void _onToDartError(error) {
      print(error);
    }

    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];
            }
        }];
    }
  • 相关阅读:
    队列

    Oracle 12c新特性之——TABLE ACCESS BY INDEX ROWID BATCHED
    连续三月涨势明显,PostgreSQL 将崛起?
    Oracle物理DG自动切换——Dataguard Broker配置
    MSSQL索引视图(indexed view)之简述及使用
    连续三月涨势明显,PostgreSQL 将崛起?
    Scheduler & Task & Worker & Thread & Request & Session & Connection of SQL Server
    MSSQL内存架构及管理
    MSSQL数据库后台进程(线程)
  • 原文地址:https://www.cnblogs.com/LeeGof/p/10925672.html
Copyright © 2011-2022 走看看