浅谈Flutter跨平台调用方式MethodChannel

33 篇文章 0 订阅
5 篇文章 3 订阅

Flutter是目前非常流行的跨平台方案,由于它的性能接近于原生应用,因而被越来越多的开发者所采用。既然是跨平台方案,那么久必然存在调用系统功能的需求,在Flutter中,Flutter层与native层的互调,是通过MethodChannel来实现的。下面来简单的分析下Android端调用Flutter的过程。

MethodChannel channel = new MethodChannel(messenger, CHANNEL_NAME , StandardMethodCodec.INSTANCE);

///构造函数
public MethodChannel(BinaryMessenger messenger, String name, MethodCodec codec)

上面创建了一个MethodChannel,从构造函数中可以看到有三个参数, messenger,name,codec.

其中messenger的参数类型是BinaryMessenger,  MethodChannel就是通过BinaryMessenger来实现和Flutter层的交互,BinaryMessenger存在期间,是在单线程中执行的,如果在主线程中创建,那么就在主线程中执行,如果是在子线程中创建的,则在主线程中执行。name指定了渠道的名字,应用中可以存在多个渠道,这个需要与Flutter层中的渠道名称一致。codec指定了信息编解码的方式, codec的类型是MethodCodec,它有两种实现:JsonMethodCodec 、StandardMethodCodec。这两种方式的区别在于编解码的方式不一样,但最终都会把消息转换成ByteBuffer。我们分别看下这种方式的编码方式:

JSONUtils.java
/** Backport of {@link JSONObject#wrap(Object)} for use on pre-KitKat systems. */
  public static Object wrap(Object o) {
    if (o == null) {
      return JSONObject.NULL;
    }
    if (o instanceof JSONArray || o instanceof JSONObject) {
      return o;
    }
    if (o.equals(JSONObject.NULL)) {
      return o;
    }
    try {
      if (o instanceof Collection) {
        JSONArray result = new JSONArray();
        for (Object e : (Collection) o) result.put(wrap(e));
        return result;
      } else if (o.getClass().isArray()) {
        JSONArray result = new JSONArray();
        int length = Array.getLength(o);
        for (int i = 0; i < length; i++) result.put(wrap(Array.get(o, i)));
        return result;
      }
      if (o instanceof Map) {
        JSONObject result = new JSONObject();
        for (Map.Entry<?, ?> entry : ((Map<?, ?>) o).entrySet())
          result.put((String) entry.getKey(), wrap(entry.getValue()));
        return result;
      }
      if (o instanceof Boolean
          || o instanceof Byte
          || o instanceof Character
          || o instanceof Double
          || o instanceof Float
          || o instanceof Integer
          || o instanceof Long
          || o instanceof Short
          || o instanceof String) {
        return o;
      }
      if (o.getClass().getPackage().getName().startsWith("java.")) {
        return o.toString();
      }
    } catch (Exception ignored) {
    }
    return null;
  }

 @Override
  public ByteBuffer encodeMessage(Object message) {
    if (message == null) {
      return null;
    }
    final Object wrapped = JSONUtil.wrap(message);
    if (wrapped instanceof String) {
      return StringCodec.INSTANCE.encodeMessage(JSONObject.quote((String)             wrapped));
    } else {
      return StringCodec.INSTANCE.encodeMessage(wrapped.toString());
    }
  }


@Override
  public ByteBuffer encodeMessage(String message) {
    if (message == null) {
      return null;
    }
    // TODO(mravn): Avoid the extra copy below.
    final byte[] bytes = message.getBytes(UTF8);
    final ByteBuffer buffer = ByteBuffer.allocateDirect(bytes.length);
    buffer.put(bytes);
    return buffer;
  }

可以很清楚的看到,JSONMethodCodec会把数据包装成Json的格式,最终装换成ByteBuffer,同时可以看到只支持基本数据类型及相关集合。

/**
   * Writes a type discriminator byte and then a byte serialization of the specified value to the
   * specified stream.
   *
   * <p>Subclasses can extend the codec by overriding this method, calling super for values that the
   * extension does not handle.
   */
  protected void writeValue(ByteArrayOutputStream stream, Object value) {
    if (value == null || value.equals(null)) {
      stream.write(NULL);
    } else if (value == Boolean.TRUE) {
      stream.write(TRUE);
    } else if (value == Boolean.FALSE) {
      stream.write(FALSE);
    } else if (value instanceof Number) {
      if (value instanceof Integer || value instanceof Short || value instanceof Byte) {
        stream.write(INT);
        writeInt(stream, ((Number) value).intValue());
      } else if (value instanceof Long) {
        stream.write(LONG);
        writeLong(stream, (long) value);
      } else if (value instanceof Float || value instanceof Double) {
        stream.write(DOUBLE);
        writeAlignment(stream, 8);
        writeDouble(stream, ((Number) value).doubleValue());
      } else if (value instanceof BigInteger) {
        stream.write(BIGINT);
        writeBytes(stream, ((BigInteger) value).toString(16).getBytes(UTF8));
      } else {
        throw new IllegalArgumentException("Unsupported Number type: " + value.getClass());
      }
    } else if (value instanceof String) {
      stream.write(STRING);
      writeBytes(stream, ((String) value).getBytes(UTF8));
    } else if (value instanceof byte[]) {
      stream.write(BYTE_ARRAY);
      writeBytes(stream, (byte[]) value);
    } else if (value instanceof int[]) {
      stream.write(INT_ARRAY);
      final int[] array = (int[]) value;
      writeSize(stream, array.length);
      writeAlignment(stream, 4);
      for (final int n : array) {
        writeInt(stream, n);
      }
    } else if (value instanceof long[]) {
      stream.write(LONG_ARRAY);
      final long[] array = (long[]) value;
      writeSize(stream, array.length);
      writeAlignment(stream, 8);
      for (final long n : array) {
        writeLong(stream, n);
      }
    } else if (value instanceof double[]) {
      stream.write(DOUBLE_ARRAY);
      final double[] array = (double[]) value;
      writeSize(stream, array.length);
      writeAlignment(stream, 8);
      for (final double d : array) {
        writeDouble(stream, d);
      }
    } else if (value instanceof List) {
      stream.write(LIST);
      final List<?> list = (List) value;
      writeSize(stream, list.size());
      for (final Object o : list) {
        writeValue(stream, o);
      }
    } else if (value instanceof Map) {
      stream.write(MAP);
      final Map<?, ?> map = (Map) value;
      writeSize(stream, map.size());
      for (final Entry<?, ?> entry : map.entrySet()) {
        writeValue(stream, entry.getKey());
        writeValue(stream, entry.getValue());
      }
    } else {
      throw new IllegalArgumentException("Unsupported value: " + value);
    }
  }

同样的,StandardMethodCodec同样是返回的是ByteBuffer,也是仅支持基本数据类型及相关集合。解码方式刚好相反,再此就不列出来了。

@UiThread
  public void invokeMethod(String method, @Nullable Object arguments, @Nullable Result callback) {
    messenger.send(
        name,
        codec.encodeMethodCall(new MethodCall(method, arguments)),
        callback == null ? null : new IncomingResultHandler(callback));
  }

MethodChannel的invokeMethod方法,触发了与Flutter的交互,可以看到,通过MethodCodec编码之后,通过BinaryMessenger的send方法发送数据给flutter层:


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

/** 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);

从截取的代码中可以看到最终是通过jni来完成Flutter和Native的交互的,另外invokeMethod方法需要在UI线程中执行,否则会跑出异常:

 private void ensureRunningOnMainThread() {
    if (Looper.myLooper() != mainLooper) {
      throw new RuntimeException(
          "Methods marked with @UiThread must be executed on the main thread. Current thread: "
              + Thread.currentThread().getName());
    }
  }

上面就是MethodChannel发送消息的流程,那么是如何接受消息的呢。上面涉及到的BinaryMessenger有个内部接口:

 /** Handler for incoming binary messages from Flutter. */
  interface BinaryMessageHandler {
    /**
     * Handles the specified message.
     *
     * <p>Handler implementations must reply to all incoming messages, by submitting a single reply
     * message to the given {@link BinaryReply}. Failure to do so will result in lingering Flutter
     * reply handlers. The reply may be submitted asynchronously.
     *
     * <p>Any uncaught exception thrown by this method will be caught by the messenger
     * implementation and logged, and a null reply message will be sent back to Flutter.
     *
     * @param message the message {@link ByteBuffer} payload, possibly null.
     * @param reply A {@link BinaryReply} used for submitting a reply back to Flutter.
     */
    @UiThread
    void onMessage(@Nullable ByteBuffer message, @NonNull BinaryReply reply);
  }

从注释中可以看到,处理消息是在onMessagel来完成,而且必须对所有接受到的消息要有回应,如果没有这么做会导致Flutter需要回应的地方始终收不到回应,从而使Flutter层停留在某个状态,消息可能为空,BinaruReply用于回应Flutter层。onMessage在收到Flutter消息的时候被调用,可以看到onMessage是在UI线程中执行的.

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

上面就是onMessage调用的地方。那么我们常用的MethodChannel的onMethodCall是在哪里调用的。上面说到了,接收到Flutter的消息是在BinaryMessageHandler的onMessage方法中处理的,而MethodChannel的内部类正好IncomingMethodCallHandler实现这个方法:

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

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

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

    private String getStackTrace(Exception e) {
      Writer result = new StringWriter();
      e.printStackTrace(new PrintWriter(result));
      return result.toString();
    }
  }

前面讲到,Native接收到消息后,需要给Flutter回复消息,在上面截取的代码中其实已经有这个实现了:

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


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

 @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方法。

本文只大概讲了怎么流程,没涉及到具体用法,具体用法可参考:https://book.flutterchina.club/chapter12/android_implement.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

释汐宇辰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值