柚子快報(bào)邀請(qǐng)碼778899分享:Flutter熱更新技術(shù)探索
一,需求背景:
APP 發(fā)布到市場(chǎng)后,難免會(huì)遇到嚴(yán)重的 BUG 阻礙用戶(hù)使用,因此有在不發(fā)布新版本 APP 的情況下使用熱更新技術(shù)立即修復(fù) BUG 需求。原生 APP(例如:Android & IOS)的熱更新需求已經(jīng)比較成熟,但 Flutter 技術(shù)棧目前還缺少類(lèi)似的技術(shù)方案,因此 Flutter 研發(fā)團(tuán)隊(duì),也需要類(lèi)似的熱更新技術(shù)。
二,F(xiàn)lutter 熱更新技術(shù)方向分析:
經(jīng)過(guò)分析目前可能有三種可行的方案:1)類(lèi)似 RN 框架;2)頁(yè)面動(dòng)態(tài)組件框架;3)Dart 虛擬機(jī)定制方案;
方案名稱(chēng)原理優(yōu)點(diǎn)缺點(diǎn)開(kāi)源方案類(lèi)似 RN 的方案用 JS 以 Flutter 語(yǔ)法寫(xiě) dart,然后用 JavaScript 把 XML DSL 轉(zhuǎn)為 Flutter 的原子 widget 組件,然后再讓 Flutter 來(lái)渲染由于 ios 系統(tǒng)內(nèi)置支持 js,ios 上完全可以實(shí)現(xiàn)更新1) 由于跨語(yǔ)言執(zhí)行,對(duì)于性能有影響;學(xué)習(xí)成本高 2) Android 端需要額外引入 JS 庫(kù)手 Q 的 MXFlutter,58 同城的 Fair頁(yè)面動(dòng)態(tài)組件方案編譯期時(shí)插樁 / 預(yù)埋好 DynamicWidget 到代碼中,然后動(dòng)態(tài)下發(fā) Json 數(shù)據(jù),通過(guò)協(xié)定好的語(yǔ)義匹配到 JSON 內(nèi)的數(shù)據(jù),動(dòng)態(tài)替換 Widget 內(nèi)容來(lái)實(shí)現(xiàn)更新能支持 Android/iOS 兩端的更新1)UI 更新相對(duì)較容易,業(yè)務(wù)邏輯動(dòng)態(tài)化較麻煩;2)語(yǔ)義解析器開(kāi)發(fā)成本相對(duì)較大,且不易維護(hù) 3)需要一整套前后端服務(wù)和工具天貓的 Tangram,淘寶的 DinamicX 等Dart 虛擬機(jī)定制方案通過(guò)分析 Dart 虛擬機(jī)的原理,修改 Flutter Engine 層 Java/C++ 代碼實(shí)現(xiàn)熱更新的目標(biāo);性能影響小,動(dòng)態(tài)性很高,技術(shù)上可以替換所有 Flutter 頁(yè)面(包括 UI,邏輯,資源文件)由于使用的是定制引擎,需要維護(hù)不同版本的 Flutter 引擎代碼;未開(kāi)源
因?yàn)槠渌绞蕉加虚_(kāi)源的示例,本案將重點(diǎn)以第三種 “Dart 虛擬機(jī)定制方案” 為目標(biāo),做方案的研究講解。
三,預(yù)備知識(shí)
在開(kāi)始了解技術(shù)方案之前,需要提前了解一些相應(yīng)的技術(shù)概念:
3.1 Flutter 編譯模式
Flutter 開(kāi)發(fā)語(yǔ)言是 Dart,它的編譯模式來(lái)自 Dart 的編譯模式,主要有 JIT (Just In Time) 和 AOT (Ahead Of Time)。
編譯模式名稱(chēng)特點(diǎn)優(yōu)點(diǎn)缺點(diǎn)JIT即時(shí)編譯,典型例子 V8,它可以即時(shí)編譯運(yùn)行 JS,只需要輸入源代碼字符串,就可以編譯運(yùn)行代碼可以動(dòng)態(tài)下發(fā)和執(zhí)行代碼,不用管 CPU 架構(gòu),可以提供動(dòng)態(tài)化內(nèi)容1, 大量字符串代碼讓 JIT 編譯器花費(fèi)時(shí)間和內(nèi)存;2, 性能不好;AOT預(yù)先編譯,典型例子 C/C++,通過(guò) GCC 編譯成二進(jìn)制代碼,然后安裝取得權(quán)限后才可以加載執(zhí)行事先編譯好的,加載和執(zhí)行速度快1, 編譯時(shí)區(qū)分 CPU 架構(gòu);2, 生成的二進(jìn)制代碼包比較大;3, 二進(jìn)制代碼需要取得權(quán)限才可以執(zhí)行,無(wú)法在 ios 系統(tǒng)上動(dòng)態(tài)更新
Flutter 編譯模式有:Debug,Release,Profile;
Flutter 編譯模式特點(diǎn)Debug對(duì)應(yīng) JIT 模式,支持設(shè)備和模擬器;打開(kāi)了斷言,支持快速開(kāi)發(fā),支持 HotReload;并未對(duì)包大小,執(zhí)行速度做優(yōu)化;Release對(duì)應(yīng) AOT 模式,支持真機(jī),不支持模擬器;禁止了所有斷言調(diào)試信息;對(duì)包大小,啟動(dòng)和執(zhí)行速度進(jìn)行了優(yōu)化;Profile類(lèi)似 Release 模式,保留了一些調(diào)試功能,幫助性能分析;
3.2 Flutter 編譯產(chǎn)物分析 Flutter 下的 iOS/Android 工程本質(zhì)上是一個(gè)標(biāo)準(zhǔn)的 iOS/Android 的工程;IOS 平臺(tái): Flutter 通過(guò)在 BuildPhase 中添加 shell (xcode_backend.sh) 來(lái)生成和嵌入 App.framework 和 Flutter.framework 到 ios; Android 平臺(tái): Flutter 通過(guò) gradle 來(lái)添加 flutter.jar 和編譯完的二進(jìn)制文件添加到 Android;3.2.1 引擎層結(jié)構(gòu)分析:
3.2.2 Android 編譯產(chǎn)物的分析
3.2.3 IOS 編譯產(chǎn)物的分析
四,熱更新技術(shù)方案分析
4.1 業(yè)務(wù)代碼分析
根據(jù) “3.3.1” ~“3.3.2” 的分析可以確定無(wú)論是 IOS 還是 Android APP 業(yè)務(wù)代碼都是由四個(gè)段組成:kDartVmSnapshotData、kDartVmSnapshotInstructions、kDartIsolateSnapshotData、kDartIsolateSnapshotInstructions;理論上只要能動(dòng)態(tài)替換加載的代碼段 & 數(shù)據(jù)段代碼即可實(shí)現(xiàn)目標(biāo)。
名稱(chēng)注釋作用注釋kDartIsolateSnapshotDataDart isolate 數(shù)據(jù)段類(lèi)信息,全局變量,函數(shù)指針等允許動(dòng)態(tài)下發(fā)kDartIsolateSnapshotInstructionsDart isolate 指令段包含由 Dart isolate 執(zhí)行的 AOT 代碼IOS 不允許動(dòng)態(tài)下發(fā)kDartVmSnapshotDatavm isolate 數(shù)據(jù)段isolate 之間共享的 Dart 堆 (heap) 的初始狀態(tài)允許動(dòng)態(tài)下發(fā)kDartVmSnapshotInstructionsvm isolate 指令段包含 VM 中所有 Dart isolate 之間共享的通用程序的 AOT 指令I(lǐng)OS 不允許動(dòng)態(tài)下發(fā)
注釋?zhuān)篿solate, snapshot, vm isolate 含義解釋如下:
名稱(chēng)含義isolateDart 是單線(xiàn)程,isolate 跟線(xiàn)程差不多,可以理解為 Dart 中的線(xiàn)程。isolate 與線(xiàn)程的區(qū)別:線(xiàn)程與線(xiàn)程之間是共享內(nèi)存的,而 isolate 和 isolate 之間是內(nèi)存不共享的。不存在鎖競(jìng)爭(zhēng)問(wèn)題,兩個(gè) Isolate 完全是兩條獨(dú)立的執(zhí)行線(xiàn),且每個(gè) Isolate 都有自己的事件循環(huán),它們之間只能通過(guò)發(fā)送消息通信,所以它的資源開(kāi)銷(xiāo)低于線(xiàn)程。snapshot將類(lèi)信息、全局變量、函數(shù)指令直接以序列化的方式存在磁盤(pán)中,稱(chēng)為 Snapshot(快照)。vm isolate同一個(gè)進(jìn)程里可以有很多 isolate,但兩個(gè) isolate 的堆區(qū)是不能共享的,所以官方設(shè)計(jì)了 VM isolate,也就是 kDartVmSnapshot,用來(lái)多個(gè) isolate 之間的交互。
4.2 業(yè)務(wù)代碼的加載分析(運(yùn)行時(shí))
按照 4.1 的分析思路,我們首先需要了解 Flutter 運(yùn)行時(shí)代碼加載的完整流程,經(jīng)過(guò)梳理分析流程如下:1 )Android- APP 業(yè)務(wù)代碼的加載流程:
2)IOS- APP 業(yè)務(wù)代碼的加載流程:
4.3 業(yè)務(wù)代碼的編譯生成(編譯時(shí))
根據(jù)以上的分析,我們知道了 Flutter 業(yè)務(wù)代碼的數(shù)據(jù)結(jié)構(gòu),也知道了在運(yùn)行時(shí)如何加載,因此我們只需要在編譯時(shí)做更改,產(chǎn)生自己需要的代碼段,和數(shù)據(jù)段文件。在運(yùn)行時(shí)加載自己的構(gòu)建產(chǎn)物即可達(dá)到目標(biāo)。1)在此以 IOS 構(gòu)建自己的業(yè)務(wù)代碼流程做詳細(xì)分析:
** 有完成構(gòu)建流程可以分析,基本流程是 “Dart Code(業(yè)務(wù)代碼)” -> (通過(guò) Dart 編譯器 gen_snapshot.cc) 生成 snapshot_assemble.S 的匯編文件 -> (通過(guò) xcrun 工具) 生成 snapshot_assemble.o 的 obj 文件 -> (通過(guò) xcun clang 工具鏈) 生成了 App.Framework。2)Android 的產(chǎn)物構(gòu)建流程和 IOS 類(lèi)似。由于 Android 有其他更簡(jiǎn)單的方案, 因此省略詳細(xì)的構(gòu)建流程分析,大致如下:
4.4 實(shí)現(xiàn)熱更新的方案探索
根據(jù)上面的技術(shù)分析結(jié)果,已經(jīng)可以獨(dú)立生成自己的代碼段,數(shù)據(jù)段文件。通過(guò)需改虛擬機(jī)底層代碼的方式,也可以動(dòng)態(tài)的加載運(yùn)行。但由于 IOS 系統(tǒng)目前底層的系統(tǒng)還不能動(dòng)態(tài)加載可讀寫(xiě)的代碼段數(shù)據(jù)到內(nèi)存中,所以還有技術(shù)難點(diǎn)需要突破。但 Android 端有更簡(jiǎn)單的路徑可以解決,因此下面以 Android 端為例重點(diǎn)分析思路,大致如下圖所示:
由上圖可以得知,Android 端 熱修復(fù)核心步驟如下:
1, 修改 Flutter Engine 代碼,加載指定路徑的 libapp.so 和 flutter_aasets,比如私有目錄 (data/data/files);
2, 編譯 APK 時(shí),利用 Gradle Transform 插件,根據(jù) Flutter SDK 的 engine version 動(dòng)態(tài)替換官方的 Flutter engine,最終寫(xiě)入修改后的 engine 到 APK;
3, 生成補(bǔ)丁包:利用 BSdiff 算法比較新舊 APK 文件,生成 patch 補(bǔ)丁包
4, APP 啟動(dòng)時(shí)訪(fǎng)問(wèn)后端接口,根據(jù)參數(shù)(app 的版本號(hào),補(bǔ)丁包版本號(hào),md5,flutter SDK 版本號(hào),Engine 版本號(hào))拉取補(bǔ)丁包;
5, 合成補(bǔ)丁包:校驗(yàn) md5,app 版本號(hào),補(bǔ)丁版本號(hào),安裝時(shí)間;
6, 自定義 Flutter Engine 加載指定路徑的 libapp.so 和 flutter_assets 資源文件;
柚子快報(bào)邀請(qǐng)碼778899分享:Flutter熱更新技術(shù)探索
相關(guān)鏈接
本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點(diǎn)和立場(chǎng)。
轉(zhuǎn)載請(qǐng)注明,如有侵權(quán),聯(lián)系刪除。