Flutter是目前非常流行的跨平台方案,由于它的性能接近于原生应用,因而被越来越多的开发者所采用。既然是跨平台方案,那么久必然存在调用系统功能的需求,在Flutter中,Flutter层与native层的互调,是通过MethodChannel来实现的。下面来简单的分析下Android端调用Flutter的过程。
1 2 3 4 | 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。我们分别看下这种方式的编码方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | 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,同时可以看到只支持基本数据类型及相关集合。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | /** * Writes a type discriminator byte and then a byte serialization of the specified value to the * specified stream. * * <p><center>[wp_ad_camp_2]</center></p><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,也是仅支持基本数据类型及相关集合。解码方式刚好相反,再此就不列出来了。
1 2 3 4 5 6 7 | @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层:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | @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线程中执行,否则会跑出异常:
1 2 3 4 5 6 7 | 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有个内部接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | /** 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线程中执行的.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | @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实现这个方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | 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回复消息,在上面截取的代码中其实已经有这个实现了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | 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