Android與Flutter混合開發-UI互動

菜鳥阿呆發表於2020-02-14

前言

  • 本人是做android的,這邊只介紹下Android和Flutter的混合開發。
  • 關於原生和Flutter的混合開發,網上有很多相關的文章了,基本上都是使用FlutterView和FlutterFragment的方式去做的,但是在新版Flutter SDK 1.12版本上,Flutter團隊把io.flutter.facade.Flutter這個包給刪了,上面倆種方式直接涼了,根本無法在Android專案裡拿到Flutter物件。所以只能去看官方文件,跑下新的整合方法,這邊做個備忘,也同時說下會踩的坑。
  • Flutter官方文件:flutter.cn/docs/develo…
  • 本文章適用於Flutter SDK 1.12版本

注:說明下,這個連結是中文文件,但是還有很多章節還是沒翻譯,例如將Flutter module整合到Android專案的章節。ios的已被翻譯,大佬們都是喜歡玩ios,所以首先翻譯ios?

準備工作

新建一個Android專案

  • 這個隨意,新建一個Android專案或者使用已有的Android專案都可以。

配置Android專案

  • Flutter 目前只支援 armeabi-v7a 和 arm64-v8a 架構。
android {
  //...
  defaultConfig {
    ndk {
      // 限制下架構,Flutter僅支援:armeabi-v7a 和 arm64-v8a 架構(目前1.12版本)
      abiFilters 'armeabi-v7a', 'arm64-v8a'
    }
  }
}
複製程式碼
  • Flutter使用了 Java 8的特性,設定支援Java 8
android {
  //...
  compileOptions {
    sourceCompatibility 1.8
    targetCompatibility 1.8
  }
}
複製程式碼

新建一個Flutter Module

  • 新建Flutter Module

新建Module

  • 設定Flutter Module

Android與Flutter混合開發-UI互動

匯入Flutter Module

自動匯入

注:Android Studio支援自動匯入,前提條件是:Android專案和Flutter Module在同一資料夾下。

  • 選擇:“New Module”
    Android與Flutter混合開發-UI互動
  • 選擇 “Import Flutter Module”
    Android與Flutter混合開發-UI互動
  • 選擇下你的Flutter Module專案資料夾就行了

手動匯入

注:只需要設定下就行了,上面自動匯入,就是預設生成程式碼的。

  • MyApp/app/build.gradle,新增下依賴
    dependencies {
      implementation project(':flutter')
    }
    複製程式碼
  • MyApp/settings.gradle,settings.gradle加上下述程式碼,“flutter_module”是專案名稱,寫上你自己的專案名稱(官網上的程式碼)
    include ':app'                                   
    setBinding(new Binding([gradle: this]))                            
    evaluate(new File(                                                      
      settingsDir.parentFile,                                              
      'flutter_module/.android/include_flutter.groovy'                         
    ))                                                                    
    複製程式碼
    一般預設生成的是下述程式碼形式
    include ':app'
    setBinding(new Binding([gradle: this]))
    evaluate(new File(
      settingsDir,
      '../flutter_module/.android/include_flutter.groovy'
    ))
    複製程式碼
  • 說明下路徑符號相關區別
    • / :根目錄
    • ./ :當前目錄
    • ../ : 父級目錄(上一級目錄)
    • settingsDir:獲取的是當前專案的全路徑,包括專案資料夾
    • settingsDir.parentFile:獲取專案資料夾上一級路徑

Flutter和Android UI互動

新增單個Flutter頁面

使用FlutterActivity的形式,呈現Flutter介面十分方便

  • 前置條件:FlutterActivity 必須在 AndroidManifest.xml 中註冊

    <activity
      android:name="io.flutter.embedding.android.FlutterActivity"
      android:theme="@style/AppTheme"
      android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
      android:hardwareAccelerated="true"
      android:windowSoftInputMode="adjustResize"
      />
    複製程式碼
  • 啟動Flutter Module預設路由(最簡單形式)

    Intent intent = FlutterActivity.createDefaultIntent(this);
    startActivity(intent);
    複製程式碼

    這樣可以直接跳轉到Flutter介面,實際是啟動FlutterActivity,在FlutterActivity裡面載入Flutter的Ui介面。

  • 選擇性跳轉某個路由介面

    Intent intent = FlutterActivity.withNewEngine()
                    .initialRoute("/you/route/") //設定你的路由地址
                    .build(this);
    startActivity(intent);
    複製程式碼

    實際上createDefaultIntent()方法裡面就是封裝了withNewEngine().build(launchContext)方法,有興趣的,可以點進程式碼裡面看看。

  • 使用快取的 FlutterEngine

    //使用快取的FlutterEngine(最大程度地減少啟動標準的延遲)
    FlutterEngine flutterEngine = new FlutterEngine(this);  //初始路由
    //flutterEngine.getNavigationChannel().setInitialRoute("your/route/here"); //自定義路由
    //開始執行Dart程式碼以預熱FlutterEngine。
    flutterEngine.getDartExecutor().executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault());
    //快取FlutterActivity要使用的FlutterEngine。
    FlutterEngineCache.getInstance().put("my_engine_id", flutterEngine);
    Intent intent = FlutterActivity
            .withCachedEngine("my_engine_id")
            .build(this);
    
    //某個按鈕的點選事件
    findViewById(R.id.flutter_button).setOnClickListener(v -> {
            startActivity(intent);
        });
    
    複製程式碼

    說明下:實際上FlutterActivity.withNewEngine()...方式跳轉,都是新生成一個FlutterEngine物件,每個FlutterEngine都有一個非常重要的預熱時間。這意味著啟動一個標準的FlutterActivity 會在Flutter體驗變得可見之前有一個短暫的延遲。為了最小化這個延遲,我們可以在到達FlutterActivity之前預熱FlutterEngine,然後可以使用快取的FlutterEngine。這種情況在debug安裝的情況尤其顯著,會有一段時間黑屏,提前快取好FlutterEngine,可以避免這種情況,提升互動體驗,推薦。

新增FlutterFragment

  • 說明:FlutterFragment可用的地方還是蠻多的,可用於顯示一個滑動的抽屜、標籤式內容、 ViewPager 中的一個頁面等等,當然,如果FlutterActivity能解決,建議使用FlutterActivity,因為FlutterActivity更容易使用。 官網原話:If an Activity is equally applicable for your application needs, consider using a FlutterActivity instead of a FlutterFragment, which is quicker and easier to use.

  • 使用FlutterFragment

    public class MyActivity extends FragmentActivity {
        private FlutterFragment flutterFragment;
        
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.my_activity_layout);
    
              //直接啟動FlutterFragment
            FragmentManager fragmentManager = getSupportFragmentManager();
            //預設路由,相當於:initialRoute("/")
            //FlutterFragment flutterFragment = FlutterFragment.createDefault();
            FlutterFragment flutterFragment = FlutterFragment.withNewEngine()
                    .initialRoute("/")   //設定路由
                    .build();
            fragmentManager
                    .beginTransaction()
                    .add(R.id.flutter_ui, flutterFragment, "flutter_fragment")
                    .commit();
        }
    }
    複製程式碼
    • R.id.flutter_ui:是佈局檔案裡面一個Fragment的id

    • 比較坑比的地方

      • FlutterFragment無法強轉成Fragment型別,涉及到強轉的部分會爆紅,但是不影響執行。

      • 原因:看了下FlutterFragment,繼承Fragment的,子類強轉父類是沒問題的,但是很坑的是,FlutterFragment裡面用的是Support包,我用的是Androidx,導致倆個物件沒法強轉。 就是:support.v4.app.Fragment的子類沒法強轉成androidx.fragment.app.Fragment ? 這個報錯真是血坑。

        Android與Flutter混合開發-UI互動

      • 解決辦法:在下只找到了可能的原因,實在找不到解決之法。可能的解決辦法

        • 報錯就報錯吧,留在那,不影響程式執行,佛系點
        • 等待Flutter團隊,把這個FlutterFragment裡面fuck的support包給刪掉,換成androidx
        • 有其他辦法解決,希望評論告知下
    • 使用上面的程式碼,足以把你的Flutter頁面展示出來了

  • 使FlutterFragment週期和Activity同步

    public class MyActivity extends FragmentActivity {
        @Override
        public void onPostResume() {
            super.onPostResume();
            flutterFragment.onPostResume();
        }
    
        @Override
        protected void onNewIntent(@NonNull Intent intent) {
            flutterFragment.onNewIntent(intent);
        }
    
        @Override
        public void onBackPressed() {
            flutterFragment.onBackPressed();
        }
    
        @Override
        public void onRequestPermissionsResult(
            int requestCode,
            @NonNull String[] permissions,
            @NonNull int[] grantResults
        ) {
            flutterFragment.onRequestPermissionsResult(
                requestCode,
                permissions,
                grantResults
            );
        }
    
        @Override
        public void onUserLeaveHint() {
            flutterFragment.onUserLeaveHint();
        }
    
        @Override
        public void onTrimMemory(int level) {
            super.onTrimMemory(level);
            flutterFragment.onTrimMemory(level);
        }
    }
    複製程式碼
  • 使用快取的 FlutterEngine

    //使用快取的FlutterEngine(最大程度地減少啟動標準的延遲)
    FragmentManager fragmentManager = getSupportFragmentManager();
    FlutterEngine flutterEngine = new FlutterEngine(this);  //初始路由
    // flutterEngine.getNavigationChannel().setInitialRoute("/"); //自定義路由
    //開始執行Dart程式碼以預熱FlutterEngine。
    flutterEngine.getDartExecutor().executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault());
    FlutterEngineCache.getInstance().put("my_engine_id", flutterEngine);//快取要使用的FlutterEngine。
    
    //點選事件
    findViewById(R.id.flutter_button).setOnClickListener(v -> {
            FlutterFragment flutterFragment = FlutterFragment.withCachedEngine("my_engine_id").build();
            fragmentManager
                    .beginTransaction()
                    .add(R.id.flutter_ui, flutterFragment, "flutter_fragment")
                    .commit();
        });
    複製程式碼

    基本上和上面使用FlutterActivity快取機制一樣

    • Flutter官網上寫完快取程式碼,單獨來了這麼段程式碼:
      flutterFragment.withCachedEngine("my_engine_id").build();
      複製程式碼
      真是嗶嗶了狗,我還以為withCachedEngine("my_engine_id").build()直接把快取的FlutterEngine寫入到flutterFragment物件裡面,實際上,build() 返回的是FlutterFragment物件,上面的程式碼只是取設定好的FlutterFragment。
      • 猜測下官網上程式碼意圖,難道是這樣:
      fragmentManager
                .beginTransaction()
                .add(R.id.flutter_ui, flutterFragment.withCachedEngine("my_engine_id").build(), "flutter_fragment")
                .commit();
      複製程式碼

相關文章