Flutter入門進階之旅(十九)Flutter與原生平臺互動

謝棟發表於2020-01-02

引言:

經過前面章節的學習,相信讀者已經對flutter有了一個整體的認識,並且也能利用flutter平臺提供的一些基礎元件自己寫一些簡單的頁面邏輯,甚至有些讀者可能已經在用純flutter開發屬於自己的app了,但是可能好多讀者都會感覺到有些場景下或者說有些原生平臺的東西從flutter端是無法獲取的,比如系統版本、電池電量、動態許可權申請等系統級的API,flutter並沒有直接給我提供相關的API去操作,這個時候我們可能就需要通過藉助Native的能力或者與原生平臺互動來獲取這些資料。

課程目標

  • 瞭解並掌握flutter與原生通訊的方法
  • 掌握flutter與原生通過MethodChannel相互回撥的實現機制
  • 掌握原生平臺通過EventChannel主動向Flutter傳遞資料
1.Flutter與原生通訊

flutter在與native端進行通訊主要藉助MethodChannel跟EventChannel建立連線,MethodChannel跟EventChannel就像管道或者橋樑一樣,把flutter端跟native連線到一塊。 我們來看下官方對此的解釋:

A named channel for communicating with platform plugins using asynchronous method calls. 使用非同步方法呼叫與平臺外掛通訊的指定通道。

  • 1.)MethodChannel方法簽名
    public MethodChannel(BinaryMessenger messenger, String name) {
        this(messenger, name, StandardMethodCodec.INSTANCE);
    }
複製程式碼
  • 2.)EventChannel方法簽名
    public EventChannel(BinaryMessenger messenger, String name) {
        this(messenger, name, StandardMethodCodec.INSTANCE);
    }
複製程式碼

為了保證使用者介面在互動過程中的流暢性,無論是從Flutter向Native端傳送訊息,還是Native向Flutter傳送訊息都是以非同步的形式進行傳遞的。

1.在整個互動過程中,無論是Flutter端還是Native端都可以通過MethodChannel向對方平臺傳送兩端提前定義好的方法名來呼叫對方平臺相對應的訊息處理邏輯並且帶回返回值給被呼叫方。 2.而EventChannel的使用場景更側重於Native平臺主動向Flutter平臺單向給Flutter平臺傳送訊息,Flutter無法返回任何資料給Native端,筆者更願意把EventChannel描述成是單通的。

到目前為止,我們已經簡單的對flutter於native端的通訊互動方式有了一個簡單的瞭解,下面我們先來看貼上本節課程要完成的程式碼效果圖,然後再對效果圖中提到的案例逐個分析講解:

平臺互動

2.效果圖程式碼案例分析
  • Flutter端通過MethodChannel呼叫Native平臺彈出Toast
  • Flutter利用MechtondChannl向Natvie端傳遞引數呼叫Native端相關函式,Native接收引數後,並把處理完成後的結果返回給Flutter端。
  • Flutter端利用MethodChannel開啟原生新頁面
  • Native端利用MethodChannel傳遞引數到Flutter端,並接收從Flutter端傳遞回來的處理後到資料
  • Native端利用EventChannel主動向Flutter端傳送資料(訊息)

兩端互動的方法註冊邏輯也比較簡單,讀者一看便知,我就不過多展開描述,下面貼上關於兩端互動的兩個代表性的例子:

1.Flutter 呼叫原生函式,計算兩個數的和並獲取處理完成的結果到Flutter端 2.Native端利用EventChannel主動向Flutter端傳送資料(訊息)

2.1 Flutter 呼叫原生函式,計算兩個數的和並獲取處理完成的結果到Flutter端

場景分析 :可類比Flutter端處理不了的業務邏輯,這個時候把必要的引數傳遞給原生,原生處理接收到引數,處理完成後,再把結果回傳到Flutter端,完成整個業務邏輯。

註冊通道 為了確保Flutter與Native能建立正常的通訊,我們首先要保證兩端註冊的MethocChannel的通道名channelName一致,如下分別在兩端註冊channelName:

Native端:

private static final String METHOD_CHANNEL = "com.zhuandian.flutter/android";
 methodChannel = new MethodChannel(getFlutterView(), METHOD_CHANNEL);
複製程式碼

Flutter端:

 static final String METHOD_CHANNEL = "com.zhuandian.flutter/android";
   static final MethodChannel _MethodChannel =MethodChannel(METHOD_CHANNEL); //平臺互動通道
複製程式碼

Android端:

public class MainActivity extends FlutterActivity {
    private static final String METHOD_CHANNEL = "com.zhuandian.flutter/android";
    private static final String METHOD_NUMBER_ADD = "numberAdd"; //簡單加法計算,並返回兩個數的和
    private MethodChannel methodChannel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
//        setContentView(R.layout.activity_main);
        GeneratedPluginRegistrant.registerWith(this);


        methodChannel = new MethodChannel(getFlutterView(), METHOD_CHANNEL);
        //接受fltuter端傳遞過來的方法,並做出響應邏輯處理
        methodChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
            @Override
            public void onMethodCall(MethodCall call, MethodChannel.Result result) {
                System.out.println(call.method);
               if (call.method.equals(METHOD_NUMBER_ADD)) {
                    int number1 = call.argument("number1");
                    int number2 = call.argument("number2");
                    result.success(number1 + number2); //返回兩個數相加後的值
                } 
            }
        });

    }
    
}
複製程式碼

Flutter 端:

class AndroidPlatformPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => PageState();
}

class PageState extends State<AndroidPlatformPage> {
  static final String METHOD_CHANNEL = "com.zhuandian.flutter/android";
  static final String NATIVE_METHOD_ADD = "numberAdd"; //原生android平臺定義的供flutter端喚起的方法名
  static final MethodChannel _MethodChannel = MethodChannel(METHOD_CHANNEL); //平臺互動通道

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("平臺互動"),
        centerTitle: true,
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: <Widget>[
          RaisedButton(
            color: Colors.orangeAccent,
            child: Text("計算兩個數的和"),
            onPressed: () {
              getNumberResult(25, 36);
            },
          ),
        ],
      ),
    );
  }

 
  /**
   * 呼叫平臺方法計算兩個數的和,並呼叫原生toast列印出結果
   */
  void getNumberResult(int i, int j) async {
    Map<String, dynamic> map = {"number1": 12, "number2": 43};
    int result = await _MethodChannel.invokeMethod(NATIVE_METHOD_ADD, map);
    print("呼叫原生平臺計算後的結果為:${result}");
  }
}
複製程式碼

通過MethodChannel傳遞引數並且獲取返回值,兩端的執行邏輯完全一樣,無論是從Flutter端回撥Native端端資料,還是Native回撥Flutter端資料都是先由被喚起端通過 methodChannel.setMethodCallHandlermethodChannel.invokeMethod(String method, [ dynamic arguments ]),一個負責處理回撥,一個負責傳送事件並且攜帶引數。

2.1.1事件接收處理端

接收處理回撥時onMethodCall(MethodCall call, MethodChannel.Result result)通過methodCall接收事件傳送者傳遞回來的資訊,通過Result把處理完的結果傳送給事件傳送方。

  • 通過methodCall.method:來區分不同函式名(方法)名以執行不同的業務邏輯,
  • 通過methodCall.hasArgument("key"):判斷是否有某個key對應的value
  • 通過methodCall.argument("key"):獲取key對應的value值
  • 通過result.success(object):把處理完的結果返回給事件傳送方
2.1.2事件傳送端

處理事件傳送方通過methodChannel.invokeMethod("方法名","要傳遞的引數")把需要傳遞的引數傳遞給事件監聽者。 其中

  • 方法名:不能為空
  • 要傳遞的引數:可以為空,若不為空則必須為可Json序列化的物件。

然後監聽者通過在setMethodCallHandler做出相關操作,對傳遞過來的引數做出相對於的邏輯處理後再把結果傳遞回來,以此完成整個平臺互動過程。

上述例子flutter端作為事件傳送方,Native端作為事件接收處理方,其實MethodChannel的設計完全可以讓二者身份互換,即從Native端去傳送訊息到Flutter然後獲取返回結果,實現的邏輯就是上述的逆過程,在真實開發中應用中筆者認為從原生端通過EventChannel主動向Flutter發訊息的使用場景要遠遠多於Native端通過MethodChannel回撥去處理Flutter端的返回資料,所以就不單獨在這裡展開贅述Native端通過MethodChannel回撥去處理Flutter端返回的資料了,感興趣的讀者可以檢視本篇部落格配套程式碼中去查閱相關程式碼示例跟註釋解讀,下面我們來看下通過EventChannel主動向Flutter發訊息的具體操作流程。

2.2 Native端利用EventChannel主動向Flutter端傳送資料(訊息)

場景分析:實現從Native端主動向Flutter端傳遞資料:頁面成功渲染後從原生向Flutter端傳送一個字串顯示在Flutter端端Text Widget上,當點選Flutter介面上的按鈕後,呼叫原生的方法,主動向Flutter傳送訊息,更新Flutter端端UI顯示。

效果圖

Native主動向Flutter傳送訊息

android端程式碼

public class MainActivity extends FlutterActivity {
    private static final String METHOD_CHANNEL = "com.zhuandian.flutter/android";
    private static final String EVENT_CHANNEL = "com.zhuandian.flutter/android/event"; //事件通道,供原生主動呼叫flutter端使用
    private static final String METHOD_NATIVE_SEND_MESSAGE_FLUTTER = "nativeSendMessage2Flutter"; //原生主動向flutter傳送訊息
    private EventChannel.EventSink eventSink;
    private MethodChannel methodChannel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        GeneratedPluginRegistrant.registerWith(this);

        methodChannel = new MethodChannel(getFlutterView(), METHOD_CHANNEL);
        //接受fltuter端傳遞過來的方法,並做出響應邏輯處理
        methodChannel.setMethodCallHandler(new MethodChannel.MethodCallHandler() {
            @Override
            public void onMethodCall(MethodCall call, MethodChannel.Result result) {
                System.out.println(call.method);
                 if (call.method.equals(METHOD_NATIVE_SEND_MESSAGE_FLUTTER)) {
                    nativeSendMessage2Flutter();
                }
            }
        });


        new EventChannel(getFlutterView(), EVENT_CHANNEL).setStreamHandler(new EventChannel.StreamHandler() {
            @Override
            public void onListen(Object o, EventChannel.EventSink eventSink) {
                MainActivity.this.eventSink = eventSink;
                eventSink.success("事件通道準備就緒");
                //在此不建議做耗時操作,因為當onListen回撥被觸發後,在此註冊當方法需要執行完畢才算結束回撥函式
                //的執行,耗時操作可能會導致介面卡死,這裡讀者需注意!!
            }

            @Override
            public void onCancel(Object o) {

            }
        });


    }


    /**
     * 原生端向flutter主動傳送訊息;
     */
    private void nativeSendMessage2Flutter() {
        //主動向flutter傳送一次更新後的資料
        eventSink.success("原生端向flutter主動傳送訊息");
    }
}

複製程式碼

Flutter端程式碼

class AndroidPlatformPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => PageState();
}

class PageState extends State<AndroidPlatformPage> {
  static final String METHOD_CHANNEL = "com.zhuandian.flutter/android";
  static final String EVENT_CHANNEL = "com.zhuandian.flutter/android/event";

  static final String NATIVE_SEND_MESSAGE_TO_FLUTTER =
      "nativeSendMessage2Flutter"; //原生主動向flutter傳送訊息

  static final MethodChannel _MethodChannel =
      MethodChannel(METHOD_CHANNEL); //平臺互動通道
  static final EventChannel _EventChannel =
      EventChannel(EVENT_CHANNEL); //原生平臺主動呼叫flutter端事件通道

  String _fromNativeInfo = "";

  @override
  void initState() {
    super.initState();
    _EventChannel.receiveBroadcastStream().listen(_onEvent, onError: _onErroe);
  }
  

  /**
   * 監聽原生傳遞回來的值(通過eventChannel)
   */
  void _onEvent(Object object) {
    print(object.toString() + "-------------從原生主動傳遞過來的值");
    setState(() {
      _fromNativeInfo = object.toString();
    });
  }

  void _onErroe(Object object) {
    print(object.toString() + "-------------從原生主動傳遞過來的值");
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("平臺互動"),
        centerTitle: true,
      ),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: <Widget>[
          Text("從原生平臺主動傳遞回來的值"),
          Text(_fromNativeInfo),
          RaisedButton(
            color: Colors.orangeAccent,
            child: Text("點選呼叫原生主動向flutter發訊息方法"),
            onPressed: () {
              _MethodChannel.invokeMethod(NATIVE_SEND_MESSAGE_TO_FLUTTER);
            },
          ),
        ],
      ),
    );
  }
}
複製程式碼

通過對比MethodChannle回撥的形式傳遞資料,結合EventChannel從Native端主動向Flutter端傳送資料我們可以發現後者更注重的監聽,我們在Native端通過註冊EventChannel物件後,然後通過EventChannel.EventSinkvoid success(Object var1)方法向Flutter傳送資料。

而Flutter端,我們通過在頁面初始化端時候繫結監聽物件

@override
  void initState() {
    super.initState();
    _EventChannel.receiveBroadcastStream().listen(_onEvent, onError: _onErroe);
  }
複製程式碼

並且通過_onEvent監聽從原生主動傳送過來值,然後註冊相應的處理。

  /**
  * 監聽原生傳遞回來的值(通過eventChannel)
  */
 void _onEvent(Object object) {
   print(object.toString() + "-------------從原生主動傳遞過來的值");
   setState(() {
     _fromNativeInfo = object.toString();
   });
 }
複製程式碼

限於篇幅問題,我在博文開頭效果圖裡展示端程式碼就不逐一講解效果圖上的程式碼示例了完整的程式碼在本篇文章對應的原始碼裡都沒有註釋,讀者可自行閱讀配套原始碼結合程式碼裡的註釋自行測試。 平臺互動部分完整程式碼: Native端:github.com/xiedong11/f… Flutter端:github.com/xiedong11/f…

相關文章