Flutter 2.0 下混合開發(fā)淺析

多余的前言

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_boostmix_stack 、 flutter_thrio 等等 ,它們是否好用這里不討論,但是這些方案都要面對(duì)的問題是:

非官方的支持必然存在每個(gè)版本需要適配的問題,而按照 Flutter 目前的 issue closedpr merge 的速度,很可能每個(gè)季度的版本都存在較大的變動(dòng),所以如果開發(fā)者不維護(hù)或者維護(hù)不及時(shí),那么侵入性極強(qiáng)的這類框架很容易就成為項(xiàng)目的瓶頸。

而官方提供的 FlutterEngineGroup 方案有沒有缺陷?肯定有的,它目前看起來更像是被催生出來的狀態(tài),各方面的問題還是有的,比如某些地方還存在不能 destroy 的問題。 (當(dāng)然這個(gè)問題以及在 master 分支 merge 了)

image.png

但是官方提供的方案,就意味著這個(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)黑屏。

image
image

簡單的使用介紹

使用 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、首先通過 findAppBundlePathentrypoint 創(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)建的 dartEntrypointcontext ,使用 FlutterEngineGroup 就可以創(chuàng)建出對(duì)應(yīng)的 FlutterEngine ,其實(shí)在內(nèi)部就是通過FlutterJNI.nativeSpawn 和原有的引擎交互,得到新的 Long 地址 id。

在 C++ 層類似于原有的 RunBundleAndSnapshotFromLibrary 方法,但是它不能更改包路徑或者 asset ,所以只能加載同一份 AOT 文件,這里得到的指針地址就是一個(gè)新的 AndroidShellHolder 。

3、最后利用生成的 FlutterEnginebinaryMessenger 來得到一個(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)境中。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容