Android Flutter 多實例實踐

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"引言","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Flutter CLI 工具支持將 Flutter Module 打包成 Android AAR 包以供外部依賴使用,即 Flutter AAR。在一個沒有使用 Flutter 技術棧的 Android 工程中集成 Flutter AAR 是沒有任何問題的,但如果目標工程本身已經使用了 Flutter 框架,在此基礎上再接入 Flutter AAR 就會失敗,我們稱之爲 Flutter 多實例問題。","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"本文主要介紹在 Android 平臺下 Flutter 多實例問題的一種解決方案。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"背景","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"企業的業務往往是複雜多樣的,如果是 ToC 的業務,我們大多時候需要開發一個體驗良好的應用 APP;而如果是 ToB 的業務,我們往往需要提供一個易於接入和使用的 SDK。在 ToC 業務上,Flutter 框架提供的跨平臺、高效開發與高性能特性,使得移動端應用開發變得更加簡單且高效;那在 ToB 業務上,SDK 的開發是否能夠享受 Flutter 框架提供的這些紅利呢?這一點對於像我們網易雲信這樣的服務、能力提供商而言尤爲重要。網易雲信是集網易 21 年 IM 以及音視頻技術打造的融合通信雲服務專家,穩定易用的通信與視頻 PaaS 平臺,其服務大多以能力 SDK 的形式對外提供,如果能夠提高 SDK 的生產效率和研發效能,好處不言而喻。所以,上面的問題答案當然是肯定的!就像使用 Flutter 開發 APP 一樣,我們同樣可以使用 Flutter 進行 SDK 開發,從而在 Android / iOS 甚至更多平臺中共享一致的業務邏輯實現,減小人力、提高生產效率和研發效能。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在使用 Flutter 進行 SDK 開發時,產物的打包方式主要有以下兩種形式:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Flutter Package / Flutter Plugin","attrs":{}},{"type":"text","text":":該打包方式需要以 Dart 源碼形式發佈到 Pub.dev 或 GitHub,第三方開發者在接入時本質上是以源碼的形式依賴,同時接入方本地需要搭建並引入 Flutter 開發環境。此種方式有明顯的缺陷:首先,源碼發佈會將 SDK 內部實現細節完全暴露在外( Flutter 框架並未提供類似 Proguard 的混淆工具),這對企業的非開源項目而言是不可接受的;其次,它變相要求接入方使用 Flutter 技術棧,這對於當前沒有在目標項目中使用 Flutter 開發的接入方而言,門檻較高不說,接入體驗也不太友好。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Android AAR","attrs":{}},{"type":"text","text":":AAR 是 Android 應用官方的依賴形式,並不存在明顯的短板。通過 Flutter 框架提供的 CLI 工具,可以很方便地將 Flutter Module 打包成 AAR 發佈出去,不用擔心泄漏業務源碼,也不損失接入體驗。因爲打包工具會將 Flutter 層的業務代碼編譯成 AOT 共享庫,而平臺層的 Java 業務代碼則可以開啓混淆避免反編譯(爲了簡便,後面統一使用 Flutter AAR 命名由 Flutter Module 打包而成的 Android AAR 包)。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"綜上所言,對於企業的一個商業 SDK 項目來說,如果選擇使用 Flutter 技術棧進行開發,那麼使用 Flutter AAR 形式來發布纔是明智之舉。但其實這又會引入新的問題。在前文 ","attrs":{}},{"type":"link","attrs":{"href":"https://mp.weixin.qq.com/s/nFsko8urXj_y0t0SzAz_ug","title":null,"type":null},"content":[{"type":"text","text":"Flutter 混合開發基礎","attrs":{}}]},{"type":"text","text":" 中我們介紹了,一個 Flutter APP 的包結構,它包含有引擎庫 `libflutter.so`、業務庫 `libapp.so`、 以及`flutter_assets` 等部分。同理,一個 Flutter Module 打包出來的 AAR 也會包含類似的結構以及產物文件。那在一個 Flutter APP 中,應該以何種姿勢接入 Flutter AAR 呢?可以預見的是,它們之間必然存在衝突,文件衝突已經顯而易見,類、資源、甚至 Flutter Engine 也可能會衝突,這種常規的 Flutter AAR 包顯然是無法集成到 Flutter APP 工程中使用的。有問題就有答案,接下來,我們就一起來分析、探索該問題的解決方案。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Flutter APP 集成 Flutter AAR 問題分析","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上面說到 Flutter APP 無法集成常規打包出來的 Flutter AAR,因爲存在一系列的衝突,但具體會出現什麼樣的錯誤,還是需要我們真正動手去集成才能知道。這個環節感興趣的小夥伴可以親自動手嘗試,不再贅述,下面直接給出結論說明兩者共存存在哪些問題:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"構建失敗,其實就是因爲文件、類衝突導致編譯失敗。主要衝突有:","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Flutter 版本依賴衝突:Flutter APP 宿主工程與 Flutter AAR 使用的 Flutter 版本不一致導致,包括 Flutter Embedding Jar 與 Flutter SO Jar,前者包含平臺層 Java 代碼,後者包含 libflutter.so 引擎庫文件。通過 Gradle 我們可以解決這個依賴的版本衝突,例如強制使用其中某個版本,但這樣做極有可能會出現運行時錯誤。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Flutter Plugin 平臺代碼 / 資源衝突:Flutter APP 和 Flutter AAR 引用了相同的 Plugin 但版本不一致導致。插件中會包含平臺層的代碼,版本不一致同樣可能會導致編譯失敗或者運行時錯誤。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"GeneratedPluginRegistrant.java 文件衝突:該文件爲 Flutter 工具生成的插件自動註冊類,用於 Flutter Engine 啓動時自動加載所需插件。Flutter APP 與 Flutter AAR 均有對應的類文件,負責加載各自依賴的插件,兩者缺一不可。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"libapp.so 衝突:這是 Dart 代碼經過 AOT 生成的動態庫,Flutter APP 和 Flutter AAR 都會生成與其對應的 so 庫,我們不能單純的只使用它們其中之一,因爲它們本身包含的 AOT 代碼是從不同的源碼編譯過來的。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"運行時錯誤","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同一個 Flutter Engine 不支持加載多個 AOT 庫:Flutter Engine 在初始化時會動態鏈接 libapp.so 這個 AOT 庫,解析其中的數據段,並執行代碼段中的機器指令。但在我們的場景中,運行時其實是包含有兩個 AOT 庫的,它們都需要加載到 Flutter Engine 中來,使用同一個Engine 是無法滿足需求的,因爲在 Flutter 的實現中,一個 Engine 只能對應一個 AOT 庫。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"圖片資源、字體庫無法正常顯示:此類資源會被打包至 flutter_assets 中,並且會生成對應的 Manifest 資源描述清單文件。但 Flutter APP 生成的資源清單文件會覆蓋 Flutter AAR 中的資源清單文件,這樣導致 Flutter Engine 在加載資源時,無法從清單文件中查詢到對應的資源,因此加載失敗。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以上就是我們在 Flutter APP 中接入 Flutter AAR 遇到的問題。針對這些問題,我們首先想到的是,Flutter Team 或者開源社區是不是已經有此類問題的解決方案了?但在經過調研後發現目前並沒有。Flutter 框架是支持多個 Engine 的,包括 Flutter 2.0 新支持的 Engine Group 僅支持加載和運行同一個 AOT 庫下的代碼,明顯不能滿足我們的需求。我們還給官方提了對應 Issue(https://github.com/flutter/flutter/issues/64542) 進行討論,但是暫時還沒有得到滿意的解決方案,爲此我們不得已走上了自己探索解決方案的自強之路。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"解決方案探索","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過上面的分析,我們已經瞭解了接入過程中出現的具體錯誤以及出錯原因。在真正着手探索解決方案前,還應設立目標解決方案應該滿足的一些原則:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先方案應該朝着最小引擎改動、甚至無改動的方向努力。因爲 Flutter 框架一直在不斷迭代演進,如果我們修改了引擎這塊的邏輯,除非這些改動能通過 PR 進入主幹分支,否則引擎一旦更新,我們的方案就得重新適配,後期維護工作大。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其次方案應該儘量不依賴宿主工程做額外的改造或支持。首先 Flutter APP 接入 Flutter AAR 就跟普通 Android APP 接入 Android AAR 一樣簡單,不應引入額外的插件或是 Gradle 腳本;其次 Flutter AAR 和 Flutter APP 的 Flutter 運行時環境應該儘量隔離。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"明確目標之後,我們再來看看入手點在哪裏。由於需要儘量避免引擎改動,那應該是自上而下,首先從應用層切入,看能否找到對策。這就需要我們深入源碼,從上到下了解 Flutter 框架的初始化、運行機制。這裏不做單獨講解,在具體問題分析解決上再說明。現在我們再回過頭來看最初遇到的一系列問題,並嘗試運用所掌握的 Android 、Flutter 框架知識來解決。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Class 衝突解決","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Class 衝突是因爲 Flutter AAR 與 Flutter APP 都有自己的 Plugins 依賴、以及可能會依賴不同版本的 Flutter Embedding Jar,這些依賴庫裏都包含有平臺代碼,這會導致編譯期類重複而失敗。那如何解決這個問題呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最簡單也是最暴力的方法就是對 Flutter AAR 依賴的所有 Plugin 以及 Embedding Jar 源碼進行重命名(修改類名或者包名),雖然能解決問題,但工作量巨大、修改面廣、不靈活,一旦 Plugin 或 Flutter 版本更新都需要重新修改。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那有沒有更好的辦法呢?答案是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"自定義ClassLoader","attrs":{}},{"type":"text","text":"。具體的,在構建 Flutter AAR 時,在源代碼編譯成 .class 階段完成之後,將所有的插件、Flutter Embedding Jar 對應的 .class 文件蒐集起來,打包成一個 DEX 文件放入 Flutter AAR 的 assets 中。在運行時,需要將 assets 下的 DEX 文件拷貝到應用的 data 私有目錄下,再通過 DexClassLoader 去動態加載這個 DEX。這裏需要注意的是 DEX 文件是版本號的概念的,它跟 Flutter AAR 的版本號是綁定的,意味着每次加載這個 DEX 時,我們首先需要檢查當前私有目錄下的文件版本是否與 Flutter AAR 版本一致,一致則直接加載即可,不一致需要刪除原 DEX 文件並重新拷貝後再加載。關鍵代碼如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/41/4102ed23983215bad709e2201fa03398.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/bd/bd40c84a1c86f9e7d955fa5af61d707e.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"針對 DEX 文件的加載一般而言我們只需要使用 DexClassLoader 這個系統類就行了,但這裏我們需要繼承 BaseDexClassLoader,並重寫 findClass 方法。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"默認類的加載基於雙親委派模型,一般都是先請求父加載器加載,如果父加載器加載失敗子加載器纔有機會加載。但在這裏,我們 findClass 的邏輯需要反其道而行之。Flutter AAR 需要加載的類應該優先使用子加載器從 DEX 文件中加載,加載失敗後才能通過父加載器加載。代碼如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/27/279f9b5aff52158d48d4cb8e3d03a3e1.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"庫文件衝突解決","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"libflutter.so 是Flutter Engine 動態庫文件,在運行時會被 Flutter Embedder Jar 加載進來。這個庫文件衝突,我們不能單純使用宿主中同名的庫文件,因爲兩者的 Engine 版本可能不一致以及不違背運行時 Flutter 版本隔離的目標。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏解決衝突最簡單的方法就是","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"重命名","attrs":{}},{"type":"text","text":"。通過閱讀代碼,我們發現 Android 以 so 庫的路徑爲 key 保存所有已經加載的動態庫,即便是完全相同的 so 庫,只要文件路徑不一致,就可以同時 load 進來。因此,這裏通過重命名能解決文件衝突的問題,也不會影響到 so 的加載。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"libapp.so 衝突也是類似的,我們同樣需要對 Flutter AAR 中的 libapp.so 重命名。此外,我們還需要特殊處理這兩個 so 的加載流程。因爲 Flutter 運行時硬編碼了動態庫的名稱,如果不修改加載流程,在查考庫時就會找到 Flutter APP 生成的庫文件,而不是我們 Flutter AAR 的庫文件。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Flutter Engine 的初始化是在 FlutterLoader 這個類中,在這裏會加載 libflutter.so 並配置一系列的參數初始化 Native Engine。我們需要做的就是替換 libflutter.so 的加載邏輯,轉而去加載重命名後的 Engine 庫文件。對於 libapp.so ,它並不是在 Java 層加載的,而是由 Native Engine 通過 dlopen 鏈接的。通過查閱 Engine 的代碼我們發現通過 ","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"--aot-shared-library-name","attrs":{}},{"type":"text","text":" 選項可以設置要加載的目標 libapp.so 路徑。關鍵代碼如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/80/80438f2b18258c92f24c7babdb917600.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Flutter 資源衝突解決","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Flutter 相關資源是打包放到 assets 目錄下的,且通過對應的 Manifest 文件來聲明,分別是:","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"FontManifest.json 與 AssetsManifest.json ","attrs":{}},{"type":"text","text":"文件。這兩個文件分別列出了 Flutter 依賴的所有字體資源與路徑映射關係、圖片資源與路徑映射關係。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Flutter-Engine 在運行時通過這兩個文件來解析圖片與字體資源,Flutter AAR 中雖然也包含了這兩個文件,但會被 Flutter APP 宿主中的同名文件覆蓋,導致字體或資源無法加載。所以,這裏有兩個簡單方案:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"支持編譯期合併對應的資源清單 json 文件;這需要開發 Plugin 插件供宿主使用,實現複雜而且接入不友好;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"Flutter AAR 中抽離出一個獨立的資源包 Package 供 Flutter APP 依賴,資源包中僅包含 Flutter AAR 引用的所有圖片、字體資源(不包含任何業務邏輯,因此可以放心的發佈到pub平臺),宿主在 Flutter 層依賴這個 Package,這樣宿主在構建時 Flutter 工具會合並所有的的資源,並生成完整的資源清單文件","attrs":{}},{"type":"text","text":"。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至此,我們解決了 Flutter AAR 與 Flutter APP 的共存問題。當然整個方案落地下來,其中還會碰到其他一些問題,比如:生成的 DEX 文件需要訪問宿主中的其他類的時候,在混淆啓用的情況下,應該如何保證 DEX 訪問主ClassLoader中的類、方法沒有問題;再如:Flutter AAR 的 DEX 中如果包含有 Android 組件怎麼辦?Android 四大組件都是需要由應用的主ClassLoader進行加載的,如果主 DEX 中沒有包含這些類,那麼肯定啓動失敗;等等諸如此類問題,這裏不再一一列舉。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"總結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下圖所示爲 Flutter 多實例運行時的架構圖。類似於多 Flutter Engine,以上方案實現的多 Flutter 實例,也是通過創建多個 Native 的 AndroidShellHolder 來實現的。不同的是,在多 Engine 下不同的 ShellHolder 綁定相同的 libapp.so,而多實例下綁定的是不同的 libapp.so ,因此該方案能在運行時隔離 Flutter APP 與 Flutter AAR 的 Flutter 運行時環境。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/66/66ed58df8353a64be956d57c5c195b24.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"該方案的主要優勢表現在:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"無 Engine 定製,可維護性較高","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Flutter APP 與 Flutter AAR 的 Flutter 版本、運行時環境相互獨立","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有得必有失,相對地,在其他方面,該方案有所不足:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用了獨立的 Flutter Engine 庫文件,因此會導致包體積增加","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"會加載兩個不同的 Flutter Engine ,內存會有所增加","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"綜上,在 SDK 開發中採用 Flutter 技術,同樣能夠發揮 Flutter 在 APP 開發中的優勢,前提是我們能夠解決好 Flutter 多實例的問題。本文主要講解了 Android Flutter 多實例的一種實現思路,希望能夠對大家有所幫助。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"作者簡介","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"李成達,網易雲信資深移動端開發工程師,熱衷於研究跨平臺開發技術以及工程提效,目前主要負責視頻會議組件化 SDK 的相關研發工作。","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章