前言
在flutter开发当中,难免有一些功能需要调用到原生,例如扫码,蓝牙等跟硬件打交道的功能。虽然说这些在网上有第三方的flutter库可以使用,但是架不住我们的产品需求特立独行啊。如果想要个性化定制扫码界面怎么办?如果想要个性化的交互,例如想停留在扫码界面连续扫码怎么办?如果想要在扫码界面扫描出条码然后连接蓝牙打印机打印怎么办?很难找到符合我现在这些需求的库,所以还是自己动手,丰衣足食吧。
Flutter 调用Android原生
通过MethodChannel来实现。MethodChannel相当于Flutter端和原生代码的消息通道。flutter调用原生的方法,通过MethodChannel将方法名和参数打包序列化为二级制数据,然后传递给Android端。Android端的插件代码用MethodChannel接受到方法flutter的方法调用后,可以通过方法名来知道到底调用的哪个方法,调用具体的方法实现即可。Android端代码可以通过BinaryMessenger 将方法执行的结果(返回值、报错信息)回传给Flutter端。
-
调用步骤
-
约定好MethodChannel的通道名字。
flutter端
static const MethodChannel _channel = const MethodChannel('com.niimbot.flutter.scan');android端
MethodChannel methodChannel = MethodChannel(flutterPluginBinding.binaryMessenger, "com.niimbot.flutter.scan")注意的是,这个通道名字在flutter和android两端必须相同。
-
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类型的函数结果。
-
编写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端和原生端代码相互访问的桥梁。
-
注册插件
class MainActivity: FlutterActivity() { override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) flutterEngine.plugins.add(FlutterScanPlugin()) } }如果插件是以单独的插件项目创建的,就可以省去这个代码,注册是通过GeneratedPluginRegistrant自动注册,里面会注册
-
在插件类的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端的通信。
-
实现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端会收到函数未实现的报错。
-
使插件里实现和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。
-
-
关键类分析
-
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 实现的代码这里不做展开。
-
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。这里放上一张各个平台类型的对照表:
Dart Java Kotlin OC Swift null null null nil (NSNull when nested) nil bool java.lang.Boolean Boolean NSNumber numberWithBool: NSNumber(value: Bool) int java.lang.Integer Int NSNumber numberWithInt: NSNumber(value: Int32) int, if 32 bits not enough java.lang.Long Long NSNumber numberWithLong: NSNumber(value: Int) double java.lang.Double Double NSNumber numberWithDouble: NSNumber(value: Double) String java.lang.String String NSString String Uint8List byte[] ByteArray FlutterStandardTypedData typedDataWithBytes: FlutterStandardTypedData(bytes: Data) Int32List int[] IntArray FlutterStandardTypedData typedDataWithInt32: FlutterStandardTypedData(int32: Data) Int64List long[] LongArray FlutterStandardTypedData typedDataWithInt64: FlutterStandardTypedData(int64: Data) Float64List double[] DoubleArray FlutterStandardTypedData typedDataWithFloat64: FlutterStandardTypedData(float64: Data) List java.util.ArrayList List NSArray Array Map java.util.HashMap HashMap NSDictionary Dictionary 这里再看看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和原生两者通信的桥梁,负责消息的转发。