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。