Flutter 學習日誌 (二) 現有項目集成 flutter

本文主要介紹怎樣在現有的 Android 項目中集成 flutter 開發界面,以及 flutter 與原生之間怎樣進行方法互調、數據雙向傳遞

依賴集成

官方目前沒有正式的集成文檔,只有一個 github 上的 wiki 頁面做了相關介紹。原因嘛,看了 wiki 就知道了

This page documents ongoing experiments and will be updated as we find more and better ways to do this, and as we build out tooling to aid.

This support is in preview, and is generally only available in the master channel.

目前現有工程集成 flutter 還處在開發探索階段。

注意

在現有的 Android 項目中集成 flutter 是以 module 的方式依賴到項目中。但 flutter 項目是創建在Android 工程的同級目錄下的,而不是像一般 Android lib module 一樣跟 app module 在同級目錄,譬如要爲 D:\AndroidSpace\xxx\AndroidProject工程集成 flutter 則 flutter module 也需要創建在 D:\AndroidSpace\xxx\ 目錄下。

集成步驟

1、命令行/powershell 進入到 Android 工程所在的目錄,即上文舉例中的 xxx 目錄,執行 flutter 命令創建 flutter module。

flutter create -t module flutter_module

2、待創建完畢後,打開 Android Project 的 setting.gradle 文件,添加如家代碼

setBinding(new Binding([gradle: this]))                       
evaluate(new File( 
        settingsDir.parentFile,
        'flutter_check/.android/include_flutter.groovy'
))

3、同步設置後在 app gradle 中添加依賴(flutter 中的 support 包可能會有依賴衝突,加入 exclude 排除掉 support 包依賴即可解決)

dependencies {
    ...
    implementation(project(':flutter'),{
        exclude group: 'com.android.support'
    })
    ...

4、經過上面的配置 flutter 已經集成到現有 Android 工程中了,可以將 flutter 頁面當做 View 添加到原生界面上,並在界面的生命週期函數中調用 FlutterView 的對應生命週期方法。也可以創建一個 FlutterFragment 顯示在界面上,由 Flutter 自行處理生命週期相關問題。

  • 添加 flutterview 方式
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.MATCH_PARENT);
        flutterView = Flutter.createView(this, getLifecycle(), "PhoneCheck");
        addContentView(flutterView, layout);
        ...
    }
  • FlutterFragment 方式
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_flutter_test);
        ...
        FragmentTransaction tx = getSupportFragmentManager().beginTransaction();
        tx.replace(R.id.fl_flutter_container, Flutter.createFragment("PhoneCheck"));
        tx.commit();
        ...
    }

當然,上面兩種方式不是必須在 oncreate 中調用,當做普通的 View / fragment 在合適的地方使用就行。使用 Flutterfragment 時如果需要 flutter 與原生間進行通信則需要繼承 Flutterfragment 重新 onCreateView() 拿到自動創建的 FlutterView 對象。因爲初始化通信信道時需要用到 FlutterView 對象。另外在使用時還遇到了 getLifecycle() 方法找不到的問題,原因是 Android 工程的 buildToolsVersion 太低,之前是 25 後面改成 27 的就解決了。

Flutter 與原生的方法互調及數據傳遞

通過上面的步驟,flutter 頁面是可以嵌入顯示在原生應用上了。但現階段還是各顯示各的互不交流,這顯然是不滿足需求的。好在 Flutter 爲我們提供了通信的橋樑——平臺插件,Flutter 與原生的通信通過兩個類型的 Channel 來實現。即通過反射進行方法調用的 MethodChannel 和將數據轉換成二進制比特數組直接傳遞的 BasicMessageChannel ,且通信都是雙向的。Channel 支持傳輸的數據類型有以下:

,如果需要傳遞自定義的對象數據等則需要轉成 json 字符串或 map 對象傳遞過去再解析。

MethodChannel

MethodChannel 是 Flutter 與平臺原生進行方法互調的插件,Android 中通過反射實現方法調用。

example

flutter module 對應的 State 中

    ...
    MethodChannel platform;
  @override
  void initState() {
    ...
    platform = const MethodChannel('my_flutter/plugin');
    platform.setMethodCallHandler(methodHandler);
    super.initState();
    ...
  }
  
   // 接收原生平臺的方法調用處理
  Future methodHandler(MethodCall call) async {
    ...
  }

初始化 WidgetState 的時候初始化 MethodChannel 併爲之設置回調處理方法,回調方法中有參數 MethodCall,可通過 call.method 來判斷需要執行的具體操作。
Android 原生代碼
與 flutter 端一樣,也需要先初始化 channel

MethodChannel mChannel;
private void initChannel() {
        mChannel = new MethodChannel(flutterView, "my_flutter/plugin");
        
        mChannel.setMethodCallHandler((MethodCall methodCall, MethodChannel.Result result) -> {
            switch (methodCall.method) {
                case "action1":
                    ...
                    break;
                case "action2":
                    ...
                    result.success("jump");
                    break;
            }
        });
    }

調用方式

  • flutter 端調用原生中的方法:
    // flutter 中有一個 getheaders 方法,從原生去獲取 headers map 對象
Map<String, String> headers;
  getHeaders() async {
    try {
      Map<dynamic, dynamic> result = await platform.invokeMethod('getHeaders');
      // 將 dynamic 類型轉換成需要的類型(與平臺端返回值給定的類型要一致)
      headers = result.cast();
    } on PlatformException catch (e) {
      print(e.message);
    }
  }

在 flutter 中使用 MethodChannel 對象執行 invoke(‘methodName’)即可調用,如方法需要傳遞參數則調用 invoke(‘methodName’[dynamic]),傳遞一個 dynamic 類型的參數數組。在原生平臺中使用 methodCall.arguments()獲取參數。 需要返回值則需要添加 await 修飾。原生平臺中的 MethodCallHandler 回調就會接收到調用信息,methodCall.method值爲 flutter 端 invoke 的方法名,result 對象設置的返回值則爲 flutter 平臺獲取到的返回值

mChannel.setMethodCallHandler((MethodCall methodCall, MethodChannel.Result result) -> {
            switch (methodCall.method) {
                case "action1":
                    ...
                    break;
                case "action2":
                    ...
                    result.success("jump");
                    break;
            }
        });
  • 原生平臺調用 flutter 方法
//與 flutter 調用原生一樣,使用 MethodChannel 的 invoke 方法
mChannel.invokeMethod("setAddress");
// 調用並傳遞參數
mChannel.invokeMethod("setAddress",Object);
  Future methodHandler(MethodCall call,) async {
    print(call.method);
    if (call.method == 'setAddress') {
        ...
    }
  }

flutter 中的 methodHandler 接收到方法調用,再做相關處理。與 flutter 調用原生不一樣,原生調用 fultter 方法是沒有返回值的。

注意事項

  • MethodChannel 初始化時,指定的 channelName 參數,如上面代碼片段中的my_flutter/plugin。需要 flutter 端與原生平臺端一致,才能保證 Channel 信道一致,互相接收到對放發送的方法調用請求。
  • 參數傳遞時,傳遞的參數爲基本數據類型或者 Map List 之類的雙方都能識別的類型,自定義數據類轉換成 map 或者 json 字符串再傳遞。當 flutter 傳遞的參數爲 json對象(內部也是轉成 map 傳遞的) 或者 map 時可以在原生中可以直接使用 methodCall.argument("key");獲取參數值,獲取前可以使用 methodCall.hasArgument("key");判斷參數是否存在。

BasicMessageChannel

BasicMessageChannel 是 Flutter 與平臺原生直接發送和接收數據的插件,數據以二進制的形式在其中傳輸。
基本步驟和 MethodChannel 一致,在 flutter 端和平臺端都實例化對象並設置回調,與 MethodChannel 初始化不同之處在於初始化時多了一個類型參數,因爲 BasicMessageChannel 本身是可以傳入泛型的,因此在初始化時需要指定泛型類型。其實打開 MethodChannel 的初始化方法會發現,內部也是傳入了類型的,只不過使用的默認值 StandardMethodCodec.INSTANCE。具體有哪些類型實現,打開 project 依賴目錄找到 flutter.jar 其中的 io.flutter.common 包中可以看到

Class Note
BinaryCodec 二進制數據類型,泛型對應 ByteBuffer,傳遞 byte 數組時使用
JSONMessageCodec Json消息類型,泛型對應 Object。傳遞 Json時可使用
StringCodec 字符串消息類型,泛型對應 String。傳遞字符串
StandardMessageCodec 標準消息類型,泛型對應 Object。傳遞 Map 等的時候可使用此類型

Flutter 中對應 class 在 flutter\src\services\message_codecs.dart 文件中。

總結

本文只是記錄對照官方文檔上手在 demo 中實踐的心得,可能會理解有所偏差。待深入研究再進行糾正。flutter 與原生平臺通信主要通過三個默認的 Channel ,除了上面說到的 MethodChannelBasicMessageChannel還有一個 EventChannel具體可以看一下這篇文章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章