美文网首页
flutter platform channel Metho

flutter platform channel Metho

作者: wayDevelop | 来源:发表于2020-09-18 11:37 被阅读0次

Flutter是Google使用Dart语言开发的一套移动应用开发框架。它不同于其他开发框架:

  • 因为Flutter使用AOT预编译代码为机器码,所以它的运行效率更高。

  • Flutter的UI控件并没有使用底层的原生控件,而是使用Skia渲染引擎绘制而成,因为不依赖底层控件,所以多端一致性非常好。

  • Flutter的扩展性也非常强,开发者可以通过Plugin与Native进行通信。

参考深入理解Flutter Platform Channel

Platform Channel 主要用于Flutter和原生端的数据传递。

Flutter定义了三种不同类型的Channel,分别是

  • BasicMessageChannel:用于传递字符串和半结构化的数据;
  • MethodChannel:用于传递方法调用;
  • EventChannel:用于数据流的通信;

消息使用平台通道在客户端(UI)和宿主(平台)之间传递,如下图所示:


根据架构图,我们可以看出在Flutter端,MethodChannel 允许发送与方法调用相对应的消息。Android上的MethodChannel 启用接收方法调用并发回结果给Flutter端。而这种数据传递方式还可以反向调用。为了保证用户界面保持相应而不卡顿,消息和响应以异步的形式进行传递。
以确保用户界面能够保持响应。

Flutter 是通过 Dart 异步发送消息的。即便如此,当你调用一个平台方法时,也需要在主线程上做调用。

具体使用方法请参考Demo实现了一个从Flutter端发起的方法调用,从原生端获取数据并返回给Flutter端用于展示。以下是基本使用方法。

//flutter 代码
class MethodChannelManager {
  MethodChannel _methodChannel;

  static MethodChannelManager _instance;
  String _methodChannelName = "flutter_method_channel";

  MethodChannelManager._internal() {
    if (_methodChannel == null) {
      _methodChannel = new MethodChannel(_methodChannelName);
    }
  }

  static MethodChannelManager getInstance() {
    if (_instance == null) {
      _instance = new MethodChannelManager._internal();
    }
    return _instance;
  }

  Future<String> sendMessage() async {
    Map<String, String> map = {"flutter": "这是一条来自flutter的参数"};
    String message = await _methodChannel.invokeMethod("success", map);
    return message;
  }

  Future<double> getRefreshRate() async {
    return MethodChannel('fps_plugin').invokeMethod("getRefreshRate");
  }
}


//android 原生端代码

public class NativeBasePlugin implements MethodCallHandler, StreamHandler, FlutterPlugin , ActivityAware {

    private Context applicationContext;

    private MethodChannel methodChannel;
    private EventChannel eventChannel;
    public static String methodChannelName = "flutter_method_channel";
    public static String eventChannelName = "flutter_event_channel";


    public int index = 0;

    /**
     * Plugin registration.
     */
    public static void registerWith(FlutterEngine flutterEngine) {
        final NativeBasePlugin instance = new NativeBasePlugin();
        instance.onAttachedToEngine(new ShimPluginRegistry(flutterEngine).registrarFor(methodChannelName).context(),
                flutterEngine.getDartExecutor());
    }

    @Override
    public void onAttachedToEngine(FlutterPluginBinding binding) {
        onAttachedToEngine(binding.getApplicationContext(), binding.getBinaryMessenger());

    }

    private void onAttachedToEngine(Context applicationContext, BinaryMessenger messenger) {
        this.applicationContext = applicationContext;

        methodChannel = new MethodChannel(messenger, methodChannelName);
        methodChannel.setMethodCallHandler(this);

        eventChannel = new EventChannel(messenger, eventChannelName);
        eventChannel.setStreamHandler(this);

    }

    @Override
    public void onMethodCall(MethodCall methodCall, Result result) {
        switch (methodCall.method) {
            case "success":
                //解析参数
                String value = methodCall.argument("flutter");
                // Toast.makeText(applicationContext, "" + value, Toast.LENGTH_SHORT).show();
                result.success("A");
                break;
            default:
                result.notImplemented();
        }
    }

    @Override
    public void onDetachedFromEngine(FlutterPluginBinding binding) {
        applicationContext = null;
        methodChannel.setMethodCallHandler(null);
        methodChannel = null;
        eventChannel.setStreamHandler(null);
        eventChannel = null;
    }

    @Override
    public void onListen(Object arguments, EventSink events) {

        /**
         * CountDownTimer timer = new CountDownTimer(3000, 1000)中,
         * 第一个参数表示总时间,第二个参数表示间隔时间。
         * 意思就是每隔一秒会回调一次方法onTick,然后1秒之后会回调onFinish方法。
         */
        CountDownTimer timer = new CountDownTimer(20000, 1000) {
            public void onTick(long millisUntilFinished) {
                events.success("index=" + millisUntilFinished / 1000 + "秒");

            }

            public void onFinish() {
                events.success("时间结束了");
            }
        };
        timer.start();
    }

    @Override
    public void onCancel(Object arguments) {
        //EventChannel 取消
        index = 0;

    }
}

Dart层方法调用的消息传递分析

首先,dart中会先创建一个MethodChannel对象,其名称为flutter_method_channel”,这个名字很关键,必须与原生端的名字相对应,具体原因后边会有解释。通过异步方式调用invokeMethod方法传入方法名来获取信息 await _methodChannel.invokeMethod('success');

invokeMethod方法具体实现如下

  @optionalTypeArgs
  Future<T> invokeMethod<T>(String method, [ dynamic arguments ]) async {
    assert(method != null);
    final ByteData result = await binaryMessenger.send(
      name,
      codec.encodeMethodCall(MethodCall(method, arguments)),
    );
    if (result == null) {
      throw MissingPluginException('No implementation found for method $method on channel $name');
    }
    final T typedResult = codec.decodeEnvelope(result);
    return typedResult;
  }

通过BinaryMessages.send()方法来发送方法调用消息,我们可以看到send方法有两个参数,第一个是channel的名称,第二个是ByteData对象(使用codec对根据方法名和参数构建的MethodCall对象进行编码得到的对象);codec对象是在MethodChannel对象创建时默认创建的StandardMethodCodec对象,其对MethodCall对象的编码过程如下



  const MethodChannel(this.name, [this.codec = const StandardMethodCodec(), BinaryMessenger binaryMessenger ])
      : assert(name != null),
        assert(codec != null),
        _binaryMessenger = binaryMessenger;

  @override
  ByteData encodeMethodCall(MethodCall call) {
    final WriteBuffer buffer = WriteBuffer();
    messageCodec.writeValue(buffer, call.method);
    messageCodec.writeValue(buffer, call.arguments);
    return buffer.done();
  }

通过messageCodec将调用的方法名和传递的参数写入到buffer中,messageCodec是一个StandardMessageCodec对象,在StandardMethodCodec对象创建时默认创建,其writeValue方法的实现如下


void writeValue(WriteBuffer buffer, dynamic value) {
    if (value == null) {
      buffer.putUint8(_valueNull);
    } else if (value is bool) {
      buffer.putUint8(value ? _valueTrue : _valueFalse);
    } else if (value is double) {  // Double precedes int because in JS everything is a double.
                                   // Therefore in JS, both `is int` and `is double` always
                                   // return `true`. If we check int first, we'll end up treating
                                   // all numbers as ints and attempt the int32/int64 conversion,
                                   // which is wrong. This precedence rule is irrelevant when
                                   // decoding because we use tags to detect the type of value.
      buffer.putUint8(_valueFloat64);
      buffer.putFloat64(value);
    } else if (value is int) {
      if (-0x7fffffff - 1 <= value && value <= 0x7fffffff) {
        buffer.putUint8(_valueInt32);
        buffer.putInt32(value);
      } else {
        buffer.putUint8(_valueInt64);
        buffer.putInt64(value);
      }
    } else if (value is String) {
      buffer.putUint8(_valueString);
      final List<int> bytes = utf8.encoder.convert(value);
      writeSize(buffer, bytes.length);
      buffer.putUint8List(bytes);
    } else if (value is Uint8List) {
      buffer.putUint8(_valueUint8List);
      writeSize(buffer, value.length);
      buffer.putUint8List(value);
    } else if (value is Int32List) {
      buffer.putUint8(_valueInt32List);
      writeSize(buffer, value.length);
      buffer.putInt32List(value);
    } else if (value is Int64List) {
      buffer.putUint8(_valueInt64List);
      writeSize(buffer, value.length);
      buffer.putInt64List(value);
    } else if (value is Float64List) {
      buffer.putUint8(_valueFloat64List);
      writeSize(buffer, value.length);
      buffer.putFloat64List(value);
    } else if (value is List) {
      buffer.putUint8(_valueList);
      writeSize(buffer, value.length);
      for (final dynamic item in value) {
        writeValue(buffer, item);
      }
    } else if (value is Map) {
      buffer.putUint8(_valueMap);
      writeSize(buffer, value.length);
      value.forEach((dynamic key, dynamic value) {
        writeValue(buffer, key);
        writeValue(buffer, value);
      });
    } else {
      throw ArgumentError.value(value);
    }
  }

上述代码看出,Flutter与平台端的消息传递支持12种类型,这12种类型分别与安卓和iOS中的类型相对应,看下面表格更直观


writeValue方法其实就是将方法名和参数转化为对应的二进制数据写入buffer中,方法名都是String类型,我们就以String类型方法写入过程来进行简单说明,如果判断一个value为String后,

1、调用buffer.putUint8(_valueString);先写入对应的类型值,_valueString = 7;,所以将00000111二进制数据写入buffer;

2、紧接着将value通过utf8编码为int数组,然后将数组的长度数据通过writeSize(buffer, bytes.length);写入buffer;

3、最后再将数组数据写入buffer,至此一个方法名编码完成;

其他类型的数据依次类推进行编码,编码完成后,将StandardMessageCodec对象编码的ByteData数据通过BinaryMessages.send()方法发送出去,看下send方法的具体实现

  @override
  Future<ByteData> send(String channel, ByteData message) {
    final MessageHandler handler = _mockHandlers[channel];
    if (handler != null)
      return handler(message);
    return _sendPlatformMessage(channel, message);
  }

会从_mockHandlers中查找是否缓存的有_MessageHandler对象,如果没有则通过_sendPlatformMessage方法发送消息,

消息处理器:Handler

当我们接收二进制格式消息并使用Codec将其解码为Handler能处理的消息后,就该Handler上场了。Flutter定义了三种类型的Handler,与Channel类型一一对应。我们向Channel注册一个Handler时,实际上就是向BinaryMessager注册一个与之对应的BinaryMessageHandler。当消息派分到BinaryMessageHandler后,Channel会通过Codec将消息解码,并传递给Handler处理。

  • MessageHandler

MessageHandler用户处理字符串或者半结构化的消息,其onMessage方法接收一个T类型的消息,并异步返回一个相同类型result。MessageHandler的功能比较基础,使用场景较少,但是其配合BinaryCodec使用时,能够方便传递二进制数据消息。

  • MethodHandler

MethodHandler用于处理方法的调用,其onMessage方法接收一个MethodCall类型消息,并根据MethodCall的成员变量method去调用对应的API,当处理完成后,根据方法调用成功或失败,返回对应的结果。

  • StreamHandler

StreamHandler与前两者稍显不同,用于事件流的通信,最为常见的用途就是Platform端向Flutter端发送事件消息。当我们实现一个StreamHandler时,需要实现其onListen和onCancel方法。而在onListen方法的入参中,有一个EventSink(其在Android是一个对象)。我们持有EventSink后,即可通过EventSink向Flutter端发送事件消息。

下面再来看下_sendPlatformMessage方法,其最终调用的是ui.window.sendPlatformMessage方法,该方法中会传递回调方法对象,在数据返回后会被回调从而得到结果数据。

  void sendPlatformMessage(String name,
                           ByteData data,
                           PlatformMessageResponseCallback callback) {
    final String error =
        _sendPlatformMessage(name, _zonedPlatformMessageResponseCallback(callback), data);
    if (error != null)
      throw Exception(error);
  }
  String _sendPlatformMessage(String name,
                              PlatformMessageResponseCallback callback,
                              ByteData data) native 'Window_sendPlatformMessage';

在以上代码中ui.window.sendPlatformMessage()方法最终会调用Dart本地接口方法_sendPlatformMessage,这里可以将这个方法简单理解为类似于java的JNI的方法,在c++层会调用"Window_sendPlatformMessage"对应的方法。至此,dart中的方法消息传递已经结束,

c++层是如何对方法调用消息进行传递的。这里不做具体分析,有兴趣可以从Flutter engine源码中分析

接下来我们开始分析java层接受到消息后的处理逻辑。

c++层通过调用flutterJNI的handlePlatformMessage方法将channel名称、消息内容和响应ID传给java层,我们来看一下FlutterJNI中的方法实现

    private void handlePlatformMessage(@NonNull String channel, byte[] message, int replyId) {
        if (this.platformMessageHandler != null) {
            this.platformMessageHandler.handleMessageFromDart(channel, message, replyId);
        }

    }

此时会调用platformMessageHandler的handleMessageFromDart()方法,handleMessageFromDart 是PlatformMessageHandler 接口里面的一个方法 在DartMessenger 里面实现

   public void handleMessageFromDart(@NonNull String channel, @Nullable byte[] message, int replyId) {
        Log.v("DartMessenger", "Received message from Dart over channel '" + channel + "'");
        //首先根据channel名称从mMessageHandlers中查找对应的BinaryMessageHandler对象
        BinaryMessageHandler handler = (BinaryMessageHandler)this.messageHandlers.get(channel);
        if (handler != null) {
            try {
                Log.v("DartMessenger", "Deferring to registered handler to process message.");
                ByteBuffer buffer = message == null ? null : ByteBuffer.wrap(message);
              //如果找到则执行该对象的onMessage()方法,
                handler.onMessage(buffer, new DartMessenger.Reply(this.flutterJNI, replyId));
            } catch (Exception var6) {
                Log.e("DartMessenger", "Uncaught exception in binary message listener", var6);
                this.flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
            }
        } else {
            Log.v("DartMessenger", "No registered handler for message. Responding to Dart with empty reply message.");
            this.flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
        }

    }


我们主要看 mMessageHandlers中是怎么保存我们的channel名称为flutter_method_channel 的对象的呢,我们看下开始所说的demo中的java模块相关代码,

    public static String methodChannelName = "flutter_method_channel";
    methodChannel.setMethodCallHandler(this);


   @Override
    public void onMethodCall(MethodCall methodCall, Result result) {
        switch (methodCall.method) {
            case "success":
                //解析参数
                String value = methodCall.argument("flutter");
                // Toast.makeText(applicationContext, "" + value, Toast.LENGTH_SHORT).show();
                result.success("A");
                break;
            default:
                result.notImplemented();
        }
    }


创建一个名为flutter_method_channel 的MethodChannel对象,然后设置对应的MethodCallHandler对象,setMethodCallHandler的方法实现如下

   @UiThread
    public void setMethodCallHandler(@Nullable MethodChannel.MethodCallHandler handler) {
        this.messenger.setMessageHandler(this.name, handler == null ? null : new MethodChannel.IncomingMethodCallHandler(handler));
    }

其中的messenger就是实现了BinaryMessenger接口的对象 再来看 this.messenger.setMessageHandler ,setMessageHandler 是一个接口,主要看他在DartMessenger 的实现类

    public void setMessageHandler(@NonNull String channel, @Nullable BinaryMessageHandler handler) {
        if (handler == null) {
            Log.v("DartMessenger", "Removing handler for channel '" + channel + "'");
            this.messageHandlers.remove(channel);
        } else {
            Log.v("DartMessenger", "Setting handler for channel '" + channel + "'");
            this.messageHandlers.put(channel, handler);
        }

    }

到此,我们发现在注册插件方法中实现的 MethodCallHandler通过一系列操作被包装到MethodChannel.IncomingMethodCallHandler对象中并设置进了mMessageHandlers中。那么我们上面所说的onMessage方法的调用即是MethodChannel.IncomingMethodCallHandler对象的方法,

    private final class IncomingMethodCallHandler implements BinaryMessageHandler {
        private final MethodChannel.MethodCallHandler handler;

        IncomingMethodCallHandler(MethodChannel.MethodCallHandler handler) {
            this.handler = handler;
        }

        @UiThread
        public void onMessage(ByteBuffer message, final BinaryReply reply) {
          //首先将从c++层传递过来的消息通过codec解码为MethodCall对象
            MethodCall call = MethodChannel.this.codec.decodeMethodCall(message);

            try {
                this.handler.onMethodCall(call, new MethodChannel.Result() {
                    public void success(Object result) {
                        reply.reply(MethodChannel.this.codec.encodeSuccessEnvelope(result));
                    }

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

                    public void notImplemented() {
                        reply.reply((ByteBuffer)null);
                    }
                });
            } catch (RuntimeException var5) {
                Log.e("MethodChannel#" + MethodChannel.this.name, "Failed to handle method call", var5);
                reply.reply(MethodChannel.this.codec.encodeErrorEnvelope("error", var5.getMessage(), (Object)null));
            }

        }
    }

方法中首先将从c++层传递过来的消息通过codec解码为MethodCall对象,然后调用注册的地方实现的MethodHandler的onMethodCall方法,该方法会接受flutter 传递的参数,

 case "success":
                //解析参数
                String value = methodCall.argument("flutter");
                // Toast.makeText(applicationContext, "" + value, Toast.LENGTH_SHORT).show();
                result.success("A");
                break;

然后调用result.success()方法,通过reply.reply(MethodChannel.this.codec.encodeSuccessEnvelope(result));将结果数据编码后进行返回。reply方法中会调用
DartMessenger .this.mFlutterJNI.invokePlatformMessageResponseCallback(replyId, reply, reply.position());方法将响应结果返回,方法具体实现如下

 public void reply(@Nullable ByteBuffer reply) {
            if (this.done.getAndSet(true)) {
                throw new IllegalStateException("Reply already submitted");
            } else {
                if (reply == null) {
                    this.flutterJNI.invokePlatformMessageEmptyResponseCallback(this.replyId);
                } else {
                    this.flutterJNI.invokePlatformMessageResponseCallback(this.replyId, reply, reply.position());
                }

            }
        }

最终会调用JNI方法将数据返回给c++层,

Dart层接收消息响应后的处理流程分析

通过以上Dart层传递消息分析可知PlatformMessageResponseCallback方法回调后对byte_buffer数据进行处理,通过completer.complete()方法完成返回数据,然后一步步返回到调用方法层,在异步方法中通过await等待数据返回后,再通过setState改变State中的变量值从而刷新页面数据信息显示到屏幕上。至此,整个flutter发消息给platform并接收消息处理的流程就完成了。

简约调用关系图如下

channel (1).jpg
image.png

总结,整个消息发送和接收结果的流程分为以下几步:

  • Dart层通过以上提到的12种类型包含的类型数据进行编码,然后通过dart的类似jni的本地接口方法传递给c++层;
  • c++层通过持有java对象flutterJNI的方法调用将消息传递到java层;
  • java层解码接收到的消息,根据消息内容做指定的逻辑处理,得到结果后进行编码通过jni方法将响应结果返回给c++层;
  • c++层处理返回的响应结果,并将结果通过发送时保存的dart响应方法对象回调给Dart层;
    Dart层通过回调方法对结果数据进行处理,然后通过codec解码数据做后续的操作;

相关文章

网友评论

      本文标题:flutter platform channel Metho

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