多余的前言
Flutter 2.0 發(fā)布時(shí),其中最受大家關(guān)注之一的內(nèi)容就是 Add-to-App 相關(guān)的更新,因?yàn)槌?strong>熱更新之外,F(xiàn)lutter 最受大家詬病的就是混合開發(fā)體驗(yàn)不好。
為什么不好呢?因?yàn)?Flutter 的控件渲染直接脫離了原生平臺(tái),也就是無論頁面堆棧和渲染樹都獨(dú)立于平臺(tái)運(yùn)行,這固然給 Flutter 帶來了較好的跨平臺(tái)體驗(yàn),但是也造成了在和原生平臺(tái)混合時(shí)存在高成本的問題。
且不說在已有的原生項(xiàng)目中集成 Flutter ,就是現(xiàn)階段在 Flutter 中集成原生控件的 PlatformView 和 Hybrid Composition 體驗(yàn)也是有待提升,當(dāng)然“有支持”和“能用”就已經(jīng)是很不錯(cuò)的進(jìn)展。
所以 Flutter 2.0 在千呼萬喚中發(fā)布了 FlutterEngineGroup 用于支持官方的 Add Flutter to existing app 方案。
在此方案出現(xiàn)之前,類似的第三方支持有 flutter_boost 、 mix_stack 、 flutter_thrio 等等 ,它們是否好用這里不討論,但是這些方案都要面對(duì)的問題是:
非官方的支持必然存在每個(gè)版本需要適配的問題,而按照 Flutter 目前的
issue closed和pr merge的速度,很可能每個(gè)季度的版本都存在較大的變動(dòng),所以如果開發(fā)者不維護(hù)或者維護(hù)不及時(shí),那么侵入性極強(qiáng)的這類框架很容易就成為項(xiàng)目的瓶頸。
而官方提供的 FlutterEngineGroup 方案有沒有缺陷?肯定有的,它目前看起來更像是被催生出來的狀態(tài),各方面的問題還是有的,比如某些地方還存在不能 destroy 的問題。 (當(dāng)然這個(gè)問題以及在 master 分支 merge 了)
但是官方提供的方案,就意味著這個(gè)設(shè)計(jì)得到了 Flutter 官方的保證,在未來的版本中會(huì)有兼容的優(yōu)勢(shì)。
FlutterEngineGroup 方案使用了多 Engine 混合模式,官方宣稱除了一個(gè) Engine 對(duì)象之外,后續(xù)每個(gè) Engine 對(duì)象在 Android 和 iOS 上僅占用 180kB 。
以前的方案每多一個(gè)Engine ,可能就會(huì)多出 19MB Android 和 13MB iOS 的占用。
從 Flutter 官方提供的例子上看,FlutterEngineGroup 的 API 十分簡單,多個(gè) Engine 實(shí)例的內(nèi)部都是獨(dú)立維護(hù)自己的內(nèi)部導(dǎo)航堆棧,所以可以做到每個(gè) Engine 對(duì)應(yīng)一個(gè)獨(dú)立的模塊。
所以使用 FlutterEngineGroup 之后,FlutterEngine 都將由 FlutterEngineGroup 去生成,生成的 FlutterEngine 可以獨(dú)立應(yīng)用于 FlutterActivity/FlutterViewController,甚至是 FlutterFragment :
所以就像例子上所示,你可以在一個(gè)
Activity上顯示兩個(gè)獨(dú)立的 FlutterView 。
這其實(shí)得益于通過 FlutterEngineGroup 生成的 FlutterEngine 可以共享 GPU 上下文, font metrics 和 isolate group snapshot ,從而實(shí)現(xiàn)了更快的初始速度和更低的內(nèi)存占用。
下圖是使用官方實(shí)例打開16個(gè)頁面之后的內(nèi)存使用情況,并且每個(gè)頁面成功返回且沒有出現(xiàn)黑屏。
簡單的使用介紹
使用 FlutterEngineGroup 首先需要?jiǎng)?chuàng)建一個(gè) FlutterEngineGroup 單例對(duì)象,之后每當(dāng)需要?jiǎng)?chuàng)建 Engine 時(shí),就通過它的 createAndRunEngine(activity, dartEntrypoint) 來創(chuàng)建對(duì)應(yīng)的 FlutterEngine 。
val app = activity.applicationContext as App
// This has to be lazy to avoid creation before the FlutterEngineGroup.
val dartEntrypoint =
DartExecutor.DartEntrypoint(
FlutterInjector.instance().flutterLoader().findAppBundlePath(), entrypoint
)
engine = app.engines.createAndRunEngine(activity, dartEntrypoint)
this.delegate = delegate
channel = MethodChannel(engine.dartExecutor.binaryMessenger, "multiple-flutters")
以官方 Demo 的這段代碼為例子:
1、首先通過 findAppBundlePath 和 entrypoint 創(chuàng)建出 DartEntrypoint 對(duì)象,這里的 findAppBundlePath 主要就是默認(rèn)的 flutter_assets 目錄;而 entrypoint 其實(shí)就是 dart 代碼里啟動(dòng)方法的名稱;也就是綁定了在 dart 中 runApp 的方法。
///kotlin
app.engines.createAndRunEngine(pathToBundle, "topMain")
///dart
@pragma('vm:entry-point')
void topMain() => runApp(MyApp());
2、通過上面創(chuàng)建的 dartEntrypoint 和 context ,使用 FlutterEngineGroup 就可以創(chuàng)建出對(duì)應(yīng)的 FlutterEngine ,其實(shí)在內(nèi)部就是通過FlutterJNI.nativeSpawn 和原有的引擎交互,得到新的 Long 地址 id。
在 C++ 層類似于原有的
RunBundleAndSnapshotFromLibrary方法,但是它不能更改包路徑或者 asset ,所以只能加載同一份 AOT 文件,這里得到的指針地址就是一個(gè)新的AndroidShellHolder。
3、最后利用生成的 FlutterEngine 的 binaryMessenger 來得到一個(gè) MethodChannel 用于原生和 dart 之間的通信。
通過上述流程得到的 Engine ,自然就可以直接用于渲染運(yùn)行新的 Flutter UI,比如直接繼承 FlutterActivity ,然后 override provideFlutterEngine 方法返回得到的 Engine 。
class SingleFlutterActivity : FlutterActivity()
·······
override fun provideFlutterEngine(context: Context): FlutterEngine? {
return engine
}
}
是不是很簡單?這么簡單的接入后:
- 在 dart 層面可以通過
MethodChannel打開原始頁面; - 在原生層可以通過新建
FlutterEngine打開新的 Flutter 頁面; - 甚至你還可以在原生層打開一個(gè)
FlutterView的 Dialog;
當(dāng)然,到這里你可能已經(jīng)注意到了,因?yàn)槊總€(gè) Flutter 頁面都是一個(gè)獨(dú)立的 Engine ,由于 dart isolate 的設(shè)計(jì)理念,每個(gè)獨(dú)立 Engine 的 Flutter 頁面內(nèi)存是無法共享的。
也就是說,當(dāng)你需要共享數(shù)據(jù)時(shí),只能在原生層持有數(shù)據(jù),然后注入或者傳遞到每個(gè) Flutter 頁面中,就像官方所說的,每個(gè) Flutter 頁面更像是一個(gè)獨(dú)立 Flutter 模塊。
當(dāng)然這也造成了一些不必要的麻煩,比如:同一張圖片,在原生層、不同 Flutter Engine 會(huì)出現(xiàn)多次加載的問題,這種問題可能就需要你針對(duì) Flutter 的圖片加載使用外界紋理,來實(shí)現(xiàn)在原生層統(tǒng)一的內(nèi)存管理等。
另外目前我發(fā)現(xiàn)問題還有: Android 11 上的 ARM TBI 問題 ,不過通過這次嘗試,相信 FlutterEngineGroup 的進(jìn)展將會(huì)越來越明朗,更早的被應(yīng)用到生產(chǎn)環(huán)境中。