美文网首页
6.Flutte3.0 遥遥领先系列|一文教你完全掌握原生交互(

6.Flutte3.0 遥遥领先系列|一文教你完全掌握原生交互(

作者: 鹏城十八少 | 来源:发表于2024-01-31 14:35 被阅读0次

    目录:

    1.3种方式比较, BasicMessageChannel, MethodChannel, EventChannel
    2.原生交互的缺点
    3.Pigeon插件的使用
    4.fluttter 嵌套原生的view视图
    5.fluttter 动态嵌套原生的view视图

    解决问题: Flutter和原生交互分为2种, 数据传输和view的控制显示

    形象的举例:

    一次典型的方法调用过程类似网络调用!
    由作为客户端的 Flutter,通过方法通道向作为服务端的原生代码宿主发送方法调用请求,原生代码宿主在监听到方法调用的消息后,调用平台相关的 API 来处理 Flutter 发起的请求,最后将处理完毕的结果通过方法通道回发至 Flutter
    dart与Native之间多了一层C++写的Engine,至于flutter的C++源码,

    架构图:


    原生交互的类型.jpg
    1. 3种方式比较, BasicMessageChannel, MethodChannel, EventChannel
      Flutter为开发者提供了一个轻量级的解决方案,即逻辑层的方法通道(Method Channel)机制。基于方法通道,我们可以将原生代码所拥有的能力,以接口形式暴露给Dart,从而实现Dart代码与原生代码的交互,就像调用了一个普通的Dart API一样。
      Flutter定义了三种不同类型的Channel

    1>. BasicMessageChannel:用于传递字符串和半结构化的信息。BasicMessageChannel支持数据双向传递,有返回值。

    2> .MethodChannel:用于传递方法调用(method invocation)。MethodChannel支持数据双向传递,有返回值。通常用来调用 native 中某个方法

    3>. EventChannel: 用于数据流(event streams)的通信。 EventChannel仅支持数据单向传递,无返回值。有监听功能,比如电量变化之后直接推送数据给flutter端
    对比表格:

    3种方式的对比.jpg

    实现调用步骤 :
    1). Flutter能够通过轻量级的异步方法调用,实现与原生代码的交互。一次典型的调用过程由Flutter发起方法调用请求开始,
    2). 请求经由唯一标识符指定的方法通道到达原生代码宿主,
    3). 而原生代码宿主则通过注册对应方法实现、响应并处理调用请求,
    4). 最后将执行结果通过消息通道,回传至Flutter。
    5). 方法调用请求的处理和响应,在Android中是通过FlutterView

    plantview.png
    1.1 问题:Flutter如何实现一次方法调用请求?

    方法调用过程是异步的,所以我们需要使用非阻塞(或者注册回调)来等待原生代码给予响应
    非阻塞: 就是单线程中的处理方案
    1.1 .1 案例演示: flutter调用原生的方法, MethodChannel

    Flutter代码

      void getBatteryInfo() async {
          
        // 声明 MethodChannel
         const platform = MethodChannel('samples.chenhang/utils');
        // 核心代码二
        final int result = await platform.invokeMethod("getBatteryInfo");
         // 返回值的类型是啥????,可以统一定义为json
        setState(() {
          _result = result;
        });
      }
    }
    

    伪代码: flutter发起原生调用

    // 声明 MethodChannel
    const platform = MethodChannel('samples.chenhang/utils');
     
    // 处理按钮点击
    handleButtonClick() async{
      int result;
      // 异常捕获
      try {
        // 异步等待方法通道的调用结果
        result = await platform.invokeMethod('openAppMarket');
      }
      catch (e) {
        result = -1;
      }
      print("Result:$result");
    }
    
    1.1.2 原生通过MethodChannel调用flutter的案例:

    在原生代码中完成方法调用的响应(flutter监听原生调用)
    android代码
    考虑到打开应用市场的过程可能会出错,我们也需要增加 try-catch 来捕获可能的异常:
    核心类:
    · ) MethodChannel
    · ) DartExecutor
    · ) BinaryMessenger:是一个接口,在FlutterView中实现了该接口,在BinaryMessenger的方法中通过JNI来与系统底层沟通
    · ) FlutterEngine: 发送数据必然要通过
    内部都是通过DartMessenger来调用FlutterJNI的相关API完成通信的

    public class MainActivity extends FlutterActivity {
      private static final String CHANNEL = "coderwhy.com/battery";
    
      @Override
      public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
        // 1.创建MethodChannel对象
        MethodChannel methodChannel = new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL);
    
        // 2.添加调用方法的回调
        methodChannel.setMethodCallHandler(
            (call, result) -> {
              // 2.1.如果调用的方法是getBatteryInfo,那么正常执行
              if (call.method.equals("getBatteryInfo")) {
    
                // 2.1.1.调用另外一个自定义方法回去电量信息
                int batteryLevel = getBatteryLevel();
    
                // 2.1.2. 判断是否正常获取到
                if (batteryLevel != -1) {
                  // 获取到返回结果
                  result.success(batteryLevel);
                } else {
                  // 获取不到抛出异常
                  result.error("UNAVAILABLE", "Battery level not available.", null);
                }
              } else {
                // 2.2.如果调用的方法是getBatteryInfo,那么正常执行
                result.notImplemented();
              }
            }
          );
      }
    

    需要注意的是,方法通道是非线程安全的。这意味着原生代码与 Flutter 之间所有接口调用必须发生在主线程

    1.2. 1 MethodChannel(flutter----> 原生----(返回结果到)---> flutter)

    源码分析:
    原理: 从flutter开始调用:
    调用栈图:

    methodChannel调用流程图.jpg
    class MethodChannel {
      /// Creates a [MethodChannel] with the specified [name].
      ///
      /// The [codec] used will be [StandardMethodCodec], unless otherwise
      /// specified.
      ///
      /// The [name] and [codec] arguments cannot be null. The default [ServicesBinding.defaultBinaryMessenger]
      /// instance is used if [binaryMessenger] is null.
      const MethodChannel(this.name, [this.codec = const StandardMethodCodec(), BinaryMessenger? binaryMessenger ])
          : _binaryMessenger = binaryMessenger;
    
      @optionalTypeArgs
      Future<T?> _invokeMethod<T>(String method, { required bool missingOk, dynamic arguments }) async {
        final ByteData input = codec.encodeMethodCall(MethodCall(method, arguments)); // 编码数据
        final ByteData? result =
          !kReleaseMode && debugProfilePlatformChannels ? 
            await (binaryMessenger as _ProfiledBinaryMessenger).sendWithPostfix(name, '#$method', input) :
            await binaryMessenger.send(name, input);    // binaryMessenger发送数据
        if (result == null) {
          if (missingOk) {
            return null;
          }
          throw MissingPluginException('No implementation found for method $method on channel $name');
        }
        return codec.decodeEnvelope(result) as T?;
      }
    

    _DefaultBinaryMessenger

      @override
      Future<ByteData?> send(String channel, ByteData? message) {
        final Completer<ByteData?> completer = Completer<ByteData?>();
        ui.PlatformDispatcher.instance.sendPlatformMessage(channel, message, (ByteData? reply) {
          try {
            completer.complete(reply);
          } catch (exception, stack) {
            FlutterError.reportError(FlutterErrorDetails(
              exception: exception,
              stack: stack,
              library: 'services library',
              context: ErrorDescription('during a platform message response callback'),
            ));
          }
        });
        return completer.future;
      }
    
    调用到PlatformDispatcher的_sendPlatformMessage方法,进入flutter engine层的_SendPlatformMessage。
    @Native<Handle Function(Handle, Handle, Handle)>(symbol: 'PlatformConfigurationNativeApi::SendPlatformMessage')
      external static String? __sendPlatformMessage(String name, PlatformMessageResponseCallback? callback, ByteData? data);
    

    PlatformConfiguration

    void _SendPlatformMessage(Dart_NativeArguments args) {
      tonic::DartCallStatic(&SendPlatformMessage, args);
    }
    }
    void PlatformConfiguration::RegisterNatives(
        tonic::DartLibraryNatives* natives) {
      natives->Register({
          {"PlatformConfiguration_sendPlatformMessage", _SendPlatformMessage, 4,
           true},
          ...
      });
    }
    
    Dart_Handle SendPlatformMessage(Dart_Handle window,
                                    const std::string& name,
                                    Dart_Handle callback,
                                    Dart_Handle data_handle) {
      UIDartState* dart_state = UIDartState::Current();
    
      if (!dart_state->platform_configuration()) {
        return tonic::ToDart(
            "Platform messages can only be sent from the main isolate");
      }
    
      fml::RefPtr<PlatformMessageResponse> response;
      if (!Dart_IsNull(callback)) {
        //PlatformMessageResponseDart对象中采用的是UITaskRunner
        response = fml::MakeRefCounted<PlatformMessageResponseDart>(
            tonic::DartPersistentValue(dart_state, callback),
            dart_state->GetTaskRunners().GetUITaskRunner());
      }
      if (Dart_IsNull(data_handle)) {
        dart_state->platform_configuration()->client()->HandlePlatformMessage(
            std::make_unique<PlatformMessage>(name, response));
      } else {
        tonic::DartByteData data(data_handle);
        const uint8_t* buffer = static_cast<const uint8_t*>(data.data());
        dart_state->platform_configuration()->client()->HandlePlatformMessage(
            std::make_unique<PlatformMessage>(
                name, fml::MallocMapping::Copy(buffer, data.length_in_bytes()),
                response));
      }
    
      return Dart_Null();
    }
    
    

    RuntimeController //flutter/runtime/runtime_controller.cc

    void RuntimeController::HandlePlatformMessage(
        std::unique_ptr<PlatformMessage> message) {
      client_.HandlePlatformMessage(std::move(message));
    }
    

    Engine

    static constexpr char kAssetChannel[] = "flutter/assets";
    
    void Engine::HandlePlatformMessage(fml::RefPtr<PlatformMessage> message) {
      if (message->channel() == kAssetChannel) {
        HandleAssetPlatformMessage(std::move(message));
      } else {
        delegate_.OnEngineHandlePlatformMessage(std::move(message));
      }
    }
    
    

    Shell //flutter/shell/common/shell.cc

    constexpr char kSkiaChannel[] = "flutter/skia";
    
    void Shell::OnEngineHandlePlatformMessage(
        fml::RefPtr<PlatformMessage> message) {
      if (message->channel() == kSkiaChannel) {
        HandleEngineSkiaMessage(std::move(message));
        return;
      }
      task_runners_.GetPlatformTaskRunner()->PostTask(
          [view = platform_view_->GetWeakPtr(), message = std::move(message)]() {
            if (view) {
              view->HandlePlatformMessage(std::move(message));
            }
          });
    }
    
    

    核心: # PlatformViewAndroid
    //flutter/shell/platform/android/platform_view_android.cc
    这个view对于Android平台的实例为PlatformViewAndroid。

    std::unordered_map<int, fml::RefPtr<flutter::PlatformMessageResponse>>
          pending_responses_;
    
    // |PlatformView|
    void PlatformViewAndroid::HandlePlatformMessage(
        std::unique_ptr<flutter::PlatformMessage> message) {
      int response_id = 0;
      if (auto response = message->response()) {
        response_id = next_response_id_++;
        pending_responses_[response_id] = response;
      }
      // This call can re-enter in InvokePlatformMessageXxxResponseCallback.
      jni_facade_->FlutterViewHandlePlatformMessage(std::move(message),
                                                    response_id);
      message = nullptr;
    }
    
    

    c调用android ,对应FlutterJNI.java中的handlePlatformMessage()方法

    void FlutterViewHandlePlatformMessage(JNIEnv* env, jobject obj,
                                          jstring channel, jobject message,
                                          jint responseId) {
      env->CallVoidMethod(obj, g_handle_platform_message_method, channel, message, responseId);
    }
    

    Android 端源码分析:
    FlutterJN

      @VisibleForTesting
      public void handlePlatformMessage(
          @NonNull final String channel,
          ByteBuffer message,
          final int replyId,
          final long messageData) {
        if (platformMessageHandler != null) {
          platformMessageHandler.handleMessageFromDart(channel, message, replyId, messageData);
        } else {
          nativeCleanupMessageData(messageData);
        }
        // TODO(mattcarroll): log dropped messages when in debug mode
        // (https://github.com/flutter/flutter/issues/25391)
      }
    
      public void handleMessageFromDart(
          @NonNull String channel, @Nullable ByteBuffer message, int replyId, long messageData) {
        Log.v(TAG, "Received message from Dart over channel '" + channel + "'");
        synchronized (handlersLock) {
          handlerInfo = messageHandlers.get(channel);
          messageDeferred = (enableBufferingIncomingMessages.get() && handlerInfo == null);
          if (messageDeferred) {
            if (!bufferedMessages.containsKey(channel)) {
              bufferedMessages.put(channel, new LinkedList<>());
            }
            List<BufferedMessageInfo> buffer = bufferedMessages.get(channel);
            buffer.add(new BufferedMessageInfo(message, replyId, messageData));
          }
        }
        if (!messageDeferred) {
          dispatchMessageToQueue(channel, handlerInfo, message, replyId, messageData);
        }
      }
    

    把消息添加到队列中去!

      private void dispatchMessageToQueue(
          @NonNull String channel,
          @Nullable HandlerInfo handlerInfo,
          @Nullable ByteBuffer message,
          int replyId,
          long messageData) {
        // Called from any thread.
        final DartMessengerTaskQueue taskQueue = (handlerInfo != null) ? handlerInfo.taskQueue : null;
        TraceSection.beginAsyncSection("PlatformChannel ScheduleHandler on " + channel, replyId);
        Runnable myRunnable =
            () -> {
              TraceSection.endAsyncSection("PlatformChannel ScheduleHandler on " + channel, replyId);
              TraceSection.begin("DartMessenger#handleMessageFromDart on " + channel);
              try {
                invokeHandler(handlerInfo, message, replyId); // 调用
                if (message != null && message.isDirect()) {
                  // This ensures that if a user retains an instance to the ByteBuffer and it
                  // happens to be direct they will get a deterministic error.
                  message.limit(0);
                }
              } finally {
                // This is deleting the data underneath the message object.
                flutterJNI.cleanupMessageData(messageData);
                TraceSection.end();
              }
            };
        final DartMessengerTaskQueue nonnullTaskQueue =
            taskQueue == null ? platformTaskQueue : taskQueue;
        nonnullTaskQueue.dispatch(myRunnable);
      }
    

    所有的Channel都会走上面的逻辑,从这里的handlerInfo.handler.onMessage开始有所不一样了,因为不同的Channel的handler不同。

      private void invokeHandler(
          @Nullable HandlerInfo handlerInfo, @Nullable ByteBuffer message, final int replyId) {
        // Called from any thread.
        if (handlerInfo != null) {
          try {
            Log.v(TAG, "Deferring to registered handler to process message.");
            handlerInfo.handler.onMessage(message, new Reply(flutterJNI, replyId));
          } catch (Exception ex) {
            Log.e(TAG, "Uncaught exception in binary message listener", ex);
            flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
          } catch (Error err) {
            handleError(err);
          }
        } else {
          Log.v(TAG, "No registered handler for message. Responding to Dart with empty reply message.");
          flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
        }
      }
    
    
       public void onMessage(ByteBuffer message, final BinaryReply reply) {
          final MethodCall call = codec.decodeMethodCall(message);
          try {
            handler.onMethodCall(
                call,
                new Result() {
                  @Override
                  public void success(Object result) {
                    reply.reply(codec.encodeSuccessEnvelope(result));
                  }
    
                  @Override
                  public void error(String errorCode, String errorMessage, Object errorDetails) {
                    reply.reply(codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails));
                  }
    
                  @Override
                  public void notImplemented() {
                    reply.reply(null);
                  }
                });
          } catch (RuntimeException e) {
            Log.e(TAG + name, "Failed to handle method call", e);
            reply.reply(
                codec.encodeErrorEnvelopeWithStacktrace(
                    "error", e.getMessage(), null, Log.getStackTraceString(e)));
          }
        }
      }
    }
    

    MethodChannel的执行流程涉及到主线程和UI线程的交互,代码从Dart到C++再到Java层,执行完相应逻辑后原路返回,从Java层到C++层再到Dart层。

    1.2.2 EventChannel (原生------>flutter)

    不存在: 原生向flutter调用, 然后flutter把结果返回给原生!
    EventChannel 可以由 Android 原生主动向 Flutter 发起交互请求
    相对于原生为主动式交互,类似于 Android 发送一个广播在 Flutter 端进行接收
    android端:

    new EventChannel(flutterView, CHANNEL).setStreamHandler(new EventChannel.StreamHandler() {
        @Override
        public void onListen(Object arguments, final EventChannel.EventSink events) {
            events.success("我来自 " + TAG +" !! 使用的是 EventChannel 方式");
        }
    
        @Override
        public void onCancel(Object arguments) {
        }
    });
    
    

    flutter部分:

    class _MyHomePageState extends State<MyHomePage> {
      static const eventChannel = const EventChannel('ace_demo_android_flutter');
      String _result = '';
      StreamSubscription _streamSubscription;
    
      @override
      void initState() {
        super.initState();
        _getEventResult();
      }
    
      @override
      void dispose() {
        super.dispose();
        if (_streamSubscription != null) {
          _streamSubscription.cancel();
        }
      }
    
      _getEventResult() async {
        try {
          _streamSubscription =
              eventChannel.receiveBroadcastStream().listen((data) {
            setState(() {
              _result = data;
            });
          });
        } on PlatformException catch (e) {
          setState(() {
            _result = "event get data err: '${e.message}'.";
          });
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(title: Text(widget.title)),
            body: Center(
                child: Text('${_result}',
                    style: TextStyle(color: Colors.blueAccent, fontSize: 18.0))),
            floatingActionButton: FloatingActionButton(
                onPressed: _incrementCounter, child: Icon(Icons.arrow_back)));
      }
    }
    
    
    1.2. 3 BasicMessageChannel

    BasicMessageChannel 主要传递字符串和半结构化的数据交互;其编解码有多种类型,在使用时建议 Android 与 Flutter 两端一致;

    1).BinaryCodec:基本二进制编码类型;
    2).StringCodec:字符串与二进制之间的编码类型;
    3).JSONMessageCodec:Json 与二进制之间的编码类型;
    4).StandardMessageCodec:默认编码类型,包括基础数据类型、二进制数据、列表、字典等与二进制之间等编码类型;
    Flutter -> Android
    用途: Flutter 端向 Android 端发送 send 数据请求,Android 端接收到后通过 replay 向 Flutter 端发送消息,从而完成一次消息交互;

    // Flutter 端
    static const basicChannel = BasicMessageChannel<String>('ace_demo_android_flutter', StringCodec());
    
    @override
    void initState() {
      super.initState();
      _getBasicResult();
    }
      
    _getBasicResult() async {
      final String reply = await basicChannel.send('ace_demo_user');
      setState(() {
        _result = reply;
      });
    }
    
    
    \// Android 端
    final BasicMessageChannel channel = new BasicMessageChannel<String> (flutterView, CHANNEL, StringCodec.INSTANCE);
    channel.setMessageHandler(new BasicMessageChannel.MessageHandler() {
        @Override
        public void onMessage(Object o, BasicMessageChannel.Reply reply) {
            reply.reply("我来自 " + TAG +" !! 使用的是 BasicMessageChannel 方式");
        }
    });
    

    Android -> Flutter
    根据上述继续由 Android 端主动向 Flutter 端发送数据,Android 通过 send 向 Flutter 发送数据请求,Flutter 通过 setMessageHandler 接收后向 Android 端 return 返回结果,再由 Android 回调接收,从而完成一次数据交互;

    public void send(T message) {
        this.send(message, (BasicMessageChannel.Reply)null);
    }
    
    public void send(T message, BasicMessageChannel.Reply<T> callback) {
        this.messenger.send(this.name, this.codec.encodeMessage(message), callback == null ? null : new BasicMessageChannel.IncomingReplyHandler(callback));
    }
    
    

    分析源码 send 有两个构造函数,有两个参数的构造方法用来接收 Flutter 回调的数据;

    // Flutter 端
    static const basicChannel = BasicMessageChannel<String>('ace_demo_android_flutter', StringCodec());
    
    @override
    void initState() {
      super.initState();
      _getBasicResult();
    }
    
    _getBasicResult() async {
      final String reply = await
      channel.setMessageHandler((String message) async {
        print('Flutter Received: ${message}');
        setState(() {
          _result = message;
        });
        return "{'name': '我不是老猪', 'gender': 1}";
      });
    }
    
    // Android 端
    channel.setMessageHandler(new BasicMessageChannel.MessageHandler() {
        @Override
        public void onMessage(Object o, BasicMessageChannel.Reply reply) {
            reply.reply("我来自 " + TAG +" !! 使用的是 BasicMessageChannel 方式");
            channel.send("ace_demo_user");
            //channel.send("ace_demo_user", new BasicMessageChannel.Reply() {
            //    @Override
            //    public void reply(Object o) {
            //        Intent intent = new Intent();
            //        intent.putExtra("data", o!=null?o.toString():"");
            //        setResult(REQUEST_CODE, intent);
            //        MyFlutterViewActivity.this.finish();
            //    }
            //});
        }
    });
    
    
    3者的通信原理总结:

    消息信使:BinaryMessenger

    以ByteBuffer为数据载体,然后通过BinaryMessenger来发送与接收数据。整体设计如下。
    在Android侧,BinaryMessenger是一个接口,在FlutterView中实现了该接口,在BinaryMessenger的方法中通过JNI来与系统底层沟通。在Flutter侧,BinaryMessenger是一个类,该类的作用就是与类window沟通,而类window才真正与系统底层沟通。

    methodchannel原理.jpg

    虽然三种Channel各有用途,但是他们与Flutter通信的工具却是相同的,均为BinaryMessager。

    BinaryMessenger是Platform端与Flutter端通信的工具,其通信使用的消息格式为二进制格式数据。当我们初始化一个Channel,并向该Channel注册处理消息的Handler时,实际上会生成一个与之对应的BinaryMessageHandler,并以channel name为key,注册到BinaryMessenger中。当Flutter端发送消息到BinaryMessenger时,BinaryMessenger会根据其入参channel找到对应的BinaryMessageHandler,并交由其处理。

    Binarymessenger在Android端是一个接口,其具体实现为FlutterNativeView。而其在iOS端是一个协议,名称为FlutterBinaryMessenger,FlutterViewController遵循了它。

    Binarymessenger并不知道Channel的存在,它只和BinaryMessageHandler打交道。而Channel和BinaryMessageHandler则是一一对应的。由于Channel从BinaryMessageHandler接收到的消息是二进制格式数据,无法直接使用,故Channel会将该二进制消息通过Codec(消息编解码器)解码为能识别的消息并传递给Handler进行处理。

    当Handler处理完消息之后,会通过回调函数返回result,并将result通过编解码器编码为二进制格式数据,通过BinaryMessenger发送回Flutter端。

    1.3. Platform Channel的代码运行在什么线程

    在文章《深入理解Flutter引擎线程模型》中提及,Flutter Engine自己不创建线程,其线程的创建于管理是由enbedder提供的,并且Flutter Engine要求Embedder提供四个Task Runner,分别是Platform Task Runner,UI Task Runner,GPU Task Runner和IO Task Runner。

    实际上,在Platform侧执行的代码运行在Platform Task Runner中,而在Flutter app侧的代码则运行在UI Task Runner中。在Android和iOS平台上,Platform Task Runner跑在主线程上。因此,不应该在Platform端的Handler中处理耗时操作。

    1.4. Platform Channel是否线程安全

    Platform Channel并非是线程安全的,这一点在官方的文档也有提及。Flutter Engine中多个组件是非线程安全的,故跟Flutter Engine的所有交互(接口调用)必须发生在Platform Thread。故我们在将Platform端的消息处理结果回传到Flutter端时,需要确保回调函数是在Platform Thread(也就是Android和iOS的主线程)中执行的。

    1.5. 是否支持大内存数据块的传递

    Platform Channel实际上是支持大内存数据块的传递,当需要传递大内存数据块时,需要使用BasicMessageChannel以及BinaryCodec。而整个数据传递的过程中,唯一可能出现数据拷贝的位置为native二进制数据转化为Dart语言二进制数据。若二进制数据大于阈值时(目前阈值为1000byte)则不会拷贝数据,直接转化,否则拷贝一份再转化。

    1.6. 如何将Platform Channel原理应用到开发工作中

    实际上Platform Channel的应用场景非常多,我们这里举一个例子:

    在平常的业务开发中,我们需要使用到一些本地图片资源,但是Flutter端是无法使用Platform端已存在的图片资源的。当Flutter端需要使用一个Platform端已有的图片资源时,只有将该图片资源拷贝一份到Flutter的Assert目录下才能使用。实际上,让Flutter端使用Platform端的资源并不是一件难事。

    我们可以使用BasicMessageChannel来完成这个工作。Flutter端将图片资源名name传递给Platform端,Native端使用Platform端接收到name后,根据name定位到图片资源,并将该图片资源以二进制数据格式,通过BasicMessageChannel,传递回Flutter端。

    2. 混合交互的缺点:

    1). android 还是得写代码,ios端还是得写代码, 要维护!
    2). 运行多个Flutter实例,或在屏幕局部上运行Flutter可能会导致不可以预测的行为;
    3). 在后台模式使用Flutter的能力还在开发中(目前不支持);
    4).将Flutter库打包到另一个可共享的库或将多个Flutter库打包到同一个应用中,都不支持;
    5).添加到应用在Android平台的实现基于 FlutterPlugin 的 API,一些不支持 FlutterPlugin 的插件可能会有不可预知的行为。
    混合开发的2种情况

    1. .flutter项目,里面用android或者ios
      2).android项目中,加入flutter进行混合

    3. 插件Pigeon

    Pigeon工具生成的代码是基于BasicMessageChannel实现通信的。

    • 生成的代码是 Java/Objective-C,但是由于 Kotlin 可以调用 Java,Swift 可以调用 Objective-C

    4.fluttter 嵌套原生的view视图

    Flutter提供了一个平台视图(Platform View)的概念。它提供了一种方法,允许开发者在Flutter里面嵌入原生系统(Android和iOS)的视图,并加入到Flutter的渲染树中,实现与Flutter一致的交互体验。

    嵌套原生的view:
    由于Flutter与原生渲染方式完全不同,因此转换不同的渲染数据会有较大的性能开销。如果在一个界面上同时实例化多个原生控件,就会对性能造成非常大的影响,所以我们要避免在使用Flutter控件也能实现的情况下去使用内嵌平台视图。

    一方面, 需要分别在Android和iOS端写大量的适配桥接代码,违背了跨平台技术的本意,也增加了后续的维护成本;
    另一方面, 毕竟除去地图、WebView、相机等涉及底层方案的特殊情况外,大部分原生代码能够实现的UI效果,完全可以用Flutter实现。

    平台视图PlatformView
    PlatformView跟add to app怎么选
    FlutterView的创建:

    public class MyFlutterViewActivity extends FlutterFragmentActivity {
    
        private static final String CHANNEL = "ace_demo_android_flutter";
        private static final String TAG = "MyFlutterViewActivity";
        private static final int REQUEST_CODE = 1000;
        FlutterView flutterView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_flutter);
    
            DisplayMetrics outMetrics = new DisplayMetrics();
            getWindowManager().getDefaultDisplay().getMetrics(outMetrics);
            int widthPixels = outMetrics.widthPixels;
            int heightPixels = outMetrics.heightPixels;
    
            flutterView = Flutter.createView(MyFlutterViewActivity.this, getLifecycle(), "/");
            FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(widthPixels, heightPixels);
            addContentView(flutterView, layout);
    

    在Flutter中嵌套原生视图: 通过平台视图,我们就可以将一个原生控件包装成Flutter控件,嵌入到Flutter页面中,
    就像使用一个普通的Widget一样。把原生视图组装成一个 Flutter 控件
    一次典型的平台视图使用过程与方法通道类似:
    首先,由作为客户端的Flutter,通过向原生视图的Flutter封装类(在iOS和Android平台分别是UIKitView和AndroidView)传入视图标识符,用于发起原生视图的创建请求;
    然后,原生代码侧将对应原生视图的创建交给平台视图工厂(PlatformViewFactory)实现;
    最后,在原生代码侧将视图标识符与平台视图工厂进行关联注册,让Flutter发起的视图创建请求可以直接找到对应的视图创建工厂。
    架构原理图:

    channel.jpg

    实现步骤:
    1. android 端,把view封装一个.PlatformView !
    2. android端创建一个工厂
    3. android通过高方法通道, 绑定注册view
    flutter端: 直接返回一个绑定方法通道标识的view,AndroidView!
    案例demo2:
    问题: flutter如何实现原生视图的接口调用?
    flutter端:

    class SampleView extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        // 使用 Android 平台的 AndroidView,传入唯一标识符 sampleView
        if (defaultTargetPlatform == TargetPlatform.android) {
          return AndroidView(viewType: 'sampleView');
        } else {
          // 使用 iOS 平台的 UIKitView,传入唯一标识符 sampleView
          return UiKitView(viewType: 'sampleView');
        }
      }
    }
    
    Scaffold(
            backgroundColor: Colors.yellowAccent,
            body:  Container(width: 200, height:200,
                child: SampleView(controller: controller)
            ));
    

    如何在原生系统实现接口?
    android端:

    // 视图工厂类
    class SampleViewFactory extends PlatformViewFactory {
        private final BinaryMessenger messenger;
        // 初始化方法
        public SampleViewFactory(BinaryMessenger msger) {
            super(StandardMessageCodec.INSTANCE);
            messenger = msger;
        }
        // 创建原生视图封装类,完成关联
        @Override
        public PlatformView create(Context context, int id, Object obj) {
            return new SimpleViewControl(context, id, messenger);
        }
    }
    // 原生视图封装类
    class SimpleViewControl implements PlatformView {
        private final View view;// 缓存原生视图
        // 初始化方法,提前创建好视图
        public SimpleViewControl(Context context, int id, BinaryMessenger messenger) {
            view = new View(context);
            view.setBackgroundColor(Color.rgb(255, 0, 0));
        }
        
        // 返回原生视图
        @Override
        public View getView() {
            return view;
        }
        // 原生视图销毁回调
        @Override
        public void dispose() {
        }
    }
    
    protected void onCreate(Bundle savedInstanceState) {
      ...
      Registrar registrar =    registrarFor("samples.chenhang/native_views");// 生成注册类
      SampleViewFactory playerViewFactory = new SampleViewFactory(registrar.messenger());// 生成视图工厂
     
    registrar.platformViewRegistry().registerViewFactory("sampleView", playerViewFactory);// 注册视图工厂
    }
    

    5.如何在程序运行时,动态地调整原生视图的样式?

    flutter端我们会用到原生视图的一个初始化属性,即 onPlatformViewCreated
    原生端: 会用到MethodChannel, 方法通道
    实现原理:
    flutter:
    1.创建一个controller

    1. 初始化的时候在controller中创建方法通道
      3.在controller中, invoke通道方法(声明方法)
      android端实现: 方法通道, 动态调用flutter的方法
      案例demo3:
      flutter端的代码
    // 原生视图控制器
    class NativeViewController {
      MethodChannel _channel;
      // 原生视图完成创建后,通过 id 生成唯一方法通道
      onCreate(int id) {
        _channel = MethodChannel('samples.chenhang/native_views_$id');
      }
      // 调用原生视图方法,改变背景颜色
      Future<void> changeBackgroundColor() async {
        return _channel.invokeMethod('changeBackgroundColor');
      }
    }
     
    // 原生视图 Flutter 侧封装,继承自 StatefulWidget
    class SampleView extends StatefulWidget {
      const SampleView({
        Key key,
        this.controller,
      }) : super(key: key);
     
      // 持有视图控制器
      final NativeViewController controller;
      @override
      State<StatefulWidget> createState() => _SampleViewState();
    }
     
    class _SampleViewState extends State<SampleView> {
      // 根据平台确定返回何种平台视图
      @override
      Widget build(BuildContext context) {
        if (defaultTargetPlatform == TargetPlatform.android) {
          return AndroidView(
            viewType: 'sampleView',
            // 原生视图创建完成后,通过 onPlatformViewCreated 产生回调
            onPlatformViewCreated: _onPlatformViewCreated,
          );
        } else {
          return UiKitView(viewType: 'sampleView',
            // 原生视图创建完成后,通过 onPlatformViewCreated 产生回调
            onPlatformViewCreated: _onPlatformViewCreated
          );
        }
      }
      // 原生视图创建完成后,调用 control 的 onCreate 方法,传入 view id
      _onPlatformViewCreated(int id) {
        if (widget.controller == null) {
          return;
        }
        widget.controller.onCreate(id);
      }
    }
    

    Android 端的代码

    class SimpleViewControl implements PlatformView, MethodCallHandler {
        private final MethodChannel methodChannel;
        ...
        public SimpleViewControl(Context context, int id, BinaryMessenger messenger) {
            ...
            // 用 view id 注册方法通道
            methodChannel = new MethodChannel(messenger, "samples.chenhang/native_views_" + id);
            // 设置方法通道回调
            methodChannel.setMethodCallHandler(this);
        }
        // 处理方法调用消息
        @Override
        public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
            // 如果方法名完全匹配
            if (methodCall.method.equals("changeBackgroundColor")) {
                // 修改视图背景,返回成功
                view.setBackgroundColor(Color.rgb(0, 0, 255));
                result.success(0);
            }else {
                // 调用方发起了一个不支持的 API 调用
                result.notImplemented();
            }
        }
      ...
    }
    

    相关文章

      网友评论

          本文标题:6.Flutte3.0 遥遥领先系列|一文教你完全掌握原生交互(

          本文链接:https://www.haomeiwen.com/subject/azxaodtx.html