Flutter 和 Android原生交互探秘

2,108 阅读8分钟

前言

在flutter开发当中,难免有一些功能需要调用到原生,例如扫码,蓝牙等跟硬件打交道的功能。虽然说这些在网上有第三方的flutter库可以使用,但是架不住我们的产品需求特立独行啊。如果想要个性化定制扫码界面怎么办?如果想要个性化的交互,例如想停留在扫码界面连续扫码怎么办?如果想要在扫码界面扫描出条码然后连接蓝牙打印机打印怎么办?很难找到符合我现在这些需求的库,所以还是自己动手,丰衣足食吧。

Flutter 调用Android原生

通过MethodChannel来实现。MethodChannel相当于Flutter端和原生代码的消息通道。flutter调用原生的方法,通过MethodChannel将方法名和参数打包序列化为二级制数据,然后传递给Android端。Android端的插件代码用MethodChannel接受到方法flutter的方法调用后,可以通过方法名来知道到底调用的哪个方法,调用具体的方法实现即可。Android端代码可以通过BinaryMessenger 将方法执行的结果(返回值、报错信息)回传给Flutter端。

  • 调用步骤

    1. 约定好MethodChannel的通道名字。

      flutter端

      static const MethodChannel _channel =
            const MethodChannel('com.niimbot.flutter.scan');
      

      android端

      MethodChannel methodChannel = MethodChannel(flutterPluginBinding.binaryMessenger, "com.niimbot.flutter.scan")
      

      注意的是,这个通道名字在flutter和android两端必须相同。

    2. Flutter端调用指定原生函数

      static Future<String> scan(String title, String content) async {
          try {
            final Map<dynamic, dynamic> result = await _channel
                .invokeMethod('niimbot_scan', {'title': title, 'content': content});
            String code = result['code'];
            return code;
          } on PlatformException catch (e) {
            return e.details['error'];
          }
        }
      

      这里的的niimbot_scan是函数名的标识符,并不代表具体的函数名,怎么定义都行,只要android端也以这个名字作为对应函数标识符,调用指定函数即可。因为调用原生是异步操作,所以如果需要这个调用返回值,则需要返回Future类型的函数结果。

    3. 编写Android平台插件

      在项目android文件夹下新建一个类继承FlutterPlugin

      public class FlutterScanPlugin : FlutterPlugin, ActivityAware, MethodCallHandler{
      
      }
      

      我们可以看看这个FlutterPlugin类

      public interface FlutterPlugin {
      
        /**
         * This {@code FlutterPlugin} has been associated with a {@link FlutterEngine} instance.
         *
         * <p>Relevant resources that this {@code FlutterPlugin} may need are provided via the {@code
         * binding}. The {@code binding} may be cached and referenced until {@link
         * #onDetachedFromEngine(FlutterPluginBinding)} is invoked and returns.
         */
        void onAttachedToEngine(@NonNull FlutterPluginBinding binding);
      
        /**
         * This {@code FlutterPlugin} has been removed from a {@link FlutterEngine} instance.
         *
         * <p>The {@code binding} passed to this method is the same instance that was passed in {@link
         * #onAttachedToEngine(FlutterPluginBinding)}. It is provided again in this method as a
         * convenience. The {@code binding} may be referenced during the execution of this method, but it
         * must not be cached or referenced after this method returns.
         *
         * <p>{@code FlutterPlugin}s should release all resources in this method.
         */
        void onDetachedFromEngine(@NonNull FlutterPluginBinding binding);
      
        /**
         * Resources made available to all plugins registered with a given {@link FlutterEngine}.
         *
         * <p>The provided {@link BinaryMessenger} can be used to communicate with Dart code running in
         * the Flutter context associated with this plugin binding.
         *
         * <p>Plugins that need to respond to {@code Lifecycle} events should implement the additional
         * {@link ActivityAware} and/or {@link ServiceAware} interfaces, where a {@link Lifecycle}
         * reference can be obtained.
         */
        class FlutterPluginBinding {
          private final Context applicationContext;
          private final FlutterEngine flutterEngine;
          private final BinaryMessenger binaryMessenger;
          private final TextureRegistry textureRegistry;
          private final PlatformViewRegistry platformViewRegistry;
          private final FlutterAssets flutterAssets;
      
          public FlutterPluginBinding(
              @NonNull Context applicationContext,
              @NonNull FlutterEngine flutterEngine,
              @NonNull BinaryMessenger binaryMessenger,
              @NonNull TextureRegistry textureRegistry,
              @NonNull PlatformViewRegistry platformViewRegistry,
              @NonNull FlutterAssets flutterAssets) {
            this.applicationContext = applicationContext;
            this.flutterEngine = flutterEngine;
            this.binaryMessenger = binaryMessenger;
            this.textureRegistry = textureRegistry;
            this.platformViewRegistry = platformViewRegistry;
            this.flutterAssets = flutterAssets;
          }
      
          @NonNull
          public Context getApplicationContext() {
            return applicationContext;
          }
      
          /**
           * @deprecated Use {@code getBinaryMessenger()}, {@code getTextureRegistry()}, or {@code
           *     getPlatformViewRegistry()} instead.
           */
          @Deprecated
          @NonNull
          public FlutterEngine getFlutterEngine() {
            return flutterEngine;
          }
      
          @NonNull
          public BinaryMessenger getBinaryMessenger() {
            return binaryMessenger;
          }
      
          @NonNull
          public TextureRegistry getTextureRegistry() {
            return textureRegistry;
          }
      
          @NonNull
          public PlatformViewRegistry getPlatformViewRegistry() {
            return platformViewRegistry;
          }
      
          @NonNull
          public FlutterAssets getFlutterAssets() {
            return flutterAssets;
          }
        }
      
        /** Provides Flutter plugins with access to Flutter asset information. */
        interface FlutterAssets {
          /**
           * Returns the relative file path to the Flutter asset with the given name, including the file's
           * extension, e.g., {@code "myImage.jpg"}.
           *
           * <p>The returned file path is relative to the Android app's standard assets directory.
           * Therefore, the returned path is appropriate to pass to Android's {@code AssetManager}, but
           * the path is not appropriate to load as an absolute path.
           */
          String getAssetFilePathByName(@NonNull String assetFileName);
      
          /**
           * Same as {@link #getAssetFilePathByName(String)} but with added support for an explicit
           * Android {@code packageName}.
           */
          String getAssetFilePathByName(@NonNull String assetFileName, @NonNull String packageName);
      
          /**
           * Returns the relative file path to the Flutter asset with the given subpath, including the
           * file's extension, e.g., {@code "/dir1/dir2/myImage.jpg"}.
           *
           * <p>The returned file path is relative to the Android app's standard assets directory.
           * Therefore, the returned path is appropriate to pass to Android's {@code AssetManager}, but
           * the path is not appropriate to load as an absolute path.
           */
          String getAssetFilePathBySubpath(@NonNull String assetSubpath);
      
          /**
           * Same as {@link #getAssetFilePathBySubpath(String)} but with added support for an explicit
           * Android {@code packageName}.
           */
          String getAssetFilePathBySubpath(@NonNull String assetSubpath, @NonNull String packageName);
        }
      }
      

      这是一个接口,定义了onAttachedToEngine连接flutter engine的回调方法和onDetachedFromEngine和flutter engine断开连接的回调方法。flutter engine是flutter的底层,是用C++实现的,提供了flutter上层的框架的支持,同时也是flutter端和原生端代码相互访问的桥梁。

    4. 注册插件

      class MainActivity: FlutterActivity() {
          override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
              super.configureFlutterEngine(flutterEngine)
              flutterEngine.plugins.add(FlutterScanPlugin())
          }
      }
      

      如果插件是以单独的插件项目创建的,就可以省去这个代码,注册是通过GeneratedPluginRegistrant自动注册,里面会注册

    5. 在插件类的onAttachedToEngine回调中建立函数调用的消息通道

      override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
              this.flutterPluginBinding = flutterPluginBinding
              channel = MethodChannel(flutterPluginBinding.binaryMessenger, "com.niimbot.flutter.scan")
              //设置函数调用的回调,flutter端调用函数会走这个回调
              channel.setMethodCallHandler(this);
          }
      

      在插件连接到flutter engine之后,会回调onAttachedToEngine,将FlutterPluginBinding 对象回传过来。我们通过拿到这个对象,就可以拿到BinaryMessenger,这个相当于消息通道,Android端通过这个就可以建立和Flutter端的通信。

    6. 实现MethodCallHandler接口的onMethodCall 回调方法,实现具体调用

      override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
              this.result = result
              if (call.method == "getPlatformVersion") {
                  result.success("Android ${android.os.Build.VERSION.RELEASE}")
              } else if (call.method == "niimbot_scan") {
                  val title = call.argument<String>("title")
                  val content = call.argument<String>("content")
                  val intent = Intent(activityPluginBinding.activity,ScanActivity::class.java)
                  intent.putExtra(TAG_TITLE,title)
                  intent.putExtra(TAG_CONTENT,content)
                  activityPluginBinding.activity.startActivityForResult(intent, ACTIVITY_REQUEST_CODE)
              } else {
                  result.notImplemented()
              }
          }
      

      这里可以看到是按照具体的函数名称标识,去判断flutter端想要调用哪个函数的,这里的标识符名称和flutter端调用MethodChannel.invokeMethod 方法传递的函数名称标识符是一致的。知道了需要调用的具体方法,那么android端就可以具体实现了。通过Result对象将结果返回回去即可。如果是正常返回则调用result.success,如果是异常返回可以调用result.onError。result.notImplemented一般是当没有找到对应的函数名称标识符时,调用这个方法,flutter端会收到函数未实现的报错。

    7. 使插件里实现和acivity关联的回调,即ActivityAware里面的回调方法

      override fun onDetachedFromActivity() {
              channel.setMethodCallHandler(null)
          }
      
          override fun onReattachedToActivityForConfigChanges(activityPluginBinding: ActivityPluginBinding) {
              this.activityPluginBinding = activityPluginBinding
          }
      
          override fun onAttachedToActivity(activityPluginBinding: ActivityPluginBinding) {
              this.activityPluginBinding = activityPluginBinding
              this.activityPluginBinding.addActivityResultListener{requestCode,resultCode,data->
                  if(requestCode== ACTIVITY_REQUEST_CODE){
                      if(resultCode== Activity.RESULT_OK){
                          val reply = HashMap<String,Any>()
                          val code = data.getStringExtra(BaseScanActivity.RESULT_QRCODE_STRING)
                          reply["code"] = code
                          result.success(reply)
                      } else {
                          var error = "cancel"
                          data?.let {
                              error = it.getStringExtra("error")
                          }
                          val reply = HashMap<String,Any>()
                          reply["error"] = error
                          result.error("error",null,reply)
                      }
                  }
                  return@addActivityResultListener true
              }
          }
      

      onAttachedToActivity这个回调会返回一个ActivityPluginBinding对象,里面包含了一个正在运行的Activity的实例,我们可以通过这个Activity完成在Activity组件里才能完成的操作,例如请求权限,启动Activity,拍照等等。可以说到了这一步,就可以做Android端能做的所有事情,flutter端做不了的都可以用这种方法教给Android端代码完成。

      看一眼ActivityPluginBinding类

      public interface ActivityPluginBinding {
          @NonNull
          Activity getActivity();
      
          @NonNull
          Object getLifecycle();
      
          void addRequestPermissionsResultListener(@NonNull RequestPermissionsResultListener var1);
      
          void removeRequestPermissionsResultListener(@NonNull RequestPermissionsResultListener var1);
      
          void addActivityResultListener(@NonNull ActivityResultListener var1);
      
          void removeActivityResultListener(@NonNull ActivityResultListener var1);
      
          void addOnNewIntentListener(@NonNull NewIntentListener var1);
      
          void removeOnNewIntentListener(@NonNull NewIntentListener var1);
      
          void addOnUserLeaveHintListener(@NonNull UserLeaveHintListener var1);
      
          void removeOnUserLeaveHintListener(@NonNull UserLeaveHintListener var1);
      
          void addOnSaveStateListener(@NonNull ActivityPluginBinding.OnSaveInstanceStateListener var1);
      
          void removeOnSaveStateListener(@NonNull ActivityPluginBinding.OnSaveInstanceStateListener var1);
      
          public interface OnSaveInstanceStateListener {
              void onSaveInstanceState(@NonNull Bundle var1);
      
              void onRestoreInstanceState(@Nullable Bundle var1);
          }
      }
      

      里面定义了一些常用的Activity生命周期回调。看名字就能知道具体的作用。

      需要注意的是getLifecycle方法得到的对象不能直接使用,需要在pubspec.yaml引入flutter_plugin_android_lifecycle插件,调用FlutterLifecycleAdapter.getActivityLifecycle(activityPluginBinding),就可以得到可用的Lifecycle。

  • 关键类分析

    1. BinaryMessenger

      在原生和flutter通信中扮演十分重要的角色,它相当于原生代码和flutter端的消息通道,负责发送和接受消息。在Android端的实现是DartExecutor。

      截取部分代码:

      public class DartExecutor implements BinaryMessenger {
        private static final String TAG = "DartExecutor";
      
        @NonNull private final FlutterJNI flutterJNI;
        @NonNull private final AssetManager assetManager;
        @NonNull private final DartMessenger dartMessenger;
        @NonNull private final BinaryMessenger binaryMessenger;
        private boolean isApplicationRunning = false;
        @Nullable private String isolateServiceId;
        @Nullable private IsolateServiceIdListener isolateServiceIdListener;
      
        private final BinaryMessenger.BinaryMessageHandler isolateChannelMessageHandler =
            new BinaryMessenger.BinaryMessageHandler() {
              @Override
              public void onMessage(ByteBuffer message, final BinaryReply callback) {
                isolateServiceId = StringCodec.INSTANCE.decodeMessage(message);
                if (isolateServiceIdListener != null) {
                  isolateServiceIdListener.onIsolateServiceIdAvailable(isolateServiceId);
                }
              }
            };
      
        public DartExecutor(@NonNull FlutterJNI flutterJNI, @NonNull AssetManager assetManager) {
          this.flutterJNI = flutterJNI;
          this.assetManager = assetManager;
          this.dartMessenger = new DartMessenger(flutterJNI);
          dartMessenger.setMessageHandler("flutter/isolate", isolateChannelMessageHandler);
          this.binaryMessenger = new DefaultBinaryMessenger(dartMessenger);
        }
        private static class DefaultBinaryMessenger implements BinaryMessenger {
          private final DartMessenger messenger;
      
          private DefaultBinaryMessenger(@NonNull DartMessenger messenger) {
            this.messenger = messenger;
          }
      
          /**
           * Sends the given {@code message} from Android to Dart over the given {@code channel}.
           *
           * @param channel the name of the logical channel used for the message.
           * @param message the message payload, a direct-allocated {@link ByteBuffer} with the message
           *     bytes
           */
          @Override
          @UiThread
          public void send(@NonNull String channel, @Nullable ByteBuffer message) {
            messenger.send(channel, message, null);
          }
      
          /**
           * Sends the given {@code messages} from Android to Dart over the given {@code channel} and then
           * has the provided {@code callback} invoked when the Dart side responds.
           *
           * @param channel the name of the logical channel used for the message.
           * @param message the message payload, a direct-allocated {@link ByteBuffer} with the message
           *     bytes between position zero and current position, or null.
           * @param callback a callback invoked when the Dart application responds to the message
           */
          @Override
          @UiThread
          public void send(
              @NonNull String channel,
              @Nullable ByteBuffer message,
              @Nullable BinaryMessenger.BinaryReply callback) {
            messenger.send(channel, message, callback);
          }
      
          /**
           * Sets the given {@link io.flutter.plugin.common.BinaryMessenger.BinaryMessageHandler} as the
           * singular handler for all incoming messages received from the Dart side of this Dart execution
           * context.
           *
           * @param channel the name of the channel.
           * @param handler a {@link BinaryMessageHandler} to be invoked on incoming messages, or null.
           */
          @Override
          @UiThread
          public void setMessageHandler(
              @NonNull String channel, @Nullable BinaryMessenger.BinaryMessageHandler handler) {
            messenger.setMessageHandler(channel, handler);
          }
        }
      }
      

      可以看到内部持有了一个DefaultBinaryMessenger对象,而这个对象里面又包裹了一个DartMessenger对象,具体的实现逻辑在这个类。

      class DartMessenger implements BinaryMessenger, PlatformMessageHandler {
        private static final String TAG = "DartMessenger";
      
        @NonNull private final FlutterJNI flutterJNI;
        @NonNull private final Map<String, BinaryMessenger.BinaryMessageHandler> messageHandlers;
        @NonNull private final Map<Integer, BinaryMessenger.BinaryReply> pendingReplies;
        private int nextReplyId = 1;
      
        DartMessenger(@NonNull FlutterJNI flutterJNI) {
          this.flutterJNI = flutterJNI;
          this.messageHandlers = new HashMap<>();
          this.pendingReplies = new HashMap<>();
        }
      
        @Override
        public void setMessageHandler(
            @NonNull String channel, @Nullable BinaryMessenger.BinaryMessageHandler handler) {
          if (handler == null) {
            Log.v(TAG, "Removing handler for channel '" + channel + "'");
            messageHandlers.remove(channel);
          } else {
            Log.v(TAG, "Setting handler for channel '" + channel + "'");
            messageHandlers.put(channel, handler);
          }
        }
      
        @Override
        @UiThread
        public void send(@NonNull String channel, @NonNull ByteBuffer message) {
          Log.v(TAG, "Sending message over channel '" + channel + "'");
          send(channel, message, null);
        }
      
        @Override
        public void send(
            @NonNull String channel,
            @Nullable ByteBuffer message,
            @Nullable BinaryMessenger.BinaryReply callback) {
          Log.v(TAG, "Sending message with callback over channel '" + channel + "'");
          int replyId = 0;
          if (callback != null) {
            replyId = nextReplyId++;
            pendingReplies.put(replyId, callback);
          }
          if (message == null) {
            flutterJNI.dispatchEmptyPlatformMessage(channel, replyId);
          } else {
            flutterJNI.dispatchPlatformMessage(channel, message, message.position(), replyId);
          }
        }
      
        @Override
        public void handleMessageFromDart(
            @NonNull final String channel, @Nullable byte[] message, final int replyId) {
          Log.v(TAG, "Received message from Dart over channel '" + channel + "'");
          BinaryMessenger.BinaryMessageHandler handler = messageHandlers.get(channel);
          if (handler != null) {
            try {
              Log.v(TAG, "Deferring to registered handler to process message.");
              final ByteBuffer buffer = (message == null ? null : ByteBuffer.wrap(message));
              handler.onMessage(buffer, new Reply(flutterJNI, replyId));
            } catch (Exception ex) {
              Log.e(TAG, "Uncaught exception in binary message listener", ex);
              flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
            }
          } else {
            Log.v(TAG, "No registered handler for message. Responding to Dart with empty reply message.");
            flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
          }
        }
      
        @Override
        public void handlePlatformMessageResponse(int replyId, @Nullable byte[] reply) {
          Log.v(TAG, "Received message reply from Dart.");
          BinaryMessenger.BinaryReply callback = pendingReplies.remove(replyId);
          if (callback != null) {
            try {
              Log.v(TAG, "Invoking registered callback for reply from Dart.");
              callback.reply(reply == null ? null : ByteBuffer.wrap(reply));
            } catch (Exception ex) {
              Log.e(TAG, "Uncaught exception in binary message reply handler", ex);
            }
          }
        }
      
        /**
         * Returns the number of pending channel callback replies.
         *
         * <p>When sending messages to the Flutter application using {@link BinaryMessenger#send(String,
         * ByteBuffer, io.flutter.plugin.common.BinaryMessenger.BinaryReply)}, developers can optionally
         * specify a reply callback if they expect a reply from the Flutter application.
         *
         * <p>This method tracks all the pending callbacks that are waiting for response, and is supposed
         * to be called from the main thread (as other methods). Calling from a different thread could
         * possibly capture an indeterministic internal state, so don't do it.
         */
        @UiThread
        public int getPendingChannelResponseCount() {
          return pendingReplies.size();
        }
      
        private static class Reply implements BinaryMessenger.BinaryReply {
          @NonNull private final FlutterJNI flutterJNI;
          private final int replyId;
          private final AtomicBoolean done = new AtomicBoolean(false);
      
          Reply(@NonNull FlutterJNI flutterJNI, int replyId) {
            this.flutterJNI = flutterJNI;
            this.replyId = replyId;
          }
      
          @Override
          public void reply(@Nullable ByteBuffer reply) {
            if (done.getAndSet(true)) {
              throw new IllegalStateException("Reply already submitted");
            }
            if (reply == null) {
              flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
            } else {
              flutterJNI.invokePlatformMessageResponseCallback(replyId, reply, reply.position());
            }
          }
        }
      }
      

      handleMessageFromDart这个方法会接收来自flutter的消息,然后根据Channel名字取出对应的BinaryMessageHandler,例如我们的MethodChannel的BinaryMessageHandler就是MethodChannel中包裹了FlutterScanPlugin这个插件的IncomingMethodCallHandler,将消息转发给它处理。最终调用FlutterScanPlugin里实现的onMethodCall回调。

      @Override
          @UiThread
          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.encodeErrorEnvelope("error", e.getMessage(), null));
            }
          }
      

      具体的接收flutter端消息和回复给flutter端消息实现在FlutterJNI里,这个类涉及了JNI native 方法的调用,最终调用到了flutter engine的实现代码。FlutterJNI部分代码截取:

      @Keep
      public class FlutterJNI {
        private static final String TAG = "FlutterJNI";
      
        @Nullable private static AsyncWaitForVsyncDelegate asyncWaitForVsyncDelegate;
        // This should also be updated by FlutterView when it is attached to a Display.
        // The initial value of 0.0 indicates unknown refresh rate.
        private static float refreshRateFPS = 0.0f;
      
        // This is set from native code via JNI.
        @Nullable private static String observatoryUri;
      
        // TODO(mattcarroll): add javadocs
        public static native void nativeInit(
            @NonNull Context context,
            @NonNull String[] args,
            @Nullable String bundlePath,
            @NonNull String appStoragePath,
            @NonNull String engineCachesPath,
            long initTimeMillis);
      
        // --------- Start Platform Message Support ------
        /**
         * Sets the handler for all platform messages that come from the attached platform view to Java.
         *
         * <p>Communication between a specific Flutter context (Dart) and the host platform (Java) is
         * accomplished by passing messages. Messages can be sent from Java to Dart with the corresponding
         * {@code FlutterJNI} methods:
         *
         * <ul>
         *   <li>{@link #dispatchPlatformMessage(String, ByteBuffer, int, int)}
         *   <li>{@link #dispatchEmptyPlatformMessage(String, int)}
         * </ul>
         *
         * <p>{@code FlutterJNI} is also the recipient of all platform messages sent from its attached
         * Flutter context. {@code FlutterJNI} does not know what to do with these messages, so a handler
         * is exposed to allow these messages to be processed in whatever manner is desired:
         *
         * <p>{@code setPlatformMessageHandler(PlatformMessageHandler)}
         *
         * <p>If a message is received but no {@link PlatformMessageHandler} is registered, that message
         * will be dropped (ignored). Therefore, when using {@code FlutterJNI} to integrate a Flutter
         * context in an app, a {@link PlatformMessageHandler} must be registered for 2-way Java/Dart
         * communication to operate correctly. Moreover, the handler must be implemented such that
         * fundamental platform messages are handled as expected. See {@link FlutterNativeView} for an
         * example implementation.
         */
        @UiThread
        public void setPlatformMessageHandler(@Nullable PlatformMessageHandler platformMessageHandler) {
          ensureRunningOnMainThread();
          this.platformMessageHandler = platformMessageHandler;
        }
      
        // Called by native.
        // TODO(mattcarroll): determine if message is nonull or nullable
        @SuppressWarnings("unused")
        @VisibleForTesting
        public void handlePlatformMessage(
            @NonNull final String channel, byte[] message, final int replyId) {
          if (platformMessageHandler != null) {
            platformMessageHandler.handleMessageFromDart(channel, message, replyId);
          }
          // TODO(mattcarroll): log dropped messages when in debug mode
          // (https://github.com/flutter/flutter/issues/25391)
        }
      
        // Called by native to respond to a platform message that we sent.
        // TODO(mattcarroll): determine if reply is nonull or nullable
        @SuppressWarnings("unused")
        private void handlePlatformMessageResponse(int replyId, byte[] reply) {
          if (platformMessageHandler != null) {
            platformMessageHandler.handlePlatformMessageResponse(replyId, reply);
          }
          // TODO(mattcarroll): log dropped messages when in debug mode
          // (https://github.com/flutter/flutter/issues/25391)
        }
      
        /**
         * Sends an empty reply (identified by {@code responseId}) from Android to Flutter over the given
         * {@code channel}.
         */
        @UiThread
        public void dispatchEmptyPlatformMessage(@NonNull String channel, int responseId) {
          ensureRunningOnMainThread();
          if (isAttached()) {
            nativeDispatchEmptyPlatformMessage(nativePlatformViewId, channel, responseId);
          } else {
            Log.w(
                TAG,
                "Tried to send a platform message to Flutter, but FlutterJNI was detached from native C++. Could not send. Channel: "
                    + channel
                    + ". Response ID: "
                    + responseId);
          }
        }
      
        // Send an empty platform message to Dart.
        private native void nativeDispatchEmptyPlatformMessage(
            long nativePlatformViewId, @NonNull String channel, int responseId);
      
        /** Sends a reply {@code message} from Android to Flutter over the given {@code channel}. */
        @UiThread
        public void dispatchPlatformMessage(
            @NonNull String channel, @Nullable ByteBuffer message, int position, int responseId) {
          ensureRunningOnMainThread();
          if (isAttached()) {
            nativeDispatchPlatformMessage(nativePlatformViewId, channel, message, position, responseId);
          } else {
            Log.w(
                TAG,
                "Tried to send a platform message to Flutter, but FlutterJNI was detached from native C++. Could not send. Channel: "
                    + channel
                    + ". Response ID: "
                    + responseId);
          }
        }
      
        // Send a data-carrying platform message to Dart.
        private native void nativeDispatchPlatformMessage(
            long nativePlatformViewId,
            @NonNull String channel,
            @Nullable ByteBuffer message,
            int position,
            int responseId);
      
        // TODO(mattcarroll): differentiate between channel responses and platform responses.
        @UiThread
        public void invokePlatformMessageEmptyResponseCallback(int responseId) {
          ensureRunningOnMainThread();
          if (isAttached()) {
            nativeInvokePlatformMessageEmptyResponseCallback(nativePlatformViewId, responseId);
          } else {
            Log.w(
                TAG,
                "Tried to send a platform message response, but FlutterJNI was detached from native C++. Could not send. Response ID: "
                    + responseId);
          }
        }
      
        // Send an empty response to a platform message received from Dart.
        private native void nativeInvokePlatformMessageEmptyResponseCallback(
            long nativePlatformViewId, int responseId);
      
        // TODO(mattcarroll): differentiate between channel responses and platform responses.
        @UiThread
        public void invokePlatformMessageResponseCallback(
            int responseId, @Nullable ByteBuffer message, int position) {
          ensureRunningOnMainThread();
          if (isAttached()) {
            nativeInvokePlatformMessageResponseCallback(
                nativePlatformViewId, responseId, message, position);
          } else {
            Log.w(
                TAG,
                "Tried to send a platform message response, but FlutterJNI was detached from native C++. Could not send. Response ID: "
                    + responseId);
          }
        }
      
        // Send a data-carrying response to a platform message received from Dart.
        private native void nativeInvokePlatformMessageResponseCallback(
            long nativePlatformViewId, int responseId, @Nullable ByteBuffer message, int position);
      }
      

      JNI的native代码会调用到handlePlatformMessage这个方法,将flutter端的消息发送给android端。invokePlatformMessageResponseCallback则会调用JNI的native方法,将android端消息发送给flutter端。

      flutter 端的MethodChannel.invokeMethod会调用到

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

      可以看到同样是通过BinaryMessenger发送消息。如果MethodChannel没有指定BinaryMessenger就是使用的默认的实现:

      class _DefaultBinaryMessenger extends BinaryMessenger {
        const _DefaultBinaryMessenger._();
      
        // Handlers for incoming messages from platform plugins.
        // This is static so that this class can have a const constructor.
        static final Map<String, MessageHandler> _handlers =
            <String, MessageHandler>{};
      
        // Mock handlers that intercept and respond to outgoing messages.
        // This is static so that this class can have a const constructor.
        static final Map<String, MessageHandler> _mockHandlers =
            <String, MessageHandler>{};
      
        Future<ByteData> _sendPlatformMessage(String channel, ByteData message) {
          final Completer<ByteData> completer = Completer<ByteData>();
          // ui.window is accessed directly instead of using ServicesBinding.instance.window
          // because this method might be invoked before any binding is initialized.
          // This issue was reported in #27541. It is not ideal to statically access
          // ui.window because the Window may be dependency injected elsewhere with
          // a different instance. However, static access at this location seems to be
          // the least bad option.
          ui.window.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;
        }
        }
      

      跟进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';
      

      这段定义在window.dart中,属于flutter engine里的类。可以看到走到这里就转向flutter engine 的 native层了,将消息转发给android端。

      flutter engine 实现的代码这里不做展开。

    2. MethodCodec

      这个在flutter 和 android 通信中作用是编解码数据。为什么需要这个东西呢,因为flutter和原生端传递的数据是二进制数据,直接拿到这些数据根本不知道是啥,所以需要根据一定的规则编解码数据,flutter和原生端才能将二进制数据转换为语言支持的数据类型。flutter端默认使用的是StandardMethodCodec,而这个类里面的编解码实现是用到了StandardMessageCodec。截取部分代码:

      class StandardMessageCodec implements MessageCodec<dynamic> {
        /// Creates a [MessageCodec] using the Flutter standard binary encoding.
        const StandardMessageCodec();
        static const int _valueNull = 0;
        static const int _valueTrue = 1;
        static const int _valueFalse = 2;
        static const int _valueInt32 = 3;
        static const int _valueInt64 = 4;
        static const int _valueLargeInt = 5;
        static const int _valueFloat64 = 6;
        static const int _valueString = 7;
        static const int _valueUint8List = 8;
        static const int _valueInt32List = 9;
        static const int _valueInt64List = 10;
        static const int _valueFloat64List = 11;
        static const int _valueList = 12;
        static const int _valueMap = 13;
        
        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 Uint8List 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);
          }
        }
        
        /// Reads a value from [buffer] as written by [writeValue].
        ///
        /// This method is intended for use by subclasses overriding
        /// [readValueOfType].
        dynamic readValue(ReadBuffer buffer) {
          if (!buffer.hasRemaining)
            throw const FormatException('Message corrupted');
          final int type = buffer.getUint8();
          return readValueOfType(type, buffer);
        }
      
        /// Reads a value of the indicated [type] from [buffer].
        ///
        /// The codec can be extended by overriding this method, calling super for
        /// types that the extension does not handle. See the discussion at
        /// [writeValue].
        dynamic readValueOfType(int type, ReadBuffer buffer) {
          switch (type) {
            case _valueNull:
              return null;
            case _valueTrue:
              return true;
            case _valueFalse:
              return false;
            case _valueInt32:
              return buffer.getInt32();
            case _valueInt64:
              return buffer.getInt64();
            case _valueFloat64:
              return buffer.getFloat64();
            case _valueLargeInt:
            case _valueString:
              final int length = readSize(buffer);
              return utf8.decoder.convert(buffer.getUint8List(length));
            case _valueUint8List:
              final int length = readSize(buffer);
              return buffer.getUint8List(length);
            case _valueInt32List:
              final int length = readSize(buffer);
              return buffer.getInt32List(length);
            case _valueInt64List:
              final int length = readSize(buffer);
              return buffer.getInt64List(length);
            case _valueFloat64List:
              final int length = readSize(buffer);
              return buffer.getFloat64List(length);
            case _valueList:
              final int length = readSize(buffer);
              final dynamic result = List<dynamic>(length);
              for (int i = 0; i < length; i++)
                result[i] = readValue(buffer);
              return result;
            case _valueMap:
              final int length = readSize(buffer);
              final dynamic result = <dynamic, dynamic>{};
              for (int i = 0; i < length; i++)
                result[readValue(buffer)] = readValue(buffer);
              return result;
            default: throw const FormatException('Message corrupted');
          }
        }
      }
      

      这里通过特定的数字约定数据的类型,发送消息给android端的话,针对每个参数分别写入数据类型(用约定的int类型表示)、大小(list、map数据)、二级制数据。如果接收android端消息,也是以同样的规则来解析二进制数据,还原成flutter的数据类型的数据。从数据类型定义就可以看出,支持的数据类型有:null、bool、int、8字节int、String、double、Uint8List、Int32List、Float64List、List、Map。这里放上一张各个平台类型的对照表:

      DartJavaKotlinOCSwift
      nullnullnullnil (NSNull when nested)nil
      booljava.lang.BooleanBooleanNSNumber numberWithBool:NSNumber(value: Bool)
      intjava.lang.IntegerIntNSNumber numberWithInt:NSNumber(value: Int32)
      int, if 32 bits not enoughjava.lang.LongLongNSNumber numberWithLong:NSNumber(value: Int)
      doublejava.lang.DoubleDoubleNSNumber numberWithDouble:NSNumber(value: Double)
      Stringjava.lang.StringStringNSStringString
      Uint8Listbyte[]ByteArrayFlutterStandardTypedData typedDataWithBytes:FlutterStandardTypedData(bytes: Data)
      Int32Listint[]IntArrayFlutterStandardTypedData typedDataWithInt32:FlutterStandardTypedData(int32: Data)
      Int64Listlong[]LongArrayFlutterStandardTypedData typedDataWithInt64:FlutterStandardTypedData(int64: Data)
      Float64Listdouble[]DoubleArrayFlutterStandardTypedData typedDataWithFloat64:FlutterStandardTypedData(float64: Data)
      Listjava.util.ArrayListListNSArrayArray
      Mapjava.util.HashMapHashMapNSDictionaryDictionary

      这里再看看Android端StandardMethodCodec类:

      public final class StandardMethodCodec implements MethodCodec {
          public static final StandardMethodCodec INSTANCE;
          private final StandardMessageCodec messageCodec;
      
          public StandardMethodCodec(StandardMessageCodec messageCodec) {
              this.messageCodec = messageCodec;
          }
      
          public ByteBuffer encodeMethodCall(MethodCall methodCall) {
              ExposedByteArrayOutputStream stream = new ExposedByteArrayOutputStream();
              this.messageCodec.writeValue(stream, methodCall.method);
              this.messageCodec.writeValue(stream, methodCall.arguments);
              ByteBuffer buffer = ByteBuffer.allocateDirect(stream.size());
              buffer.put(stream.buffer(), 0, stream.size());
              return buffer;
          }
      
          public MethodCall decodeMethodCall(ByteBuffer methodCall) {
              methodCall.order(ByteOrder.nativeOrder());
              Object method = this.messageCodec.readValue(methodCall);
              Object arguments = this.messageCodec.readValue(methodCall);
              if (method instanceof String && !methodCall.hasRemaining()) {
                  return new MethodCall((String)method, arguments);
              } else {
                  throw new IllegalArgumentException("Method call corrupted");
              }
          }
      
          public ByteBuffer encodeSuccessEnvelope(Object result) {
              ExposedByteArrayOutputStream stream = new ExposedByteArrayOutputStream();
              stream.write(0);
              this.messageCodec.writeValue(stream, result);
              ByteBuffer buffer = ByteBuffer.allocateDirect(stream.size());
              buffer.put(stream.buffer(), 0, stream.size());
              return buffer;
          }
      
          public ByteBuffer encodeErrorEnvelope(String errorCode, String errorMessage, Object errorDetails) {
              ExposedByteArrayOutputStream stream = new ExposedByteArrayOutputStream();
              stream.write(1);
              this.messageCodec.writeValue(stream, errorCode);
              this.messageCodec.writeValue(stream, errorMessage);
              this.messageCodec.writeValue(stream, errorDetails);
              ByteBuffer buffer = ByteBuffer.allocateDirect(stream.size());
              buffer.put(stream.buffer(), 0, stream.size());
              return buffer;
          }
      
          public Object decodeEnvelope(ByteBuffer envelope) {
              envelope.order(ByteOrder.nativeOrder());
              byte flag = envelope.get();
              Object code;
              switch(flag) {
              case 0:
                  code = this.messageCodec.readValue(envelope);
                  if (!envelope.hasRemaining()) {
                      return code;
                  }
              case 1:
                  code = this.messageCodec.readValue(envelope);
                  Object message = this.messageCodec.readValue(envelope);
                  Object details = this.messageCodec.readValue(envelope);
                  if (code instanceof String && (message == null || message instanceof String) && !envelope.hasRemaining()) {
                      throw new FlutterException((String)code, (String)message, details);
                  }
              default:
                  throw new IllegalArgumentException("Envelope corrupted");
              }
          }
      
          static {
              INSTANCE = new StandardMethodCodec(StandardMessageCodec.INSTANCE);
          }
      }
      

      可以看到同样是用StandardMessageCodec实现数据的具体编解码:

      public class StandardMessageCodec implements MessageCodec<Object> {
          private static final String TAG = "StandardMessageCodec#";
          public static final StandardMessageCodec INSTANCE = new StandardMessageCodec();
          private static final boolean LITTLE_ENDIAN;
          private static final Charset UTF8;
          private static final byte NULL = 0;
          private static final byte TRUE = 1;
          private static final byte FALSE = 2;
          private static final byte INT = 3;
          private static final byte LONG = 4;
          private static final byte BIGINT = 5;
          private static final byte DOUBLE = 6;
          private static final byte STRING = 7;
          private static final byte BYTE_ARRAY = 8;
          private static final byte INT_ARRAY = 9;
          private static final byte LONG_ARRAY = 10;
          private static final byte DOUBLE_ARRAY = 11;
          private static final byte LIST = 12;
          private static final byte MAP = 13;
          
           protected void writeValue(ByteArrayOutputStream stream, Object value) {
              if (value != null && !value.equals((Object)null)) {
                  if (value == Boolean.TRUE) {
                      stream.write(1);
                  } else if (value == Boolean.FALSE) {
                      stream.write(2);
                  } else if (value instanceof Number) {
                      if (!(value instanceof Integer) && !(value instanceof Short) && !(value instanceof Byte)) {
                          if (value instanceof Long) {
                              stream.write(4);
                              writeLong(stream, (Long)value);
                          } else if (!(value instanceof Float) && !(value instanceof Double)) {
                              if (!(value instanceof BigInteger)) {
                                  throw new IllegalArgumentException("Unsupported Number type: " + value.getClass());
                              }
      
                              stream.write(5);
                              writeBytes(stream, ((BigInteger)value).toString(16).getBytes(UTF8));
                          } else {
                              stream.write(6);
                              writeAlignment(stream, 8);
                              writeDouble(stream, ((Number)value).doubleValue());
                          }
                      } else {
                          stream.write(3);
                          writeInt(stream, ((Number)value).intValue());
                      }
                  } else if (value instanceof String) {
                      stream.write(7);
                      writeBytes(stream, ((String)value).getBytes(UTF8));
                  } else if (value instanceof byte[]) {
                      stream.write(8);
                      writeBytes(stream, (byte[])((byte[])value));
                  } else {
                      int var5;
                      int var6;
                      if (value instanceof int[]) {
                          stream.write(9);
                          int[] array = (int[])((int[])value);
                          writeSize(stream, array.length);
                          writeAlignment(stream, 4);
                          int[] var4 = array;
                          var5 = array.length;
      
                          for(var6 = 0; var6 < var5; ++var6) {
                              int n = var4[var6];
                              writeInt(stream, n);
                          }
                      } else if (value instanceof long[]) {
                          stream.write(10);
                          long[] array = (long[])((long[])value);
                          writeSize(stream, array.length);
                          writeAlignment(stream, 8);
                          long[] var12 = array;
                          var5 = array.length;
      
                          for(var6 = 0; var6 < var5; ++var6) {
                              long n = var12[var6];
                              writeLong(stream, n);
                          }
                      } else if (value instanceof double[]) {
                          stream.write(11);
                          double[] array = (double[])((double[])value);
                          writeSize(stream, array.length);
                          writeAlignment(stream, 8);
                          double[] var14 = array;
                          var5 = array.length;
      
                          for(var6 = 0; var6 < var5; ++var6) {
                              double d = var14[var6];
                              writeDouble(stream, d);
                          }
                      } else {
                          Iterator var15;
                          if (value instanceof List) {
                              stream.write(12);
                              List<?> list = (List)value;
                              writeSize(stream, list.size());
                              var15 = list.iterator();
      
                              while(var15.hasNext()) {
                                  Object o = var15.next();
                                  this.writeValue(stream, o);
                              }
                          } else {
                              if (!(value instanceof Map)) {
                                  throw new IllegalArgumentException("Unsupported value: " + value);
                              }
      
                              stream.write(13);
                              Map<?, ?> map = (Map)value;
                              writeSize(stream, map.size());
                              var15 = map.entrySet().iterator();
      
                              while(var15.hasNext()) {
                                  Entry<?, ?> entry = (Entry)var15.next();
                                  this.writeValue(stream, entry.getKey());
                                  this.writeValue(stream, entry.getValue());
                              }
                          }
                      }
                  }
              } else {
                  stream.write(0);
              }
      
          }
          
      }
      

      和flutter端相同的编解码方式,不再赘述了。

总结

flutter和原生的交互本质上是通过二级制流的消息来完成相互的通信。MethodChannel作为通信的频道,封装了方法调用这类消息的发送接收和编解码;BinaryMessenger作为消息通道,负责发送和接收消息;MethodCodec作为编解码器,负责序列化和反序列化数据;而flutter engine 作为flutter和原生两者通信的桥梁,负责消息的转发。