flutter使用platform-channels製作外掛

brzhang發表於2019-03-04

一、flutter使用platform-channels製作外掛是否是一種完美的體驗?

flutter的優勢在於非常方便構建UI,而且跑起來在兩個平臺(Android,IOS)上表現幾乎完全一樣,而且,效能看起來似乎還可以。 但是有一個痛點,那就是,當需要獲取平臺相關的一些屬性的時候,難題就來了,根本就沒有這樣的api給你呼叫。不過,值得高興且悲哀的是:google給開發者提供了一種折中的方式,那就是使用platform-channels做一個外掛,來實現我們可能遇到的一些需求。

為什麼說值得高興?值得高興是因為,最終這個問題有一個解決的辦法,不至於噶皮了,沒辦法繞過。那麼,有為什麼說悲哀呢?很簡單,如果你是一個android開發者,你實現android的部分沒有什麼問題,但是實現IOS部分,你找誰去,沒人是不是得學一學。

總體來說,個人也是覺得這種體驗並不算太好,加上flutter社群目前可供使用的外掛比較少,可能會導致很多開發者對flutter望而止步。

二、作為一個追求技術的人,我們是不是還是要躺一躺這個坑呢?

是的,佛說:“我不入地獄誰入地獄”,總有第一個吃螃蟹的人,你已經錯過了第一個,難躺的坑別人已經躺過了,難道你還不試一試嗎?反正,我下面是要試一試了。 那麼,在嘗試寫外掛時,我們想一想,我們為什麼需要寫外掛,不寫外掛難道就不能實現麼?是的,還真是,比如,有一下場景,我們就不得不寫外掛。 1、比如,我們要使用騰訊雲上面的雲通訊,誒,這個就悲劇了,你去它官網找一下,他沒有提供flutter版本的,而且社群,目前應該還沒有人共享,估計已經有人實現了,但是還是私有的。 2、比如,官方提到的,獲取手機電量,充電狀態,網路制式狀態,等等等等。 3、bugly等錯誤上報。。 4、推送。。 好,不在舉例了,聰明的你已經發現了,這些基本上都和UI無關的一些庫,一些sdk,是的,不基於這些玩意,你有時間,很多一些功能似乎你也可以實現,比如: IM功能,實際上,你完全可以自己實現一套,配合前後臺。but,你要多久呢?1個月,2個月?是否值得這個成本呢?

總結:看來platform-channels這趟渾水,是有必要趟一趟的。

三、platform-channels能做什麼?

flutter使用platform-channels製作外掛

image.png

嗯,這裡很無恥的盜圖了,這個圖也是話的夠TM簡潔的,他是說,通過MethodChannel,你就能夠呼叫不論是android,還是ios那邊的平臺相關的api,或者第三方庫。

嗯,總結一下,就是通過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那一套了,如何知道你是什麼型別呢?

flutter使用platform-channels製作外掛

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(List<TIMMessage> list) {
                    eventSink.success(list);
                    return false;
                }
            });複製程式碼

這樣的方式。很顯然這個方式有點類似於Rxjavaemit資料了,那麼,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;
        }
      }
    };
  }複製程式碼

這明顯是沒有任何道理的,實際上官方這個程式碼用到廣播接受者是因為要收到充電狀態相關通知,才用到了廣播而已。

五、總結 使用platform-channels製作flutter外掛的時候,使用MethodChannel來從dart端呼叫平臺,使用EventChannel的方式來讓平臺向dart端推送訊息,這兩者結合起來,實現外掛基本就沒什麼問題了。

flutter使用platform-channels製作外掛

同時送上一幅圖,方便讀者很輕易的記住MethodChannel 主導 flutter->平臺的呼叫,EventChannel主導平臺推送內容給flutter。


相關文章