前言
- 本人是做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
- 設定Flutter Module
匯入Flutter Module
自動匯入
注:Android Studio支援自動匯入,前提條件是:Android專案和Flutter Module在同一資料夾下。
- 選擇:“New Module”
- 選擇 “Import Flutter Module”
- 選擇下你的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 ? 這個報錯真是血坑。
-
解決辦法:在下只找到了可能的原因,實在找不到解決之法。可能的解決辦法
- 報錯就報錯吧,留在那,不影響程式執行,佛系點
- 等待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官網上寫完快取程式碼,單獨來了這麼段程式碼:
真是嗶嗶了狗,我還以為withCachedEngine("my_engine_id").build()直接把快取的FlutterEngine寫入到flutterFragment物件裡面,實際上,build() 返回的是FlutterFragment物件,上面的程式碼只是取設定好的FlutterFragment。flutterFragment.withCachedEngine("my_engine_id").build(); 複製程式碼
- 猜測下官網上程式碼意圖,難道是這樣:
fragmentManager .beginTransaction() .add(R.id.flutter_ui, flutterFragment.withCachedEngine("my_engine_id").build(), "flutter_fragment") .commit(); 複製程式碼
- Flutter官網上寫完快取程式碼,單獨來了這麼段程式碼: