一、flutter使用platform-channels制作插件是否是一种完美的体验?
flutter的优势在于非常方便构建UI,而且跑起来在两个平台(Android,IOS)上表现几乎完全一样,而且,性能看起来似乎还可以。 但是有一个痛点,那就是,当需要获取平台相关的一些属性的时候,难题就来了,根本就没有这样的api给你调用。不过,值得高兴且悲哀的是:google给开发者提供了一种折中的方式,那就是使用做一个插件,来实现我们可能遇到的一些需求。
为什么说值得高兴?值得高兴是因为,最终这个问题有一个解决的办法,不至于噶皮了,没办法绕过。那么,有为什么说悲哀呢?很简单,如果你是一个android开发者,你实现android的部分没有什么问题,但是实现IOS部分,你找谁去,没人是不是得学一学。
总体来说,个人也是觉得这种体验并不算太好,加上flutter社区目前可供使用的插件比较少,可能会导致很多开发者对flutter望而止步。
二、作为一个追求技术的人,我们是不是还是要躺一躺这个坑呢?
是的,佛说:“我不入地狱谁入地狱”,总有第一个吃螃蟹的人,你已经错过了第一个,难躺的坑别人已经躺过了,难道你还不试一试吗?反正,我下面是要试一试了。 那么,在尝试写插件时,我们想一想,我们为什么需要写插件,不写插件难道就不能实现么?是的,还真是,比如,有一下场景,我们就不得不写插件。 1、比如,我们要使用腾讯云上面的云通信,诶,这个就悲剧了,你去它官网找一下,他没有提供flutter版本的,而且社区,目前应该还没有人共享,估计已经有人实现了,但是还是私有的。 2、比如,官方提到的,获取手机电量,充电状态,网络制式状态,等等等等。 3、bugly等错误上报。。 4、推送。。 好,不在举例了,聪明的你已经发现了,这些基本上都和UI无关的一些库,一些sdk,是的,不基于这些玩意,你有时间,很多一些功能似乎你也可以实现,比如: IM功能,实际上,你完全可以自己实现一套,配合前后台。but,你要多久呢?1个月,2个月?是否值得这个成本呢?
总结:看来platform-channels这趟浑水,是有必要趟一趟的。
三、platform-channels能做什么?
image.png
嗯,这里很无耻的盗图了,这个图也是话的够TM简洁的,他是说,通过MethodChannel
,你就能够调用不论是android,还是ios那边的平台相关的api,或者第三方库。
嗯,总结一下,就是通过MethodChannel
调用平台或库,拿到返回结果。
试着想一想,仅仅是这样,那够么?回答,肯定是不够的,比如,一个第三方库是一个server
,我这里说server
可能有点不准,那你就理解为能够不定期向外发送消息的模块,或者,你就干脆理解为IM或者推送吧。 那么,怎么做呢?我通过MethodChannel
传递一个Listener
过去,嗯,这种非常常规的观察者模式,多么easy啊?but可行么?很遗憾,这不行,为什么?
我们来了解一下flutter端调用MethodChannel
的方式
FutureimLogin(int appid, String identifier, String sig) async { return await _methodChannel.invokeMethod("im_login", { 'sdkAppId': appid, 'identifier': identifier, 'userSig': sig }); }复制代码
然后,我们看看MethodChannel.MethodCallHandler
的实现实例那边解析参数的方式
if (call.method.equals("im_login")) { int appid = call.argument("sdkAppId"); String identifier = call.argument("identifier"); String userSig = call.argument("userSig");复制代码
然后,你想在在flutter这端定义一个Listener
,或者你直接使用ValueChanged
,
abstract class MessageListener{ void onMessage(Listmessage);}/// Signature for callbacks that report that an underlying value has changed.////// See also [ValueSetter].typedef void ValueChanged (T value);复制代码
那么,invokeMethod是这样的了,对么?
FutureimLogin(int appid, String identifier, String sig, ValueChanged callBack) async { return await _methodChannel.invokeMethod("im_login", { 'sdkAppId': appid, 'identifier': identifier, 'userSig': sig, 'callback':callBack }); }复制代码
然而,在plugin实现那边,请问你如何转型?这边是已经不是dart那一套了,如何知道你是什么类型呢?
image.png
那么,正确的实现方式是什么呢?
三、认识EventChannel
EventChannel才是解决上面问题的办法,那么,EventChannel该怎么玩呢?实际上和MethodChannel
的玩法差不多,这里是代码示例:
/** * DimPlugin */public class DimPlugin implements MethodCallHandler, EventChannel.StreamHandler { private static final String TAG = "DimPlugin"; private Registrar registrar; private EventChannel.EventSink eventSink; public DimPlugin(Registrar registrar) { this.registrar = registrar; } /** * Plugin registration. */ public static void registerWith(Registrar registrar) { final MethodChannel channel = new MethodChannel(registrar.messenger(), "dim"); final EventChannel eventChannel = new EventChannel(registrar.messenger(), "event"); final DimPlugin dimPlugin = new DimPlugin(registrar); channel.setMethodCallHandler(dimPlugin); eventChannel.setStreamHandler(dimPlugin); } .......@Override ///public void onMethodCall(MethodCall call, final Result result) { .......... @Override public void onListen(Object o, EventChannel.EventSink eventSink) { this.eventSink = eventSink; } @Override public void onCancel(Object o) { Log.e(TAG, "onCancel() called with: o = [" + o + "]"); }复制代码
这里,具体的channel实现这里,实现多了一个EventChannel.StreamHandler
,然后在初始化的时候,eventChannel.setStreamHandler(dimPlugin);
对,设置了一下setStreamHandler。
我们关心一下这个eventSink
,这个对象就是用来向Stream
发送数据的,当这边的server
需要push内容到dart那边的时候,就能够使用
TIMManager.getInstance().addMessageListener(new TIMMessageListener() { @Override public boolean onNewMessages(Listlist) { eventSink.success(list); return false; } });复制代码
这样的方式。很显然这个方式有点类似于Rxjava
的emit
数据了,那么,dart那边是需要一个消费者的,怎么玩? 首先
Stream_listener; Stream get onMessage { if (_listener == null) { _listener = _eventChannel .receiveBroadcastStream() .map((dynamic event) => _parseBatteryState(event)); } return _listener; }复制代码
把链路建好,建好了在干什么,还记得Rxjava的subScribe么?对,这里也是这样
if (_messageStreamSubscription == null) { _messageStreamSubscription = _dim.onMessage.listen((dynamic onData) { print("我监听到数据了$onData"); }); }复制代码
不多,这里的listen就相当于订阅了这个发送序列,一旦那边有类容推送,这边就能收到了。 好,结束了之后,改如何关闭呢这个链路呢?
@override void dispose() { // TODO: implement dispose super.dispose(); if (_messageStreamSubscription != null) { _messageStreamSubscription.cancel(); } }复制代码
对的,和Rxjava类似,类似于在onDestory中,终止这种订阅协议。
注意!!!建立链路的代码.receiveBroadcastStream()
,这里写的接收广播流,然后官方的这里面也写了广播,就会有同学认为消息发送需要在广播接收者中进行
private BroadcastReceiver createChargingStateChangeReceiver(final EventSink events) { return new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS, -1); switch (status) { case BatteryManager.BATTERY_STATUS_CHARGING: events.success("charging"); break; case BatteryManager.BATTERY_STATUS_FULL: events.success("full"); break; case BatteryManager.BATTERY_STATUS_DISCHARGING: events.success("discharging"); break; default: events.error("UNAVAILABLE", "Charging status unavailable", null); break; } } }; }复制代码
这明显是没有任何道理的,实际上官方这个代码用到广播接受者是因为要收到充电状态相关通知,才用到了广播而已。
五、总结 使用platform-channels
制作flutter插件的时候,使用MethodChannel
来从dart端调用平台,使用EventChannel
的方式来让平台向dart端推送消息,这两者结合起来,实现插件基本就没什么问题了。
同时送上一幅图,方便读者很轻易的记住MethodChannel 主导 flutter->平台的调用,EventChannel主导平台推送内容给flutter。