前面幾篇文章介紹了 Navigator 組件、Flex 佈局、圖片加載、Widget 生命週期等 Flutter 開發基礎知識,文章鏈接如下:
今天介紹一下 Flutter 混合開發模式,以及如何在 Android 現有項目中添加 Flutter 模塊等,主要內容如下:
- Flutter 混合開發模式
- Flutter Module 的創建方式
- 添加 Flutter 的幾種方式
- 添加單個 Flutter 頁面
- 添加 FlutterFragment
- Flutter 與 Android 互相跳轉
Flutter 混合開發模式#
Flutter 混合開發一般有兩種方式:
- 直接將原生項目作為 Flutter 項目的子項目,Flutter 默認會創建 android 和 ios 的工程目錄;
- 創建 Flutter Module 作為依賴添加到現有的原生項目中。
第二種方式相較第一種方式更解耦,尤其是針對現有項目改造成本更小。
Flutter Module 的創建方式#
創建 Flutter Module 有兩種方式:
- 使用命令創建 Flutter Module
flutter create -t module --org com.manu.flutter flutter_module_one
- 使用 As 創建 Flutter Module
在 As 中選擇 File->New->New Flutter Project,選擇 Flutter Module 創建 Flutter Module 子項目,如下:
添加 Flutter 的幾種方式#
這裡的添加方式指的是第二種方式,以 Flutter Module 的方式將 Flutter 模塊添加到現有項目中,在 Android 現有項目中添加 Flutter 有兩種方式:
- 以 aar 的方式集成的 Android 現有項目中:
創建好 Flutter Module 之後需要將其編譯成 aar 的形式,可以通過如下命令進行 aar 的編譯:
// cd到Flutter Module根目錄
cd flutter_module
flutter build aar
在 Android 中也可以通過 As 工具來編譯 aar,選擇 Build->Flutter->Build AAR 來進行 aar 的編譯。
然後根據提示在主項目工程的 build.grade 文件中進行相關配置,參考如下:
repositories {
maven {
url 'G:/xxx/flutter_module_one/build/host/outputs/repo'
}
maven {
url 'https://storage.googleapis.com/download.flutter.io'
}
}
buildTypes {
profile {
initWith debug
}
}
dependencies {
debugImplementation 'com.manu.flutter.flutter_module_one:flutter_debug:1.0'
profileImplementation 'com.manu.flutter.flutter_module_one:flutter_profile:1.0'
releaseImplementation 'com.manu.flutter.flutter_module_one:flutter_release:1.0'
}
- 以 Flutter module 的方式集成到現有 Android 項目中:
在 setting.gradle 文件中配置 flutter module 如下:
include ':app'
setBinding(new Binding([gradle: this]))
evaluate(new File(
settingsDir,
'flutter_module_two/.android/include_flutter.groovy'
))
然後在 build.gradle 文件中添加 flutter module 的依賴,如下:
dependencies {
implementation project(':flutter')
}
添加單個 Flutter 頁面#
創建一個 Activity 繼承 FlutterActivity 並在 AndroidManifest.xml 文件中聲明:
<activity
android:name=".AgentActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
</activity>
那麼如何啟動這個 FlutterActivity 呢,如下:
// 默認路由 /
myButton.setOnClickListener {
startActivity(
FlutterActivity.createDefaultIntent(this)
)
}
// 自定義路由
myButton.setOnClickListener {
startActivity(
FlutterActivity
.withNewEngine()
.initialRoute("/my_route")
.build(this)
)
}
上述代碼會在內部創建自己的 FlutterEngine 實例,每個 FlutterActivity 都創建自己的 FlutterEngine,這意味著啟動一個標準的 FlutterActivity 會在界面可見時出現一短暫的延遲,可以選擇使用預緩存的 FlutterEngine 來減小其延遲,實際上在內部會先檢查是否存在預緩存的 FlutterEngine,如果存在則使用該 FlutterEngine,否則繼續使用非預緩存的 FlutterEngine,其源碼判斷如下:
/* package */ void setupFlutterEngine() {
Log.v(TAG, "Setting up FlutterEngine.");
// 1. 檢查預緩存的FlutterEngine
String cachedEngineId = host.getCachedEngineId();
if (cachedEngineId != null) {
flutterEngine = FlutterEngineCache.getInstance().get(cachedEngineId);
isFlutterEngineFromHost = true;
if (flutterEngine == null) {
throw new IllegalStateException(
"The requested cached FlutterEngine did not exist in the FlutterEngineCache: '"
+ cachedEngineId
+ "'");
}
return;
}
// 2. 是否有自定義的FlutterEngine
// Second, defer to subclasses for a custom FlutterEngine.
flutterEngine = host.provideFlutterEngine(host.getContext());
if (flutterEngine != null) {
isFlutterEngineFromHost = true;
return;
}
Log.v(
TAG,
"No preferred FlutterEngine was provided. Creating a new FlutterEngine for"
+ " this FlutterFragment.");
// 3. 創建新的FlutterEngine
flutterEngine =
new FlutterEngine(
host.getContext(),
host.getFlutterShellArgs().toArray(),
/*automaticallyRegisterPlugins=*/ false);
isFlutterEngineFromHost = false;
}
預緩存的 FlutterEngine 的使用方式就不再贅述,可自行查看官網。
添加 FlutterFragment#
同樣的在 Android 現有項目中添加 FlutterFragment,為了後續通信方便也應該自定義 Fragment 繼承 FlutterFragment,然後將其添加到某個 Activity 中,如下:
class AgentActivity2 : FragmentActivity() {
private val flutterFragmentTag = "flutter_fragment_tag"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_agent2)
val fragmentManager = supportFragmentManager
var flutterFragment = fragmentManager.findFragmentByTag(flutterFragmentTag)
if (flutterFragment == null){
// flutterFragment = FlutterFragment.createDefault()
flutterFragment = MFlutterFragment
.withNewEngine()
?.build()
if (flutterFragment != null) {
fragmentManager.beginTransaction()
.add(R.id.ff_container,flutterFragment,flutterFragmentTag)
.commit()
}
}
}
}
跳轉添加 FlutterFragment 的 Activity 使用的 Intent 即可,如下:
// 跳轉添加Fragment的Activyt
val intent = Intent(this@LaunchActivity,AgentActivity2::class.java)
startActivity(intent)
Flutter 與 Android 互相跳轉#
Flutter 與 Android 互相跳轉,上文中基本都是原生 Android 跳轉 FlutterActivity 或者是添加 FlutterFragment 的 Activity,那麼 Flutter 頁面如何跳轉到原生 Activity 呢。
涉及到 Flutter 與原生的通信機制,主要包括 MethodChannel、EventChannel 以及 BasicMessageChannel,這一塊內容比較多,一小節肯定介紹不完,這裡只簡單介紹一下使用 MethodChannel,MethodChannel 主要用來傳遞方法調用,通過 MethodChannel 可以在 Flutter 頁面調用 Android 原生 API 提供的方法。
主要介紹一下使用 MethodChannel 來實現 Flutter 向原生 Android 的跳轉,無論是單個 Flutter 頁面還是添加的是一個 FlutterFragment,都需要分別繼承 FlutterActivity 和 FlutterFragment,然後重寫 configureFlutterEngine 方法,參考如下:
// FlutterActivity
class AgentActivity : FlutterActivity() {
private val tag = AgentActivity::class.java.simpleName;
private val channel = "com.manu.startMainActivity"
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
Log.d(tag,"configureFlutterEngine")
// 註冊MethodChannel,用來監聽Flutter頁面的方法調用
MethodChannel(flutterEngine.dartExecutor, channel)
.setMethodCallHandler { methodCall: MethodCall, result: MethodChannel.Result ->
if ("startMainActivity" == methodCall.method) {
MainActivity.startMainActivity(this)
result.success("success")
} else {
result.notImplemented()
}
}
}
companion object{
/**
* 重新創建NewEngineIntentBuilder才能保證生效
*/
fun withNewEngine(): MNewEngineIntentBuilder? {
return MNewEngineIntentBuilder(AgentActivity::class.java)
}
}
/**
* 自定義NewEngineIntentBuilder
*/
class MNewEngineIntentBuilder(activityClass: Class<out FlutterActivity?>?) :
NewEngineIntentBuilder(activityClass!!)
}
// 同理FlutterFragment也一樣
// 省略 ...
記得一定要重寫 withNewEngine 方法,否則 Flutter 跳轉原生 Activity 失敗,Flutter 頁面 invokeMethod 來進行方法調用,具體如下:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("Flutter Page"),
centerTitle: true,
),
body: PageWidget()
),
routes: <String,WidgetBuilder>{
},
);
}
}
/// Stateful Widget
class PageWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _PageState();
}
}
/// State
class _PageState extends State<PageWidget> {
MethodChannel platform;
@override
void initState() {
super.initState();
platform = new MethodChannel('com.manu.startMainActivity');
}
@override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
_startMainActivity();
},
child: Text("Flutter to Android"),
);
}
/// 跳轉到原生Activity
void _startMainActivity(){
platform.invokeMethod('startMainActivity').then((value) {
print("value:startMainActivity");
}).catchError((e) {
print(e.message);
});
}
}
此外,關於 Flutter 與原生通信機制將在後續的文章中標進行介紹。