上次从一个路径插件看来一下Flutter中如何调用iOS和Android中的方法以及平台如何返回值给Flutter框架。今天就来详细讲讲MethodChannel是如何连同另一个世界的。
1.从吐司弹框开始说起(Android端/Java)
想要达成的效果是这样使用可以弹出一个时间较长的吐司
这个示例要讲述的是Flutter中如何向平台传递参数
var show = RaisedButton( onPressed: () { IaToast.show(msg: "hello",type: Toast.LENGTH_LONG); }, child: Text("点击弹吐司"), );
1.1.Flutter/Dart端
定义一个
IaToast
的吐司类,根据枚举类型使用MethodChannel调用原生方法
import 'package:flutter/services.dart'; ///吐司类型 [LENGTH_SHORT]短时间,[LENGTH_LONG]长时间 enum Toast { LENGTH_SHORT, LENGTH_LONG } ///吐司类 class IaToast { static const MethodChannel _channel =//方法渠道名 const MethodChannel('www.toly1994.com.flutter_journey.toast'); static show(//静态方法显示吐司 {String msg, Toast type = Toast.LENGTH_SHORT}) { if (type == Toast.LENGTH_SHORT) { _channel.invokeMethod('showToast', {//渠道对象调用方法 "msg": msg, "type": 0, }); } else { _channel.invokeMethod('showToast', { "msg": msg, "type": 1, }); } } }
1.2:Android/Java端
通过FlutterView和渠道名可以获取MethodChannel对象,对其进行方法调用监听
其中的两个回调参数分别储存着方法信息和返回信息。
public class MainActivity extends FlutterActivity { private static final String CHANNEL = "www.toly1994.com.flutter_journey.toast";//渠道名 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); GeneratedPluginRegistrant.registerWith(this); MethodChannel channel = new MethodChannel(getFlutterView(), CHANNEL);//获取渠道 channel.setMethodCallHandler(this::handleMethod);//设置方法监听 } /** * 处理方法回调监听 * @param methodCall 方法的参数相关 * @param result 方法的返回值相关 */ private void handleMethod(MethodCall methodCall, MethodChannel.Result result) { switch (methodCall.method){//根据方法名进行处理 case "showToast": handleToast(this,methodCall);//具体处理 break; default: result.notImplemented(); } } public static void handleToast(Context context,MethodCall methodCall) { String msg=methodCall.argument("msg"); int type=methodCall.argument("type"); Toast.makeText(context, msg, type).show(); } }
1.3:使用效果
这样对应Android端,在Flutter中就可以开心的弹吐司了
var show = RaisedButton(
onPressed: () {
IaToast.show(msg: "hello Flutter", type: Toast.LENGTH_LONG);//使用吐司
},
child: Text("点击弹吐司"),
);
var app = MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('Flutter之旅'),
),
body: show,
),
);
void main() => runApp(app);
也简单的画了一幅Flutter和iOS沟通的图
2.1:创建插件类:
现在来看iOS端如何接受Flutter中的参数,和Android中基本一致,首先要获得渠道
在iOS里FlutterMethodChannel通过渠道标识和FlutterViewController来获取。
有了渠道方法之后,剩下的就几乎一致了,只是语法问题。
通过FlutterMethodCall回调中的call中的arguments值来获取参数,强转成NSDictionary
不过iOS系统并没有直接弹吐司的方法,所以需要自定义吐司。
import UIKit import Flutter @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { public static let channelId="www.toly1994.com.flutter_journey.toast" override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { let controller:FlutterViewController = window.rootViewController as! FlutterViewController let messageChannel = FlutterMethodChannel.init(//获取方法渠道 name: AppDelegate.channelId, binaryMessenger:controller) messageChannel.setMethodCallHandler{(call, result) in self.handle(call,result) } return super.application(application, didFinishLaunchingWithOptions: launchOptions) } public func handle(_ call: FlutterMethodCall,_ result: @escaping FlutterResult) { let args: NSDictionary = call.arguments as! NSDictionary switch call.method { case "showToast": let msg:String = args["msg"] as! String let type:Int = args["type"] as! Int handleToast(msg:msg,type:type) default: result(FlutterMethodNotImplemented) } } public func handleToast(msg: String, type: Int) { Toast.toast(text: msg,type:type) } }
2.2:自定义吐司
使用UILabel和UIButton进行模拟一个吐司框
import UIKit let toastDispalyDuration: CGFloat = 2.0 let toastBackgroundColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.6) class Toast: NSObject { var duration: CGFloat = toastDispalyDuration var contentView: UIButton//内容框 init(text: String) { let rect = text.boundingRect( with: CGSize(width: 250, height: CGFloat.greatestFiniteMagnitude), attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 20)],//该值可调节边距 context: nil) let textLabel = UILabel(//标签 frame: CGRect(x: 0, y: 0, width: rect.size.width + 40, height: rect.size.height + 20)) textLabel.backgroundColor = UIColor.clear textLabel.textColor = UIColor.white textLabel.textAlignment = .center textLabel.font = UIFont.systemFont(ofSize: 16) textLabel.text = text textLabel.numberOfLines = 0 contentView = UIButton(type: .roundedRect) contentView.frame=CGRect(x: 0, y: 0, width: textLabel.frame.size.width, height: textLabel.frame.size.height) contentView.layer.cornerRadius = 15 contentView.backgroundColor = toastBackgroundColor contentView.addSubview(textLabel) contentView.autoresizingMask = UIView.AutoresizingMask.flexibleWidth super.init() contentView.addTarget(self, action: #selector(toastTaped), for: .touchDown) NotificationCenter.default.addObserver(self, selector: #selector(toastTaped), name: UIDevice.orientationDidChangeNotification, object: UIDevice.current) } @objc func toastTaped() { self.hideAnimation() } func deviceOrientationDidChanged(notify: Notification) { self.hideAnimation() } @objc func dismissToast() { contentView.removeFromSuperview() } func setDuration(duration: CGFloat) { self.duration = duration } func showAnimation() { UIView.beginAnimations("show", context: nil) UIView.setAnimationCurve(UIView.AnimationCurve.easeIn) UIView.setAnimationDuration(0.3) contentView.alpha = 1.0 UIView.commitAnimations() } @objc func hideAnimation() { UIView.beginAnimations("hide", context: nil) UIView.setAnimationCurve(UIView.AnimationCurve.easeOut) UIView.setAnimationDelegate(self) UIView.setAnimationDidStop(#selector(dismissToast)) UIView.setAnimationDuration(0.3) contentView.alpha = 0.0 UIView.commitAnimations() } func showFromBottomOffset(bottom: CGFloat) { let window: UIWindow = UIApplication.shared.windows.last! contentView.center = CGPoint(x: window.center.x, y: window.frame.size.height - (bottom + contentView.frame.size.height/2)) window.addSubview(contentView) self.showAnimation() self.perform(#selector(hideAnimation), with: nil, afterDelay: TimeInterval(duration)) } class func toast(text: String,type: Int) { let toast = Toast(text: text) var duration=0 if type==0 {duration=1}else{duration=3} toast.setDuration(duration: CGFloat(duration)) toast.showFromBottomOffset(bottom: 60) } }
现在应该对MethodChannel有了一个感性的认知了,它可以连通Flutter框架和平台。
3.Flutter视角看MethodChannel
在Flutter中MethodChannel是一个Dart类,
处于flutter/lib/src/services/platform_channel.dart
文件中
3.1:MethodChannel的成员
其中有三个成员变量,我们在使用时只是传来一个字符串而已,其实还有两个是默认的
codec是消息的编解码器,类型MethodCodec,默认是StandardMethodCodec
binaryMessenger是二进制信使,类型BinaryMessenger,默认是defaultBinaryMessenger
class MethodChannel { const MethodChannel(this.name, [this.codec = const StandardMethodCodec(), this.binaryMessenger = defaultBinaryMessenger ]) : assert(name != null), assert(binaryMessenger != null), assert(codec != null); final String name; final MethodCodec codec; final BinaryMessenger binaryMessenger;
3.2:MethodChannel的invokeMethod方法
首先它是一个异步方法,传递方法名和参数,可以看出首先由codec编码MethodCall对象
然后通过binaryMessenger去发送信息,获取的结构是一个字节数据,
如果结果非空,通过codec去解码,然后进行返回,可见这个泛型便是期望的结果类型
Future<T> invokeMethod<T>(String method, [ dynamic arguments ]) async { assert(method != null); final ByteData result = await binaryMessenger.send( name, codec.encodeMethodCall(MethodCall(method, arguments)), ); if (result == null) { throw MissingPluginException('No implementation found for method $method on channel $name'); } final T typedResult = codec.decodeEnvelope(result); return typedResult; }
3.3:MethodCodec类及StandardMethodCodec
MethodCodec是一个抽象接口,定义了编解码的方法,所以具体逻辑还要看它的实现类
MethodCodec有两个实现类StandardMethodCodec和JSONMethodCodec
abstract class MethodCodec { ByteData encodeMethodCall(MethodCall methodCall); MethodCall decodeMethodCall(ByteData methodCall); dynamic decodeEnvelope(ByteData envelope); ByteData encodeSuccessEnvelope(dynamic result); ByteData encodeErrorEnvelope({ @required String code, String message, dynamic details }); }
StandardMethodCodec的编码方法
可以看出StandardMethodCodec对MethodCall的编码是通过messageCodec实现的
messageCodec是StandardMessageCodec对象,其中的writeValue是编码的核心方法
将方法名和参数根据类型放入buffer中,从而将这些方法信息存储其中。
class StandardMethodCodec implements MethodCodec { const StandardMethodCodec([this.messageCodec = const StandardMessageCodec()]); @override ByteData encodeMethodCall(MethodCall call) { final WriteBuffer buffer = WriteBuffer(); messageCodec.writeValue(buffer, call.method); messageCodec.writeValue(buffer, call.arguments); return buffer.done(); } //略... } ---->[StandardMessageCodec#writeValue]---- 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 int) { if (-0x7fffffff - 1 <= value && value <= 0x7fffffff) { buffer.putUint8(_valueInt32); buffer.putInt32(value); } else { buffer.putUint8(_valueInt64); buffer.putInt64(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); } }
3.5:BinaryMessages发送信息
BinaryMessenger是一个抽象接口,默认使用的实现了是
defaultBinaryMessenger
_sendPlatformMessage方法进行对平台发送信息
const BinaryMessenger defaultBinaryMessenger = _DefaultBinaryMessenger._(); ---->[BinaryMessenger]---- abstract class BinaryMessenger { const BinaryMessenger(); Future<void> handlePlatformMessage(String channel, ByteData data, ui.PlatformMessageResponseCallback callback); Future<ByteData> send(String channel, ByteData message); void setMessageHandler(String channel, Future<ByteData> handler(ByteData message)); void setMockMessageHandler(String channel, Future<ByteData> handler(ByteData message)); } ---->[_DefaultBinaryMessenger]---- @override Future<ByteData> send(String channel, ByteData message) { final MessageHandler handler = _mockHandlers[channel]; if (handler != null) return handler(message); return _sendPlatformMessage(channel, message); }
_sendPlatformMessage
这里使用Window对象进行信息发送,最终调用的是
Window_sendPlatformMessage
的native方法
final Window window = Window._();
Future<ByteData> _sendPlatformMessage(String channel, ByteData message) {
final Completer<ByteData> completer = Completer<ByteData>();
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;
}
---->[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';
在Android中MethodChannel是一个Java类,处于
io.flutter.plugin.common
包
主要的成员变量也是三位messenger,name和codec,在构造方法中需要传入BinaryMessenger
默认的MethodCodec是StandardMethodCodec.INSTANCE
public final class MethodChannel { private static final String TAG = "MethodChannel#"; private final BinaryMessenger messenger; private final String name; private final MethodCodec codec; public MethodChannel(BinaryMessenger messenger, String name) { this(messenger, name, StandardMethodCodec.INSTANCE); }
4.1:设置方法监听处理器
监听器是设置在了messenger的身上,如果监听器非空会使用
IncomingMethodCallHandler
messenger需要的监听器的类型是BinaryMessenger.BinaryMessageHandler
,所以关系如下
public void setMethodCallHandler(@Nullable MethodChannel.MethodCallHandler handler) { this.messenger.setMessageHandler(this.name, handler == null ? null : new MethodChannel.IncomingMethodCallHandler(handler)); } ---->[BinaryMessenger]---- public interface BinaryMessenger { @UiThread void setMessageHandler(@NonNull String var1, @Nullable BinaryMessenger.BinaryMessageHandler var2);
4.2:IncomingMethodCallHandler与回调参数的生成
IncomingMethodCallHandler实现了BinaryMessageHandler接口,必然实现其接口方法
onMessage中需要回调了ByteBuffer的方法字节信息以及BinaryReply对象
回调中的MethodCall对象是通过codec将字节信息解码生成的
MethodChannel.Result是一个接口,有三个接口方法,这里直接new对象并实现三个方法
通过codec编码success传入的对象,后通过reply对象的reply将返回值传给Flutter端
private final class IncomingMethodCallHandler implements BinaryMessageHandler {
private final MethodChannel.MethodCallHandler handler;
IncomingMethodCallHandler(MethodChannel.MethodCallHandler handler) {
this.handler = handler;
}
@UiThread
public void onMessage(ByteBuffer message, final BinaryReply reply) {
MethodCall call = MethodChannel.this.codec.decodeMethodCall(message);
try {
this.handler.onMethodCall(call, new MethodChannel.Result() {
public void success(Object result) {
reply.reply(MethodChannel.this.codec.encodeSuccessEnvelope(result));
}
public void error(String errorCode, String errorMessage, Object errorDetails) {
reply.reply(MethodChannel.this.codec.encodeErrorEnvelope(errorCode, errorMessage, errorDetails));
}
public void notImplemented() {
reply.reply((ByteBuffer)null);
}
});
} catch (RuntimeException var5) {
Log.e("MethodChannel#" + MethodChannel.this.name, "Failed to handle method call", var5);
reply.reply(MethodChannel.this.codec.encodeErrorEnvelope("error", var5.getMessage(), (Object)null));
}
}
到这里一切矛头指向BinaryMessenger,它是一个接口,定义了发生信息的三个方法。
和信息发送相关的类有四个:
public interface BinaryMessenger { @UiThread void send(@NonNull String var1, @Nullable ByteBuffer var2); @UiThread void send(@NonNull String var1, @Nullable ByteBuffer var2, @Nullable BinaryMessenger.BinaryReply var3); @UiThread void setMessageHandler(@NonNull String var1, @Nullable BinaryMessenger.BinaryMessageHandler var2); public interface BinaryReply { @UiThread void reply(@Nullable ByteBuffer var1); } public interface BinaryMessageHandler { @UiThread void onMessage(@Nullable ByteBuffer var1, @NonNull BinaryMessenger.BinaryReply var2); } }
5.1:FlutterView
我们在创建MethodChannel的时候传入的是getFlutterView()
追踪一下可以看到返回的是一个FlutterView,这也就说明FlutterView实现了BinaryMessenger
所以可以从实现的方法入手,最终发现是调用mNativeView的方法,其为FlutterNativeView类型
MethodChannel channel = new MethodChannel(getFlutterView(), CHANNEL);//获取渠道 ---->[FlutterActivity]---- public FlutterView getFlutterView() { return this.viewProvider.getFlutterView(); } ---->[FlutterView]---- public interface Provider { FlutterView getFlutterView(); } ---->[FlutterView]---- @UiThread public void send(String channel, ByteBuffer message) { this.send(channel, message, (BinaryReply)null); } @UiThread public void send(String channel, ByteBuffer message, BinaryReply callback) { if (!this.isAttached()) { Log.d("FlutterView", "FlutterView.send called on a detached view, channel=" + channel); } else { this.mNativeView.send(channel, message, callback); } }
5.2:FlutterNativeView与DartExecutor
FlutterNativeView调用dartExecutor的方法,其为DartExecutor类型
在构造方法中创建了FlutterJNI对象来创建DartExecutor,
DartExecutor中通过DartMessenger对象messenger发送,这些DartMessenger跑不掉了
private final DartExecutor dartExecutor; private final FlutterJNI mFlutterJNI; public FlutterNativeView(@NonNull Context context) { this(context, false); } public FlutterNativeView(@NonNull Context context, boolean isBackgroundView) { this.mContext = context; this.mPluginRegistry = new FlutterPluginRegistry(this, context); this.mFlutterJNI = new FlutterJNI(); this.mFlutterJNI.setRenderSurface(new FlutterNativeView.RenderSurfaceImpl()); this.dartExecutor = new DartExecutor(this.mFlutterJNI); ---->[FlutterNativeView]---- @UiThread public void send(String channel, ByteBuffer message) { this.dartExecutor.send(channel, message); } @UiThread public void send(String channel, ByteBuffer message, BinaryReply callback) { if (!this.isAttached()) { Log.d("FlutterNativeView", "FlutterView.send called on a detached view, channel=" + channel); } else { this.dartExecutor.send(channel, message, callback); } } ---->[DartExecutor]---- @UiThread public void send(@NonNull String channel, @Nullable ByteBuffer message) { this.messenger.send(channel, message, (BinaryReply)null); } @UiThread public void send(@NonNull String channel, @Nullable ByteBuffer message, @Nullable BinaryReply callback) { this.messenger.send(channel, message, callback); }
5.3:DartMessenger
DartMessenger通过flutterJNI.dispatchPlatformMessage发送信息
最终到nativeDispatchPlatformMessage一个native方法,
然后那些C++里见不得人的勾当这里就不说了,有机会再细细道来。
@UiThread public void send(@NonNull String channel, @NonNull ByteBuffer message) { Log.v("DartMessenger", "Sending message over channel '" + channel + "'"); this.send(channel, message, (BinaryReply)null); } public void send(@NonNull String channel, @Nullable ByteBuffer message, @Nullable BinaryReply callback) { Log.v("DartMessenger", "Sending message with callback over channel '" + channel + "'"); int replyId = 0; if (callback != null) { replyId = this.nextReplyId++; this.pendingReplies.put(replyId, callback); } if (message == null) { this.flutterJNI.dispatchEmptyPlatformMessage(channel, replyId); } else { this.flutterJNI.dispatchPlatformMessage(channel, message, message.position(), replyId); } } @UiThread public void dispatchPlatformMessage(@NonNull String channel, @Nullable ByteBuffer message, int position, int responseId) { this.ensureRunningOnMainThread(); if (this.isAttached()) { this.nativeDispatchPlatformMessage(this.nativePlatformViewId, channel, message, position, responseId); } else { Log.w("FlutterJNI", "Tried to send a platform message to Flutter, but FlutterJNI was detached from native C++. Could not send. Channel: " + channel + ". Response ID: " + responseId); } }