1、flutter使用platform-channels制做插件是不是一种完美的体验?java
flutter的优点在于很是方便构建UI,并且跑起来在两个平台(Android,IOS)上表现几乎彻底同样,并且,性能看起来彷佛还能够。 可是有一个痛点,那就是,当须要获取平台相关的一些属性的时候,难题就来了,根本就没有这样的api给你调用。不过,值得高兴且悲哀的是:google给开发者提供了一种折中的方式,那就是使用platform-channels作一个插件,来实现咱们可能遇到的一些需求。android
为何说值得高兴?值得高兴是由于,最终这个问题有一个解决的办法,不至于噶皮了,没办法绕过。那么,有为何说悲哀呢?很简单,若是你是一个android开发者,你实现android的部分没有什么问题,可是实现IOS部分,你找谁去,没人是否是得学一学。ios
整体来讲,我的也是以为这种体验并不算太好,加上flutter社区目前可供使用的插件比较少,可能会致使不少开发者对flutter望而止步。git
2、做为一个追求技术的人,咱们是否是仍是要躺一躺这个坑呢?github
是的,佛说:“我不入地狱谁入地狱”,总有第一个吃螃蟹的人,你已经错过了第一个,难躺的坑别人已经躺过了,难道你还不试一试吗?反正,我下面是要试一试了。 那么,在尝试写插件时,咱们想想,咱们为何须要写插件,不写插件难道就不能实现么?是的,还真是,好比,有一下场景,咱们就不得不写插件。 一、好比,咱们要使用腾讯云上面的云通讯,诶,这个就悲剧了,你去它官网找一下,他没有提供flutter版本的,并且社区,目前应该尚未人共享,估计已经有人实现了,可是仍是私有的。 二、好比,官方提到的,获取手机电量,充电状态,网络制式状态,等等等等。 三、bugly等错误上报。。 四、推送。。 好,不在举例了,聪明的你已经发现了,这些基本上都和UI无关的一些库,一些sdk,是的,不基于这些玩意,你有时间,不少一些功能彷佛你也能够实现,好比: IM功能,实际上,你彻底能够本身实现一套,配合先后台。but,你要多久呢?1个月,2个月?是否值得这个成本呢?api
总结:看来platform-channels这趟浑水,是有必要趟一趟的。bash
3、platform-channels能作什么? 网络
image.pngapp
嗯,这里很无耻的盗图了,这个图也是话的够TM简洁的,他是说,经过MethodChannel
,你就可以调用不管是android,仍是ios那边的平台相关的api,或者第三方库。async
嗯,总结一下,就是经过MethodChannel
调用平台或库,拿到返回结果。
试着想想,仅仅是这样,那够么?回答,确定是不够的,好比,一个第三方库是一个server
,我这里说server
可能有点不许,那你就理解为可以不按期向外发送消息的模块,或者,你就干脆理解为IM或者推送吧。 那么,怎么作呢?我经过MethodChannel
传递一个Listener
过去,嗯,这种很是常规的观察者模式,多么easy啊?but可行么?很遗憾,这不行,为何?
咱们来了解一下flutter端调用MethodChannel
的方式
Future<dynamic> imLogin(int appid, String identifier, String sig) async {
return await _methodChannel.invokeMethod("im_login", <String, dynamic>{
'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(List<dynamic> message);
}
/// Signature for callbacks that report that an underlying value has changed.
///
/// See also [ValueSetter].
typedef void ValueChanged<T>(T value);复制代码
那么,invokeMethod是这样的了,对么?
Future<dynamic> imLogin(int appid, String identifier, String sig, ValueChanged callBack) async {
return await _methodChannel.invokeMethod("im_login", <String, dynamic>{
'sdkAppId': appid,
'identifier': identifier,
'userSig': sig,
'callback':callBack
});
}复制代码
然而,在plugin实现那边,请问你如何转型?这边是已经不是dart那一套了,如何知道你是什么类型呢?
image.png
那么,正确的实现方式是什么呢?
3、认识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(List<TIMMessage> list) {
eventSink.success(list);
return false;
}
});复制代码
这样的方式。很显然这个方式有点相似于Rxjava
的emit
数据了,那么,dart那边是须要一个消费者的,怎么玩? 首先
Stream<dynamic> _listener;
Stream<dynamic> 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()
,这里写的接收广播流,而后官方的demo这里面也写了广播,就会有同窗认为消息发送须要在广播接收者中进行
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;
}
}
};
}复制代码
这明显是没有任何道理的,实际上官方这个代码用到广播接受者是由于要收到充电状态相关通知,才用到了广播而已。
5、总结 使用platform-channels
制做flutter插件的时候,使用MethodChannel
来从dart端调用平台,使用EventChannel
的方式来让平台向dart端推送消息,这二者结合起来,实现插件基本就没什么问题了。
同时送上一幅图,方便读者很轻易的记住MethodChannel 主导 flutter->平台的调用,EventChannel主导平台推送内容给flutter。