安卓 Flutter Channel 源码解析

Flutter 官方提供三种 Platform Dart 端消息通信方式,他们分别是 MethodChannel BasicMessageChannel EventChannel
MethodChanel :用于传递方法调用, MethodCallHandler 最终必须在 UI 线程通过 result.
success(x) 方法返回结果,返回前自己可以异步新起线程做任意耗时操作。
BasicMessageChannel :用于传递字符串和半结构化的消息。
EventChannel :用于数据流的发送。
Android Platform Channel 定义
public class MethodChannel {
  private final BinaryMessenger messenger;
  private final String name;
  private final MethodCodec codec;
  //......
  private final class IncomingMethodCallHandler implements BinaryMessageHandler {
    private final MethodCallHandler handler;
  }
}

public final class BasicMessageChannel<T> {
  @NonNull private final BinaryMessenger messenger;
  @NonNull private final String name;
  @NonNull private final MessageCodec<T> codec;
  //......
  private final class IncomingMessageHandler implements BinaryMessageHandler {
    private final MessageHandler<T> handler;
  }
}

public final class EventChannel {
  private final BinaryMessenger messenger;
  private final String name;
  private final MethodCodec codec;
  //......
  private final class IncomingStreamRequestHandler implements BinaryMessageHandler {
    private final StreamHandler handler;
  }
}

  MethodChannelBasicMessageChannelEventChannel 结构来看,都是由namemessengercodec三个属性字段组成

name:String 类型,唯一标识符代表 Channel 的名字,因为一个 Flutter 应用中存在多个                         Channel,每个 Channel 在创建时必须指定一个独一无二的 name 作为标识
messenger:BinaryMessenger 类型,充当信使邮递员角色,消息的发送与接收工具人。
codec:MethodCodec 或MessageCodec<T>类型,充当消息的编解码器。

重点逻辑是messenger字段,看下BinaryMessenger定义

public interface BinaryMessenger {

   public interface TaskQueue {}

  @UiThread
  void send(@NonNull String channel, @Nullable ByteBuffer message, @Nullable BinaryReply callback);


  @UiThread
  void setMessageHandler(@NonNull String channel, @Nullable BinaryMessageHandler handler);

  interface BinaryMessageHandler {
  
    @UiThread
    void onMessage(@Nullable ByteBuffer message, @NonNull BinaryReply reply);
  }

   */
  interface BinaryReply {
   
    void reply(@Nullable ByteBuffer reply);
  }

}
看下DartMessenger实现类
class DartMessenger implements BinaryMessenger, PlatformMessageHandler {

  @NonNull private final Map<String, HandlerInfo> messageHandlers = new HashMap<>();
  
@Override
  public void setMessageHandler(
      @NonNull String channel,
      @Nullable BinaryMessenger.BinaryMessageHandler handler,
      @Nullable TaskQueue taskQueue) {
    ......

    List<BufferedMessageInfo> list;
    synchronized (handlersLock) {
      messageHandlers.put(channel, new HandlerInfo(handler, dartMessengerTaskQueue));
    ......
    }
    ......
    }
  }

  @Override
  public void handleMessageFromDart(
      @NonNull String channel, @Nullable ByteBuffer message, int replyId, long messageData) {

    synchronized (handlersLock) {
      handlerInfo = messageHandlers.get(channel);
      messageDeferred = (enableBufferingIncomingMessages.get() && handlerInfo == null);
     ......
    if (!messageDeferred) {
      dispatchMessageToQueue(channel, handlerInfo, message, replyId, messageData);
    }
  }

  private void dispatchMessageToQueue(
      @NonNull String channel,
      @Nullable HandlerInfo handlerInfo,
      @Nullable ByteBuffer message,
      int replyId,
      long messageData) {
    // Called from any thread.
    final DartMessengerTaskQueue taskQueue = (handlerInfo != null) ? handlerInfo.taskQueue : null;
    TraceSection.beginAsyncSection("PlatformChannel ScheduleHandler on " + channel, replyId);
    Runnable myRunnable =
        () -> {
          TraceSection.endAsyncSection("PlatformChannel ScheduleHandler on " + channel, replyId);
          try (TraceSection e =
              TraceSection.scoped("DartMessenger#handleMessageFromDart on " + channel)) {
            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);
          }
        };
    final DartMessengerTaskQueue nonnullTaskQueue =
        taskQueue == null ? platformTaskQueue : taskQueue;
    nonnullTaskQueue.dispatch(myRunnable);
  }


  private void invokeHandler(
      @Nullable HandlerInfo handlerInfo, @Nullable ByteBuffer message, final int replyId) {
    // Called from any thread.
    if (handlerInfo != null) {
      try {
        Log.v(TAG, "Deferring to registered handler to process message.");
        handlerInfo.handler.onMessage(message, new Reply(flutterJNI, replyId));
      } catch (Exception ex) {
        Log.e(TAG, "Uncaught exception in binary message listener", ex);
        flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
      } catch (Error err) {
        handleError(err);
      }
    } else {
      Log.v(TAG, "No registered handler for message. Responding to Dart with empty reply message.");
      flutterJNI.invokePlatformMessageEmptyResponseCallback(replyId);
    }
  }
}

 上面整体流程先注册handler,以channel name为key缓存到messageHandlers,等待

handleMessageFromDart(String channel, ByteBuffer message, int replyId, long messageData)
从Flutter端收到数据后,根据channel参数从messageHandlers获取到对应HandlerInfo对象,然后执行该队向的onMessage方法 handlerInfo.handler.onMessage(message, new Reply(flutterJNI, replyId));

这里可以先看下Reply对象,创建Reply对象保存replyId字段,后面用来回复Flutter。

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

通过flutterJNI调用invokePlatformMessageResponseCallback发送到Flutter端

现在回过头继续跟踪setMessageHandler流程,前面提到channel有MethodChannelEventChannelBasicMessageChannel,分别看下它们是如何注册handler的,三种类型有啥不一样。

先看MethodChannel类型

public class MethodChannel {
  private static final String TAG = "MethodChannel#";

  private final BinaryMessenger messenger;
  private final String name;
  private final MethodCodec codec;
  private final BinaryMessenger.TaskQueue taskQueue;

 ......

  @UiThread
  public void setMethodCallHandler(final @Nullable MethodCallHandler handler) {
    // We call the 2 parameter variant specifically to avoid breaking changes in
    // mock verify calls.
    // See https://github.com/flutter/flutter/issues/92582.
    if (taskQueue != null) {
      messenger.setMessageHandler(
          name, handler == null ? null : new IncomingMethodCallHandler(handler), taskQueue);
    } else {
      messenger.setMessageHandler(
          name, handler == null ? null : new IncomingMethodCallHandler(handler));
    }
  }
......
}

注册的类型为IncomingMethodCallHandler,从Incoming名字看就是“接听”,用来处理Flutter端发送的数据。

  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, Log.getStackTraceString(e)));
      }
    }
  }

onMessage方法通过codec编码器将字节数组message转化成MethodCall对象,MethodCall对象里面包括方法名称和方法携带参数

再看EventChannel类型

public final class EventChannel {
  private static final String TAG = "EventChannel#";

  private final BinaryMessenger messenger;
  private final String name;
  private final MethodCodec codec;
  @Nullable private final BinaryMessenger.TaskQueue taskQueue;

  ......
  public void setStreamHandler(final StreamHandler handler) {
    // We call the 2 parameter variant specifically to avoid breaking changes in
    // mock verify calls.
    // See https://github.com/flutter/flutter/issues/92582.
    if (taskQueue != null) {
      messenger.setMessageHandler(
          name, handler == null ? null : new IncomingStreamRequestHandler(handler), taskQueue);
    } else {
      messenger.setMessageHandler(
          name, handler == null ? null : new IncomingStreamRequestHandler(handler));
    }
  }
......
}

注册的类型为IncomingStreamRequestHandler,从Incoming名字看也是带“接听”,同样是用来处理Flutter端发送的数据。

 private final class IncomingStreamRequestHandler implements BinaryMessageHandler {
    private final StreamHandler handler;
    private final AtomicReference<EventSink> activeSink = new AtomicReference<>(null);

    IncomingStreamRequestHandler(StreamHandler handler) {
      this.handler = handler;
    }

    @Override
    public void onMessage(ByteBuffer message, final BinaryReply reply) {
      final MethodCall call = codec.decodeMethodCall(message);
      if (call.method.equals("listen")) {
        onListen(call.arguments, reply);
      } else if (call.method.equals("cancel")) {
        onCancel(call.arguments, reply);
      } else {
        reply.reply(null);
      }
    }
}

IncomingStreamRequestHandler 的onMessage方法通过使用codec编码器将字节数组转成MethodCall对象,可以看到该对象只有listencancel两个方法,listen标识监听,cancel标识取消监听

最后再看BasicMessageChannel

public final class BasicMessageChannel<T> {
  private static final String TAG = "BasicMessageChannel#";
  public static final String CHANNEL_BUFFERS_CHANNEL = "dev.flutter/channel-buffers";

  @NonNull private final BinaryMessenger messenger;
  @NonNull private final String name;
  @NonNull private final MessageCodec<T> codec;
  @Nullable private final BinaryMessenger.TaskQueue taskQueue;

......
  public void setMessageHandler(@Nullable final MessageHandler<T> handler) {
    // We call the 2 parameter variant specifically to avoid breaking changes in
    // mock verify calls.
    // See https://github.com/flutter/flutter/issues/92582.
    if (taskQueue != null) {
      messenger.setMessageHandler(
          name, handler == null ? null : new IncomingMessageHandler(handler), taskQueue);
    } else {
      messenger.setMessageHandler(
          name, handler == null ? null : new IncomingMessageHandler(handler));
    }
  }

......
}

注册的类型为IncomingMessageHandler,名字依然带Incoming,同样是用来处理Flutter端发送的数据。

  private final class IncomingMessageHandler implements BinaryMessageHandler {
    private final MessageHandler<T> handler;

    private IncomingMessageHandler(@NonNull MessageHandler<T> handler) {
      this.handler = handler;
    }

    @Override
    public void onMessage(@Nullable ByteBuffer message, @NonNull final BinaryReply callback) {
      try {
        handler.onMessage(
            codec.decodeMessage(message),
            new Reply<T>() {
              @Override
              public void reply(T reply) {
                callback.reply(codec.encodeMessage(reply));
              }
            });
      } catch (RuntimeException e) {
        Log.e(TAG + name, "Failed to handle message", e);
        callback.reply(null);
      }
    }
  }

onMessage方法里面将message通过codec编码器转成T对应类型。

通过上面的代码跟踪,我们已经明了MethodChannel注册到messageHandlers里面的是IncomingMethodCallHandler,EventChannel注册的是IncomingStreamRequestHandler,BasicMessageChannel注册的是IncomingMessageHandler

MethodChannelIncomingMethodCallHandler
EventChannelIncomingStreamRequestHandler
BasicMessageChannelIncomingMessageHandler

我们继续跟踪IncomingMethodCallHandler,确认它是如何处理Flutter的方法调用

我们以官方的MouseCursorChannel为例,确认逻辑细节

public class MouseCursorChannel {
  private static final String TAG = "MouseCursorChannel";

  @NonNull public final MethodChannel channel;
  @Nullable private MouseCursorMethodHandler mouseCursorMethodHandler;

  public MouseCursorChannel(@NonNull DartExecutor dartExecutor) {
    channel = new MethodChannel(dartExecutor, "flutter/mousecursor", StandardMethodCodec.INSTANCE);
    channel.setMethodCallHandler(parsingMethodCallHandler);
  }

  /**
   * Sets the {@link MouseCursorMethodHandler} which receives all events and requests that are
   * parsed from the underlying platform channel.
   */
  public void setMethodHandler(@Nullable MouseCursorMethodHandler mouseCursorMethodHandler) {
    this.mouseCursorMethodHandler = mouseCursorMethodHandler;
  }

  @NonNull
  private final MethodChannel.MethodCallHandler parsingMethodCallHandler =
      new MethodChannel.MethodCallHandler() {
        @Override
        public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
          if (mouseCursorMethodHandler == null) {
            // If no explicit mouseCursorMethodHandler has been registered then we don't
            // need to forward this call to an API. Return.
            return;
          }

          final String method = call.method;
          Log.v(TAG, "Received '" + method + "' message.");
          try {
            // More methods are expected to be added here, hence the switch.
            switch (method) {
              case "activateSystemCursor":
                @SuppressWarnings("unchecked")
                final HashMap<String, Object> data = (HashMap<String, Object>) call.arguments;
                final String kind = (String) data.get("kind");
                try {
                  mouseCursorMethodHandler.activateSystemCursor(kind);
                } catch (Exception e) {
                  result.error("error", "Error when setting cursors: " + e.getMessage(), null);
                  break;
                }
                result.success(true);
                break;
              default:
            }
          } catch (Exception e) {
            result.error("error", "Unhandled error: " + e.getMessage(), null);
          }
        }
      };

  @VisibleForTesting
  public void synthesizeMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
    parsingMethodCallHandler.onMethodCall(call, result);
  }

  public interface MouseCursorMethodHandler {
    // Called when the pointer should start displaying a system mouse cursor
    // specified by {@code shapeCode}.
    public void activateSystemCursor(@NonNull String kind);
  }
}

可以看到,真正干活的handler就是parsingMethodCallHandler,它里面复写了onMethodCall方法,从源码可以看出支持方法名activateSystemCursor,执行完成后通过result.success(true)回调回去,这里的result就是前面的Result


    public void onMessage(ByteBuffer message, final BinaryReply reply) {
      final MethodCall call = codec.decodeMethodCall(message);
      try {
        handler.onMethodCall(
            call,
            new Result() {
              @Override
              public void success(Object result) {
                reply.reply(codec.encodeSuccessEnvelope(result));
              }

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

              @Override
              public void notImplemented() {
                reply.reply(null);
              }
            });
      } catch (RuntimeException e) {
        Log.e(TAG + name, "Failed to handle method call", e);
        reply.reply(
            codec.encodeErrorEnvelopeWithStacktrace(
                "error", e.getMessage(), null, Log.getStackTraceString(e)));
      }
    }

result接收到success回调后,通过reply.reply 回调给Flutter层。

这样就明确了,Android从Flutter端收到数据后,执行完对应方法后,再通过reply通过FlutterJNI发送到Flutter,这样一个调用回路就形成了。

EventChannel和BasicMessageChannel逻辑也是类似,这里就不再赘述,大家可以查阅源码进行分析。

上面的整个流程都是Flutter调用Android原生的方法流程,接下来我们跟踪下Android如何Flutter的方法

我们以NavigationChannel为例,看看它是如何调用的

  public void pushRoute(@NonNull String route) {
    Log.v(TAG, "Sending message to push route '" + route + "'");
    channel.invokeMethod("pushRoute", route);
  }
  @UiThread
  public void invokeMethod(@NonNull String method, @Nullable Object arguments) {
    invokeMethod(method, arguments, null);
  }
  @UiThread
  public void invokeMethod(
      @NonNull String method, @Nullable Object arguments, @Nullable Result callback) {
    messenger.send(
        name,
        codec.encodeMethodCall(new MethodCall(method, arguments)),
        callback == null ? null : new IncomingResultHandler(callback));
  }
  @Override
  public void send(
      @NonNull String channel,
      @Nullable ByteBuffer message,
      @Nullable BinaryMessenger.BinaryReply callback) {
    try (TraceSection e = TraceSection.scoped("DartMessenger#send on " + channel)) {
      Log.v(TAG, "Sending message with callback over channel '" + channel + "'");
      int replyId = nextReplyId++;
      if (callback != null) {
        pendingReplies.put(replyId, callback);
      }
      if (message == null) {
        flutterJNI.dispatchEmptyPlatformMessage(channel, replyId);
      } else {
        flutterJNI.dispatchPlatformMessage(channel, message, message.position(), replyId);
      }
    }
  }

跟踪源码,我们看到最后是通过flutterJNI调用了dispatchPlatformMessage方法发送到了Flutter层。

Android原生MethodChannel整体调用流程如下图

接下来我们根据下MethodChannel里面的codec编码器是如何工作的。

我们可以看到MethodChannel里面的codec是MethodCodec类型。

public interface MethodCodec {

  @NonNull
  ByteBuffer encodeMethodCall(@NonNull MethodCall methodCall);


  @NonNull
  MethodCall decodeMethodCall(@NonNull ByteBuffer methodCall);


  @NonNull
  ByteBuffer encodeSuccessEnvelope(@Nullable Object result);


  @NonNull
  ByteBuffer encodeErrorEnvelope(
      @NonNull String errorCode, @Nullable String errorMessage, @Nullable Object errorDetails);


  @NonNull
  ByteBuffer encodeErrorEnvelopeWithStacktrace(
      @NonNull String errorCode,
      @Nullable String errorMessage,
      @Nullable Object errorDetails,
      @Nullable String errorStacktrace);
  @NonNull
  Object decodeEnvelope(@NonNull ByteBuffer envelope);
}

这里一般使用的是StandardMethodCodec类型。

public final class StandardMethodCodec implements MethodCodec {
  public static final StandardMethodCodec INSTANCE =
      new StandardMethodCodec(StandardMessageCodec.INSTANCE);
  private final StandardMessageCodec messageCodec;

  /** Creates a new method codec based on the specified message codec. */
  public StandardMethodCodec(@NonNull StandardMessageCodec messageCodec) {
    this.messageCodec = messageCodec;
  }

  @Override
  @NonNull
  public ByteBuffer encodeMethodCall(@NonNull MethodCall methodCall) {
    final ExposedByteArrayOutputStream stream = new ExposedByteArrayOutputStream();
    messageCodec.writeValue(stream, methodCall.method);
    messageCodec.writeValue(stream, methodCall.arguments);
    final ByteBuffer buffer = ByteBuffer.allocateDirect(stream.size());
    buffer.put(stream.buffer(), 0, stream.size());
    return buffer;
  }

  @Override
  @NonNull
  public MethodCall decodeMethodCall(@NonNull ByteBuffer methodCall) {
    methodCall.order(ByteOrder.nativeOrder());
    final Object method = messageCodec.readValue(methodCall);
    final Object arguments = messageCodec.readValue(methodCall);
    if (method instanceof String && !methodCall.hasRemaining()) {
      return new MethodCall((String) method, arguments);
    }
    throw new IllegalArgumentException("Method call corrupted");
  }
}

可以看到encodeMethodCall将MethodCall对象编码成字节数组,decodeMethodCall将字节数组转化成MethodCall对象。这里的核心算法是通过messageCodec writeValue和readValue转化。

  protected void writeValue(@NonNull ByteArrayOutputStream stream, @Nullable Object value) {
    if (value == null || value.equals(null)) {
      stream.write(NULL);
    } else if (value instanceof Boolean) {
      stream.write(((Boolean) value).booleanValue() ? TRUE : 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 CharSequence) {
      stream.write(STRING);
      writeBytes(stream, value.toString().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 if (value instanceof float[]) {
      stream.write(FLOAT_ARRAY);
      final float[] array = (float[]) value;
      writeSize(stream, array.length);
      writeAlignment(stream, 4);
      for (final float f : array) {
        writeFloat(stream, f);
      }
    } else {
      throw new IllegalArgumentException(
          "Unsupported value: '" + value + "' of type '" + value.getClass() + "'");
    }
  }

可以看到写入到字节流时,先写入类型,再写入数据,类型固定占一个字节。

  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;
  private static final byte FLOAT_ARRAY = 14;

我们追踪下字符串如何写入

if (value instanceof CharSequence) {
      stream.write(STRING);
      writeBytes(stream, value.toString().getBytes(UTF8));
    } 

-------------------------------------------------------------------------

  protected static final void writeBytes(
      @NonNull ByteArrayOutputStream stream, @NonNull byte[] bytes) {
    writeSize(stream, bytes.length);
    stream.write(bytes, 0, bytes.length);
  }

先写入STRING类型,再写入字符串的长度,再写入字符串内容。

现在我们已经明确了,通过类型+(数据长度)+数据就能将方法进行编码,响应的我们根据这个规则就可以将字节数据进行解码,获得方法名和参数类型。

类型数据长度数据......

从上面也能看到,这里不支持自定义类型,只支持普通数据类型。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/663454.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【基础算法总结】位运算

位运算 1.基础位运算2.常见用法总结3.面试题 01.01. 判定字符是否唯一4.丢失的数字5.两整数之和6.只出现一次的数字 II7.面试题 17.19. 消失的两个数字 点赞&#x1f44d;&#x1f44d;收藏&#x1f31f;&#x1f31f;关注&#x1f496;&#x1f496; 你的支持是对我最大的鼓励…

基于物理的分析模型,用于具有场板结构的GaN HEMT的输入、输出及反向电容

Physics-Based Analytical Model for Input, Output, and Reverse Capacitance of a GaN HEMT With the Field-Plate Structure&#xff08;TPE 17年&#xff09; 摘要 该论文提出了一种分析模型&#xff0c;用于描述带有场板结构的常开型AlGaN/GaN高电子迁移率晶体管&#x…

无意间看到男主眼神,这也太有感觉了吧❗❗

2025即将首播《藏海传》中国大陆剧情/奇幻/古装共40集。 原本&#xff0c;稚奴身为大雍国钦天监监正蒯铎之子&#xff0c;背负着家族血仇。 历经十年沉默与磨砺&#xff0c;他化名为藏海&#xff08;肖战 饰&#xff09;&#xff0c;重返京城。 他凭借卓越的营造技艺和深谙纵…

深入探讨 Android 的 View 显示过程与源码分析

文章目录 1. 探讨 Android 的 View 显示过程1.1. onFinishInflate1.2. onAttachedToWindow1.3. onMeasure1.4. onSizeChanged1.5. onLayout1.6. onDraw 2. 系统代码分析1.1. onFinishInflate1.2. onAttachedToWindow1.3. onMeasure1.4. onSizeChanged1.5. onLayout1.6. onDraw …

Python网页处理与爬虫实战:使用Requests库进行网页数据抓取

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

LDR6328Q:重塑Type-C接口取电体验的新星

在当今日益发展的电子设备市场中&#xff0c;快速、高效的电源管理成为了众多厂商和消费者关注的焦点。LDR6328Q作为一款专为设备端设计的Type-C接口取电芯片&#xff0c;凭借其独特的功能和优势&#xff0c;正在逐步改变我们的电源管理方式。 一、LDR6328Q的核心特点 多协议…

高磷废酸除铝除铁再生技术的实际应用

在化工和金属加工行业中&#xff0c;高磷废酸的处理和再生是一个重要的环保和经济效益问题。废酸中通常含有铝、铁等杂质&#xff0c;这些杂质不仅影响废酸的再利用价值&#xff0c;还可能对环境造成污染。因此&#xff0c;开发高效的高磷废酸除铝除铁再生技术具有重要的实际意…

[排序算法]插入排序+希尔排序全梳理!

目录 1.排序是什么&#xff1f;1.1排序的概念1.2排序运用1.3常见的排序算法 2.插入排序分类3.直接插入排序基本思想具体步骤&#xff1a;动图演示代码实现直接插入排序的特性总结&#xff1a; 4. 希尔排序基本思想具体步骤动图演示代码实现希尔排序的特性总结&#xff1a; 5.总…

阿里云CDN流量被盗刷或CC攻击会怎么样?

最近&#xff0c;一位使用了阿里云CDN的站长向主机吧反应&#xff0c;其域名使用的阿里云CDN不知道是因为被盗刷还是被CC攻击&#xff0c;导致不仅原本帐号上的3T流量包用完了&#xff0c;连帐户也欠了几百元的流量费。 而产生这么多流量的只是晚上睡一觉起来&#xff0c;手机…

全志H616 通过Cedrus和v4l2_request API实现硬件编解码加速(香橙派zero2)

编译安装或加载cedrus驱动模块&#xff0c;加载v4l2-mem2mem Sunxi-Cedrus 致力于为全志 SoC 提供硬件加速的视频解码和编码支持&#xff0c;并将其引入主线 Linux 内核。此外&#xff0c;还为典型的基于 GNU/Linux 的系统提供了与内核驱动程序接口的其他用户空间组件。 Sunx…

调节效应多元统计回归

什么是调节效应&#xff0c;给个例子说明一下: 背景 假设我们有一个国家的经济数据&#xff0c;我们希望研究产业数字化是否调节了环境规制对产业结构调整的影响。 步骤 1. 假设检验 原假设 (H0)&#xff1a; 产业数字化对环境规制与产业结构调整之间的关系没有调节作用。…

浏览器提示413 Request Entity Too Large

1 问题 2 解决 2.1 后端java配置 2.2 Nginx配置

【Git篇 二】idea中使用git合并分支(拉取分支)

idea中使用git合并分支 前言idea使用git合并分支1) 将主分支&#xff08;master&#xff09;更新到自己的分支&#xff08;dev&#xff09;① checkout到自己分支② 目标分支&#xff08;dev&#xff09;更新到当前分支&#xff08;dev_KC240524&#xff09;③ 当前分支出现“绿…

提升B端图表设计技能:教程分享

图表是数据可视化的常用表现形式&#xff0c;是对数据的二次加工&#xff0c;可以帮助我们理解数据、洞悉数据背后的真相&#xff0c;让我们更好地适应这个数据驱动的世界。本期就来带大家学习图表的设计及构成&#xff0c;帮助大家更好的理解图表设计。 设计教程源文件http:/…

Keras深度学习框架实战(1):图像分类识别

1、绪论 1.1 图像分类的定义 图像分类是计算机视觉领域中的一项基本任务&#xff0c;其定义是将输入图像分配给预定义类别中的一个或多个。具体来说&#xff0c;图像分类系统接受一个图像作为输入&#xff0c;并输出一个或多个类别标签&#xff0c;这些标签描述了图像中的内容…

华为设备RIP基础路由实验

华为设备RIP基础路由实验 实验拓扑&#xff1a; RIP&#xff1a;距离矢量的动态路由&#xff0c;路由通信有方向&#xff0c;度量值metric取值范围&#xff08;1-16&#xff09;16表示目标主机不可达。 路由的版本分为&#xff1a;RIPV1&#xff08;广播通信目标地址是255.255…

Mysql树形结构递归查询

Mysql树形结构递归查询 1.表的基础数据2.基础查询语句3.Mysql8递归查询 1.表的基础数据 类似于这种三级目录&#xff1a; – 1&#xff1a;根结点 – 1-1至1-11 – 1-1-1 至1-1-10 2.基础查询语句 可以使用内联查询inner join去查询&#xff1a; SELECTone.id o…

如何做好流程优化?看这里的目的、原则和方法

流程管理的本质是通过构造卓越的业务流程让流程增值&#xff0c;为客户创造真正的价值。 但卓越的业务流程并不是一蹴而就的&#xff0c;有一个过程&#xff0c;这个过程就是业务流程和流程管理体系不断优化提升的过程&#xff08;可以参照流程成熟度评价模型&#xff09;。 …

React组件通信——兄弟组件

兄弟组件通信 方法一&#xff1a;状态提升 子组件先将数据传递到父组件&#xff0c;父组件再把数据传到另一个子组件中。 import { useState } from "react"; // 定义A组件&#xff0c;向B组件发送数据 function A({ onGetMsg }) {const name "this is A na…

【机器学习】Adaboost: 强化弱学习器的自适应提升方法

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 Adaboost: 强化弱学习器的自适应提升方法引言Adaboost基础概念弱学习器与强学习…