日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > Android >内容正文

Android

技术实践 | Android Flutter 多实例实践

發(fā)布時(shí)間:2025/3/8 Android 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 技术实践 | Android Flutter 多实例实践 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

導(dǎo)讀:Flutter CLI 工具支持將 Flutter Module 打包成 Android AAR 包以供外部依賴(lài)使用,即 Flutter AAR。在一個(gè)沒(méi)有使用 Flutter 技術(shù)棧的 Android 工程中集成 Flutter AAR 是沒(méi)有任何問(wèn)題的,但如果目標(biāo)工程本身已經(jīng)使用了 Flutter 框架,在此基礎(chǔ)上再接入 Flutter AAR 就會(huì)失敗,我們稱(chēng)之為 Flutter 多實(shí)例問(wèn)題。本文主要介紹在 Android 平臺(tái)下 Flutter 多實(shí)例問(wèn)題的一種解決方案。

文|李成達(dá) 網(wǎng)易云信資深移動(dòng)端開(kāi)發(fā)工程師

企業(yè)的業(yè)務(wù)往往是復(fù)雜多樣的,如果是 ToC 的業(yè)務(wù),我們大多時(shí)候需要開(kāi)發(fā)一個(gè)體驗(yàn)良好的應(yīng)用 APP;而如果是 ToB 的業(yè)務(wù),我們往往需要提供一個(gè)易于接入和使用的 SDK。在 ToC 業(yè)務(wù)上,Flutter 框架提供的跨平臺(tái)、高效開(kāi)發(fā)與高性能特性,使得移動(dòng)端應(yīng)用開(kāi)發(fā)變得更加簡(jiǎn)單且高效;那在 ToB 業(yè)務(wù)上,SDK 的開(kāi)發(fā)是否能夠享受 Flutter 框架提供的這些紅利呢?這一點(diǎn)對(duì)于像我們網(wǎng)易云信這樣的服務(wù)、能力提供商而言尤為重要。網(wǎng)易云信是集網(wǎng)易 21 年 IM 以及音視頻技術(shù)打造的融合通信云服務(wù)專(zhuān)家,穩(wěn)定易用的通信與視頻 PaaS 平臺(tái),其服務(wù)大多以能力 SDK 的形式對(duì)外提供,如果能夠提高 SDK 的生產(chǎn)效率和研發(fā)效能,好處不言而喻。所以,上面的問(wèn)題答案當(dāng)然是肯定的!就像使用 Flutter 開(kāi)發(fā) APP 一樣,我們同樣可以使用 Flutter 進(jìn)行 SDK 開(kāi)發(fā),從而在 Android / iOS 甚至更多平臺(tái)中共享一致的業(yè)務(wù)邏輯實(shí)現(xiàn),減小人力、提高生產(chǎn)效率和研發(fā)效能。

在使用 Flutter 進(jìn)行 SDK 開(kāi)發(fā)時(shí),產(chǎn)物的打包方式主要有以下兩種形式:

  • Flutter Package / Flutter Plugin:該打包方式需要以 Dart 源碼形式發(fā)布到 Pub.dev 或 GitHub,第三方開(kāi)發(fā)者在接入時(shí)本質(zhì)上是以源碼的形式依賴(lài),同時(shí)接入方本地需要搭建并引入 Flutter 開(kāi)發(fā)環(huán)境。此種方式有明顯的缺陷:首先,源碼發(fā)布會(huì)將 SDK 內(nèi)部實(shí)現(xiàn)細(xì)節(jié)完全暴露在外( Flutter 框架并未提供類(lèi)似 Proguard 的混淆工具),這對(duì)企業(yè)的非開(kāi)源項(xiàng)目而言是不可接受的;其次,它變相要求接入方使用 Flutter 技術(shù)棧,這對(duì)于當(dāng)前沒(méi)有在目標(biāo)項(xiàng)目中使用 Flutter 開(kāi)發(fā)的接入方而言,門(mén)檻較高不說(shuō),接入體驗(yàn)也不太友好。

  • Android AAR:AAR 是 Android 應(yīng)用官方的依賴(lài)形式,并不存在明顯的短板。通過(guò) Flutter 框架提供的 CLI 工具,可以很方便地將 Flutter Module 打包成 AAR 發(fā)布出去,不用擔(dān)心泄漏業(yè)務(wù)源碼,也不損失接入體驗(yàn)。因?yàn)榇虬ぞ邥?huì)將 Flutter 層的業(yè)務(wù)代碼編譯成 AOT 共享庫(kù),而平臺(tái)層的 Java 業(yè)務(wù)代碼則可以開(kāi)啟混淆避免反編譯(為了簡(jiǎn)便,后面統(tǒng)一使用 Flutter AAR 命名由 Flutter Module 打包而成的 Android AAR 包)。

綜上所言,對(duì)于企業(yè)的一個(gè)商業(yè) SDK 項(xiàng)目來(lái)說(shuō),如果選擇使用 Flutter 技術(shù)棧進(jìn)行開(kāi)發(fā),那么使用 Flutter AAR 形式來(lái)發(fā)布才是明智之舉。但其實(shí)這又會(huì)引入新的問(wèn)題。在前文 Flutter 混合開(kāi)發(fā)基礎(chǔ)中我們介紹了,一個(gè) Flutter APP 的包結(jié)構(gòu),它包含有引擎庫(kù) `libflutter.so`、業(yè)務(wù)庫(kù) `libapp.so`、 以及`flutter_assets` 等部分。同理,一個(gè) Flutter Module 打包出來(lái)的 AAR 也會(huì)包含類(lèi)似的結(jié)構(gòu)以及產(chǎn)物文件。那在一個(gè) Flutter APP 中,應(yīng)該以何種姿勢(shì)接入 Flutter AAR 呢?可以預(yù)見(jiàn)的是,它們之間必然存在沖突,文件沖突已經(jīng)顯而易見(jiàn),類(lèi)、資源、甚至 Flutter Engine 也可能會(huì)沖突,這種常規(guī)的 Flutter AAR 包顯然是無(wú)法集成到 Flutter APP 工程中使用的。有問(wèn)題就有答案,接下來(lái),我們就一起來(lái)分析、探索該問(wèn)題的解決方案。

Flutter APP 集成 Flutter AAR 問(wèn)題分析

上面說(shuō)到 Flutter APP 無(wú)法集成常規(guī)打包出來(lái)的 Flutter AAR,因?yàn)榇嬖谝幌盗械臎_突,但具體會(huì)出現(xiàn)什么樣的錯(cuò)誤,還是需要我們真正動(dòng)手去集成才能知道。對(duì)這個(gè)環(huán)節(jié)感興趣的小伙伴可以親自動(dòng)手嘗試,不再贅述,下面直接給出結(jié)論說(shuō)明兩者共存存在哪些問(wèn)題:

  • 構(gòu)建失敗,其實(shí)就是因?yàn)槲募㈩?lèi)沖突導(dǎo)致編譯失敗。主要沖突有:

  • Flutter 版本依賴(lài)沖突:Flutter APP 宿主工程與 Flutter AAR 使用的 Flutter 版本不一致導(dǎo)致,包括 Flutter Embedding Jar 與 Flutter SO Jar,前者包含平臺(tái)層 Java 代碼,后者包含 libflutter.so 引擎庫(kù)文件。通過(guò) Gradle 我們可以解決這個(gè)依賴(lài)的版本沖突,例如強(qiáng)制使用其中某個(gè)版本,但這樣做極有可能會(huì)出現(xiàn)運(yùn)行時(shí)錯(cuò)誤。

  • Flutter Plugin 平臺(tái)代碼 / 資源沖突:Flutter APP 和 Flutter AAR 引用了相同的 Plugin 但版本不一致導(dǎo)致。插件中會(huì)包含平臺(tái)層的代碼,版本不一致同樣可能會(huì)導(dǎo)致編譯失敗或者運(yùn)行時(shí)錯(cuò)誤。

  • GeneratedPluginRegistrant.java 文件沖突:該文件為 Flutter 工具生成的插件自動(dòng)注冊(cè)類(lèi),用于 Flutter Engine 啟動(dòng)時(shí)自動(dòng)加載所需插件。Flutter APP 與 Flutter AAR 均有對(duì)應(yīng)的類(lèi)文件,負(fù)責(zé)加載各自依賴(lài)的插件,兩者缺一不可。

  • libapp.so 沖突:這是 Dart 代碼經(jīng)過(guò) AOT 生成的動(dòng)態(tài)庫(kù),Flutter APP 和 Flutter AAR 都會(huì)生成與其對(duì)應(yīng)的 so 庫(kù),我們不能單純的只使用它們其中之一,因?yàn)樗鼈儽旧戆?AOT 代碼是從不同的源碼編譯過(guò)來(lái)的。

  • 運(yùn)行時(shí)錯(cuò)誤

  • 同一個(gè) Flutter Engine 不支持加載多個(gè) AOT 庫(kù):Flutter Engine 在初始化時(shí)會(huì)動(dòng)態(tài)鏈接 libapp.so 這個(gè) AOT 庫(kù),解析其中的數(shù)據(jù)段,并執(zhí)行代碼段中的機(jī)器指令。但在我們的場(chǎng)景中,運(yùn)行時(shí)其實(shí)是包含有兩個(gè) AOT 庫(kù)的,它們都需要加載到 Flutter Engine 中來(lái),使用同一個(gè)Engine 是無(wú)法滿(mǎn)足需求的,因?yàn)樵?Flutter 的實(shí)現(xiàn)中,一個(gè) Engine 只能對(duì)應(yīng)一個(gè) AOT 庫(kù)。

  • 圖片資源、字體庫(kù)無(wú)法正常顯示:此類(lèi)資源會(huì)被打包至 flutter_assets 中,并且會(huì)生成對(duì)應(yīng)的 Manifest 資源描述清單文件。但 Flutter APP 生成的資源清單文件會(huì)覆蓋 Flutter AAR 中的資源清單文件,這樣導(dǎo)致 Flutter Engine 在加載資源時(shí),無(wú)法從清單文件中查詢(xún)到對(duì)應(yīng)的資源,因此加載失敗。

以上就是我們?cè)?Flutter APP 中接入 Flutter AAR 遇到的問(wèn)題。針對(duì)這些問(wèn)題,我們首先想到的是,Flutter Team 或者開(kāi)源社區(qū)是不是已經(jīng)有此類(lèi)問(wèn)題的解決方案了?但在經(jīng)過(guò)調(diào)研后發(fā)現(xiàn)目前并沒(méi)有。Flutter 框架是支持多個(gè) Engine 的,包括 Flutter 2.0 新支持的 Engine Group 僅支持加載和運(yùn)行同一個(gè) AOT 庫(kù)下的代碼,明顯不能滿(mǎn)足我們的需求。我們還給官方提了對(duì)應(yīng)Issue(https://github.com/flutter/flutter/issues/64542) 進(jìn)行討論,但是暫時(shí)還沒(méi)有得到滿(mǎn)意的解決方案,為此我們不得已走上了自己探索解決方案的自強(qiáng)之路。

解決方案探索

通過(guò)上面的分析,我們已經(jīng)了解了接入過(guò)程中出現(xiàn)的具體錯(cuò)誤以及出錯(cuò)原因。在真正著手探索解決方案前,還應(yīng)設(shè)立目標(biāo)解決方案應(yīng)該滿(mǎn)足的一些原則:

  • 首先方案應(yīng)該朝著最小引擎改動(dòng)、甚至無(wú)改動(dòng)的方向努力。因?yàn)?Flutter 框架一直在不斷迭代演進(jìn),如果我們修改了引擎這塊的邏輯,除非這些改動(dòng)能通過(guò) PR 進(jìn)入主干分支,否則引擎一旦更新,我們的方案就得重新適配,后期維護(hù)工作大。

  • 其次方案應(yīng)該盡量不依賴(lài)宿主工程做額外的改造或支持。首先 Flutter APP 接入 Flutter AAR 就跟普通 Android APP 接入 Android AAR 一樣簡(jiǎn)單,不應(yīng)引入額外的插件或是 Gradle 腳本;其次 Flutter AAR 和 Flutter APP 的 Flutter 運(yùn)行時(shí)環(huán)境應(yīng)該盡量隔離。

明確目標(biāo)之后,我們?cè)賮?lái)看看入手點(diǎn)在哪里。由于需要盡量避免引擎改動(dòng),那應(yīng)該是自上而下,首先從應(yīng)用層切入,看能否找到對(duì)策。這就需要我們深入源碼,從上到下了解 Flutter 框架的初始化、運(yùn)行機(jī)制。這里不做單獨(dú)講解,在具體問(wèn)題分析解決上再說(shuō)明。現(xiàn)在我們?cè)倩剡^(guò)頭來(lái)看最初遇到的一系列問(wèn)題,并嘗試運(yùn)用所掌握的 Android 、Flutter 框架知識(shí)來(lái)解決。

Class 沖突解決監(jiān)控?

Class 沖突是因?yàn)?Flutter AAR 與 Flutter APP 都有自己的 Plugins 依賴(lài)、以及可能會(huì)依賴(lài)不同版本的 Flutter Embedding Jar,這些依賴(lài)庫(kù)里都包含有平臺(tái)代碼,這會(huì)導(dǎo)致編譯期類(lèi)重復(fù)而失敗。那如何解決這個(gè)問(wèn)題呢?

最簡(jiǎn)單也是最暴力的方法就是對(duì) Flutter AAR 依賴(lài)的所有 Plugin 以及 Embedding Jar 源碼進(jìn)行重命名(修改類(lèi)名或者包名),雖然能解決問(wèn)題,但工作量巨大、修改面廣、不靈活,一旦 Plugin 或 Flutter 版本更新都需要重新修改。

那有沒(méi)有更好的辦法呢?答案是自定義 ClassLoader。具體的,在構(gòu)建 Flutter AAR 時(shí),在源代碼編譯成 .class 階段完成之后,將所有的插件、Flutter Embedding Jar 對(duì)應(yīng)的 .class 文件搜集起來(lái),打包成一個(gè) DEX 文件放入 Flutter AAR 的 assets 中。在運(yùn)行時(shí),需要將 assets 下的 DEX 文件拷貝到應(yīng)用的 data 私有目錄下,再通過(guò) DexClassLoader 去動(dòng)態(tài)加載這個(gè) DEX。這里需要注意的是 DEX 文件是版本號(hào)的概念的,它跟 Flutter AAR 的版本號(hào)是綁定的,意味著每次加載這個(gè) DEX 時(shí),我們首先需要檢查當(dāng)前私有目錄下的文件版本是否與 Flutter AAR 版本一致,一致則直接加載即可,不一致需要?jiǎng)h除原 DEX 文件并重新拷貝后再加載。關(guān)鍵代碼如下:

針對(duì) DEX 文件的加載一般而言我們只需要使用 DexClassLoader 這個(gè)系統(tǒng)類(lèi)就行了,但這里我們需要繼承 BaseDexClassLoader,并重寫(xiě) findClass 方法。

默認(rèn)類(lèi)的加載基于雙親委派模型,一般都是先請(qǐng)求父加載器加載,如果父加載器加載失敗子加載器才有機(jī)會(huì)加載。但在這里,我們 findClass 的邏輯需要反其道而行之。Flutter AAR 需要加載的類(lèi)應(yīng)該優(yōu)先使用子加載器從 DEX 文件中加載,加載失敗后才能通過(guò)父加載器加載。代碼如下:

庫(kù)文件沖突解決?

libflutter.so 是Flutter Engine 動(dòng)態(tài)庫(kù)文件,在運(yùn)行時(shí)會(huì)被 Flutter Embedder Jar 加載進(jìn)來(lái)。這個(gè)庫(kù)文件沖突,我們不能單純使用宿主中同名的庫(kù)文件,因?yàn)閮烧叩?Engine 版本可能不一致以及不違背運(yùn)行時(shí) Flutter ?版本隔離的目標(biāo)。

這里解決沖突最簡(jiǎn)單的方法就是重命名。通過(guò)閱讀代碼,我們發(fā)現(xiàn) Android 以 so 庫(kù)的路徑為 key 保存所有已經(jīng)加載的動(dòng)態(tài)庫(kù),即便是完全相同的 so 庫(kù),只要文件路徑不一致,就可以同時(shí) load 進(jìn)來(lái)。因此,這里通過(guò)重命名能解決文件沖突的問(wèn)題,也不會(huì)影響到 so 的加載。

libapp.so 沖突也是類(lèi)似的,我們同樣需要對(duì) Flutter AAR 中的 libapp.so 重命名。此外,我們還需要特殊處理這兩個(gè) so 的加載流程。因?yàn)?Flutter 運(yùn)行時(shí)硬編碼了動(dòng)態(tài)庫(kù)的名稱(chēng),如果不修改加載流程,在查考庫(kù)時(shí)就會(huì)找到 Flutter APP 生成的庫(kù)文件,而不是我們 Flutter AAR 的庫(kù)文件。

Flutter Engine 的初始化是在 FlutterLoader 這個(gè)類(lèi)中,在這里會(huì)加載 libflutter.so 并配置一系列的參數(shù)初始化 Native Engine。我們需要做的就是替換 libflutter.so 的加載邏輯,轉(zhuǎn)而去加載重命名后的 Engine 庫(kù)文件。對(duì)于 libapp.so ,它并不是在 Java 層加載的,而是由 Native Engine 通過(guò) dlopen 鏈接的。通過(guò)查閱 Engine 的代碼我們發(fā)現(xiàn)通過(guò) --aot-shared-library-name 選項(xiàng)可以設(shè)置要加載的目標(biāo) libapp.so 路徑。關(guān)鍵代碼如下:

Flutter 資源沖突解決?

Flutter 相關(guān)資源是打包放到 assets 目錄下的,且通過(guò)對(duì)應(yīng)的 Manifest 文件來(lái)聲明,分別是:FontManifest.json 與 AssetsManifest.json 文件。這兩個(gè)文件分別列出了 Flutter 依賴(lài)的所有字體資源與路徑映射關(guān)系、圖片資源與路徑映射關(guān)系。

Flutter-Engine 在運(yùn)行時(shí)通過(guò)這兩個(gè)文件來(lái)解析圖片與字體資源,Flutter AAR 中雖然也包含了這兩個(gè)文件,但會(huì)被 Flutter APP 宿主中的同名文件覆蓋,導(dǎo)致字體或資源無(wú)法加載。所以,這里有兩個(gè)簡(jiǎn)單方案:

  • 支持編譯期合并對(duì)應(yīng)的資源清單 json 文件;這需要開(kāi)發(fā) Plugin 插件供宿主使用,實(shí)現(xiàn)復(fù)雜而且接入不友好。

  • Flutter AAR 中抽離出一個(gè)獨(dú)立的資源包 Package 供 Flutter APP 依賴(lài),資源包中僅包含 Flutter AAR 引用的所有圖片、字體資源(不包含任何業(yè)務(wù)邏輯,因此可以放心的發(fā)布到pub平臺(tái)),宿主在 Flutter 層依賴(lài)這個(gè) Package,這樣宿主在構(gòu)建時(shí) Flutter 工具會(huì)合并所有的的資源,并生成完整的資源清單文件。

至此,我們解決了 Flutter AAR 與 Flutter APP 的共存問(wèn)題。當(dāng)然整個(gè)方案落地下來(lái),其中還會(huì)碰到其他一些問(wèn)題,比如:生成的 DEX 文件需要訪(fǎng)問(wèn)宿主中的其他類(lèi)的時(shí)候,在混淆啟用的情況下,應(yīng)該如何保證 DEX 訪(fǎng)問(wèn)主ClassLoader中的類(lèi)、方法沒(méi)有問(wèn)題;再如:Flutter AAR 的 DEX 中如果包含有 Android 組件怎么辦?Android 四大組件都是需要由應(yīng)用的主ClassLoader進(jìn)行加載的,如果主 DEX 中沒(méi)有包含這些類(lèi),那么肯定啟動(dòng)失敗;等等諸如此類(lèi)問(wèn)題,這里不再一一列舉。

結(jié)語(yǔ)

下圖所示為 Flutter 多實(shí)例運(yùn)行時(shí)的架構(gòu)圖。類(lèi)似于多 Flutter Engine,以上方案實(shí)現(xiàn)的多 Flutter 實(shí)例,也是通過(guò)創(chuàng)建多個(gè) Native 的 AndroidShellHolder 來(lái)實(shí)現(xiàn)的。不同的是,在多 Engine 下不同的 ShellHolder 綁定相同的 libapp.so,而多實(shí)例下綁定的是不同的 libapp.so ,因此該方案能在運(yùn)行時(shí)隔離 Flutter APP 與 Flutter AAR 的 Flutter 運(yùn)行時(shí)環(huán)境。

該方案的主要優(yōu)勢(shì)表現(xiàn)在:

  • 無(wú) Engine 定制,可維護(hù)性較高

  • Flutter APP 與 Flutter AAR 的 Flutter 版本、運(yùn)行時(shí)環(huán)境相互獨(dú)立

有得必有失,相對(duì)地,在其他方面,該方案有所不足:

  • 使用了獨(dú)立的 Flutter Engine 庫(kù)文件,因此會(huì)導(dǎo)致包體積增加

  • 會(huì)加載兩個(gè)不同的 Flutter Engine ,內(nèi)存會(huì)有所增加

綜上,在 SDK 開(kāi)發(fā)中采用 Flutter 技術(shù),同樣能夠發(fā)揮 Flutter 在 APP 開(kāi)發(fā)中的優(yōu)勢(shì),前提是我們能夠解決好 Flutter 多實(shí)例的問(wèn)題。本文主要講解了 Android Flutter 多實(shí)例的一種實(shí)現(xiàn)思路,希望能夠?qū)Υ蠹矣兴鶐椭?/p>

本文作者?

李成達(dá),網(wǎng)易云信資深移動(dòng)端開(kāi)發(fā)工程師,熱衷于研究跨平臺(tái)開(kāi)發(fā)技術(shù)以及工程提效,目前主要負(fù)責(zé)視頻會(huì)議組件化 SDK 的相關(guān)研發(fā)工作。

*各渠道文章轉(zhuǎn)載需注明來(lái)源及作者

總結(jié)

以上是生活随笔為你收集整理的技术实践 | Android Flutter 多实例实践的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。