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

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

形象的举例:

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

架构图:

  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

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";

  
  public void configureFlutterEngine( 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开始调用:
调用栈图:

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;

  
  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

  
  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

  
  public void handlePlatformMessage(
       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(
       String channel,  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(
       String channel,
       HandlerInfo handlerInfo,
       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(
       HandlerInfo handlerInfo,  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() {
              
              public void success(Object result) {
                reply.reply(codec.encodeSuccessEnvelope(result));
              }

              
              public void error(String errorCode, String errorMessage, Object errorDetails) {
                reply.reply(codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails));
              }

              
              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() {
    
    public void onListen(Object arguments, final EventChannel.EventSink events) {
        events.success("我来自 " + TAG +" !! 使用的是 EventChannel 方式");
    }

    
    public void onCancel(Object arguments) {
    }
});


flutter部分:

class _MyHomePageState extends State<MyHomePage> {
  static const eventChannel = const EventChannel('ace_demo_android_flutter');
  String _result = '';
  StreamSubscription _streamSubscription;

  
  void initState() {
    super.initState();
    _getEventResult();
  }

  
  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}'.";
      });
    }
  }

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


void initState() {
  super.initState();
  _getBasicResult();
}
  
_getBasicResult() async {
  final String reply = await basicChannel.send('ace_demo_user');
  setState(() {
    _result = reply;
  });
}


\// Androidfinal BasicMessageChannel channel = new BasicMessageChannel<String> (flutterView, CHANNEL, StringCodec.INSTANCE);
channel.setMessageHandler(new BasicMessageChannel.MessageHandler() {
    
    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());


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() {
    
    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才真正与系统底层沟通。

虽然三种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 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;

    
    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发起的视图创建请求可以直接找到对应的视图创建工厂。
架构原理图:

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

class SampleView extends StatelessWidget {
  
  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;
    }
    // 创建原生视图封装类,完成关联
    
    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));
    }
    
    // 返回原生视图
    
    public View getView() {
        return view;
    }
    // 原生视图销毁回调
    
    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;
  
  State<StatefulWidget> createState() => _SampleViewState();
}
 
class _SampleViewState extends State<SampleView> {
  // 根据平台确定返回何种平台视图
  
  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);
    }
    // 处理方法调用消息
    
    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();
        }
    }
  ...
}

最后

为了能够方便大家快速学习Flutter, 这里整理了Flutter学习路线图以及《Flutter Dart 语言编程入门到精通》&《Flutter实战:第二版》帮助大家配置相关环境,学习Flutter 的基本语法以及最后的项目实际利用。

学习路线:
在这里插入图片描述

Dart语言是Flutter的开发语言,所以我们需要掌握Dart语言的基础知识, 由于内容过多,截图展示的目录及部分内容,完整文档领取方式扫描下方二维码即可免费获取!

《Flutter Dart 语言编程入门到精通》

第一章 Dart语言基础

  • 环境准备
  • 基础语法

第二章 Dart 异步编程

  • Dart的事件循环
  • 调度任务
  • 延时任务
  • Future详解
  • async和await
  • lsolate

img

第三章 异步之 Stream 详解

  • 什么是Stream
  • 单订阅流
  • 广播流
  • Stream Transformer
  • 总结

第四章 Dart标准输入输出流

  • 文件操作

img

第五章 Dart 网络编程

  • TCP服务端
  • TCP客户端
  • UDP服务端
  • UDP客户端
  • HTTP服务器与请求
  • WebSocket

第六章 Flutter 爬虫与服务端

  • Dart爬虫开发
  • Dart服务端
  • 创建Flutter项目演示
  • 总结

第七章 Dart 的服务端开发

  • 注册登录实现

第八章 Dart 调用C语言混合编程

  • 环境准备
  • 测试Dart ffi接口
  • 总结

第九章 LuaDardo中Dart与Lua的相互调用

  • Lua C API
  • 创建运行时
  • Dart调Lua
  • Lua调Dart

img

掌握了Dart语言之后,咱们就可以通过实战来掌握Flutter的知识点, 由于内容过多,截图展示的目录及部分内容,完整文档领取方式扫描下方二维码即可免费获取!

《Flutter实战:第二版》

第一章:起步

  • 1.1 移动开发技术简介
  • 1.2 初始Flutter
  • 1.3 搭建Flutter开发环境
  • 1.4 Dart语言简介

第二章:第一个Flutter应用

  • 2.1 计数器应用实例
  • 2.2 Widget简介
  • 2.3 状态管理
  • 2.4路由管理
  • 2.5包管理
  • 2.6 资源管理
  • 2.7 调试Flutter应用
  • 2.8 Flutter异常捕获

在这里插入图片描述

第三章:基础组件

  • 3.1 文本及样式
  • 3.2 按钮
  • 3.3 图片及ICON
  • 3.4 单选开关和复选框
  • 3.5 输入框及表单
  • 3.6 进度指示器

第四章:布局类组件

  • 4.1 布局类组件简介
  • 4.2 布局原理与约束(constraints)
  • 4.3 线性布局(Row和Column)
  • 4.4 弹性布局(Flex)

在这里插入图片描述

第五章:容器类组件

  • 5.1 填充(Padding)
  • 5.2 装饰容器(DecoratedBox)
  • 5.3 变换(Transform)
  • 5.4 容器组件(Container)
  • 5.5 剪裁(Clip)
  • 5.6 空间适配(FittedBox)
  • 5.7 页面骨架(Scaffold)

第六章:可滚动组件

  • 6.1 可滚动组件简介
  • 6.2 SingleChildScrollView
  • 6.3 ListView
  • 6.4 滚动监听及控制

在这里插入图片描述

第七章:功能型组件

  • 7.1 导航返回拦截(WillPopScope)
  • 7.2 数据共享(InheritedWidget)
  • 7.3 跨组件状态共享
  • 7.4 颜色和主题
  • 7.5 按需rebuild(ValueListenableBuilder)
  • 7.6 异步UI更新(FutureBuilder、StreamBuilder)
  • 7.7 对话框详解

第八章:事件处理与通知

  • 8.1 原始指针事件处理
  • 8.2 手势识别
  • 8.3 Flutter事件机制
  • 8.4 手势原理与手势冲突
  • 8.5 事件总线
  • 8.6 通知 Notification

在这里插入图片描述

第九章:动画

  • 9.1 Flutter动画简介
  • 9.2 动画基本结构及状态监听
  • 9.3 自定义路由切换动画
  • 9.4 Hero动画
  • 9.5 交织动画
  • 9.6 动画切换组件(AnimatedSwitcher)
  • 9.7 动画过渡组件

第十章:自定义组件

  • 10.1 自定义组件方法简介
  • 10.2 组合现有组件
  • 10.3 组合实例:TurnBox
  • 10.4 CustomPaint 与 Canvas
  • 10.5 自绘实例:圆形背景渐变进度条
  • 10.6 自绘组件:CustomCheckbox
  • 10.7 自绘组件: DoneWidget
  • 10.8 水印实例: 文本绘制与离屏渲染

img

第十一章:文件操作与网络请求

  • 11.1 文件操作
  • 11.2 通过HttpClient发起HTTP请求
  • 11.3 Http请求库-dio
  • 11.4 实例:Http分块下载

第十二章:Flutter扩展

  • 12.1 包和插件
  • 12.2 Flutter Web

第十三章:国际化

  • 13.1 让App支持多语言
  • 13.2 实现Localizations
  • 13.3 使用Intl包
  • 13.4 国际化常见问题

在这里插入图片描述

第十四章:Flutter核心原理

  • 14.1 Flutter UI 框架(Framework)
  • 14.2 Element、BuildContext和RenderObject
  • 14.3 Flutter启动流程和渲染管线
  • 14.4 布局(Layout)过程
  • 14.5 绘制(一)绘制原理及Layer

第十五章:一个完整的Flutter应用

  • 15.1 Github客户端示例
  • 15.2 Flutter APP代码结构
  • 15.3 Model类定义
  • 15.4 全局变量及共享状态
  • 15.5 网络请求封装
  • 15.6 APP入口及主页
  • 15.7 登录页
  • 15.8 多语言和多主题

img

由于内容过多,截图展示的目录及部分内容,完整文档领取方式扫描下方二维码即可免费获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值