1. 加載流程分析
flutter sdk版本:v3.0.3
dart版本:v2.17.5
1.1 FlutterApplication啟動流程
按照安卓原生啟動流程,當繼承FlutterApplication且在AndroidManifest.xml將application:name設(shè)置后,除Content Provider初始化,Application流程將首先執(zhí)行,此時FlutterApplication代碼如下:
FlutterApplication的onCreate只執(zhí)行了FlutterLoader類下的startInitialzation,并把自己作為Context傳入。
FlutterLoader對于startInitialzation有多個重載方法,此時將調(diào)用其中一個重載方法,并且生成了一個Settings傳入。
這時能看見startInitialization(@NonNull Context applicationContext, @NonNull Settings settings)才是實際執(zhí)行的方法,方法前半段限制了該方法只能運行在主線程,如運行在非主線程則直接拋出異常。
新建了一個initTask的Callable類型的回調(diào),并提交給了線程池執(zhí)行。此時可以看見flutterJni.loadLibrary(),這里就是在加載libflutter.so,flutterJni該成員變量是FlutterInjector.instance()生成單例時,通過工廠+建造者生成的一個實例。
initResultFuture將作為線程任務(wù)執(zhí)行的結(jié)果進行返回。
這說明了FlutterApplication執(zhí)行過程中,最重要的就是在子線程中System.loadLibrary("flutter"),并且在線程執(zhí)行完成后生成了initResultFuture,提供給后續(xù)操作。
1.2 FlutterActivity啟動流程
如果已繼承FlutterActivity作為LaunchActivity,且沒有設(shè)置自定義的Application,將不會執(zhí)行1.1的流程。
FlutterActivity時序圖如下所示:
(下圖出自得物技術(shù)專欄,鏈接:Flutter啟動流程分析之插件化升級探索_configureflutterengine_得物技術(shù)的博客-CSDN博客)
通過FlutterAcitivity的onCreate可以得知,大部分操作全部放在FlutterActivityAndFragmentDelegate中進行(代理模式)。
deletegate.onAttach將自己注入到方法。此時由于成員變量flutterEngine必定為空,則執(zhí)行setupFlutterEngine()。
進入setupFlutterEngine方法,此時可以通過getCachedEngineId,從FlutterEngineCache.getInstance()的map中,取出已經(jīng)生成的flutterEngine實例,getCachedEngineId()方法具體實現(xiàn)在FlutterActivity。這里說明在FlutterActivity啟動前預生成,可以有效減少onCreate()的時間。
當Cache和FlutterActivity子類中,都沒有提供FlutterEngine時,將自己生成一個FlutterEngine,其參數(shù)將通過FlutterActivity子類提供。此時可以發(fā)現(xiàn),通過FlutterActivity自動生成的Engine,automaticallyRegisterPlugins默認是為false的,這說明了此時需要手動注冊插件,在預生成的Engine中可以設(shè)置為true。
通過FlutterEngine的構(gòu)造方法,可以看見此時和FlutterApplication相似,生成了FlutterInjector的單例、flutterJni實例,并且初始化DartExecutor,生成了名為"flutter/isolate的channel,最后flutterJni與該executor的messager進行綁定。
此時由于flutterJni還未與Engine綁定,必定會執(zhí)行FlutterLoader的startInitialzation方法,具體邏輯1.1中已經(jīng)介紹。
之后將調(diào)用ensureInitialzationComplete的方法。
該方法執(zhí)行必定要在主線程中執(zhí)行,且Settings不能為空。
此時可以發(fā)現(xiàn),initResultFuture.get將掛起線程,并等待startInitialzation方法中執(zhí)行的子線程結(jié)果后,才會繼續(xù)向下執(zhí)行。
這時定義了一個shellArgs,用于存放執(zhí)行dart vm時攜帶的參數(shù),并且可以將外部的args傳入。
此時可以通過vm的參數(shù),發(fā)現(xiàn)JIT和AOT將加載不同的產(chǎn)物:
共同加載:libflutter.so
JIT:vm_snapshot_data、isolate_snapshot_data、kernel_blob.bin
AOT:libapp.so、libvmservice_snapshot.so(Profile)
在AOT的代碼段,注釋:
// Most devices can load the AOT shared library based on the library name
// with no directory path. Provide a fully qualified path to the library
// as a workaround for devices where that fails.
這里說明了當?shù)谝欢?/p>
"--" + AOT_SHARED_LIBRARY_NAME + "=" + flutterApplicationInfo.aotSharedLibraryName 路徑加載不到庫時,將自動加載第二段絕對路徑
"--"
+ AOT_SHARED_LIBRARY_NAME
+ "="
+ flutterApplicationInfo.nativeLibraryDir
+ File.separator
+ flutterApplicationInfo.aotSharedLibraryName
此時可以推斷,"--" + AOT_SHARED_LIBRARY_NAME + PATH,當加載成功庫,將不再執(zhí)行同參數(shù)的后續(xù)加載。
最后將用flutterJni.init將參數(shù)傳遞給nativeInit的jni映射方法中,在jni層啟動.
進入Jni層,可以通過flutter_main.cc文件下的Jni方法映射,找到實際引用的函數(shù)引用Init。
在Init方法中,將java層傳輸過來的args,增加了名叫flutter的參數(shù)
然后通過SettingsFromCommandLine生成了Settings類
Settings類中大部分都是作為設(shè)置的邏輯,在SettingsFromCommandLine方法中可以找到,aot_shared_library_name為數(shù)組,說明可以同時存在多個路徑。
并且全部存到Settings類的application_library_path中。
通過注解:
// Path to a library containing the application's compiled Dart code.
// This is a vector so that the embedder can provide fallback paths in
// case the primary path to the library can not be loaded.
可以得知,該屬性將作為默認加載路徑無法加載后的備選路徑。
通過dart vm的生命周期,dart_vm.cc文件中,Create方法會調(diào)用DartVMData的Create方法。
dart_vm_data.cc文件中的Create方法,將調(diào)用IsoSnapshotFromSettings函數(shù),該函數(shù)中會調(diào)用SearchMapping函數(shù)。
SearchMapping函數(shù)中表明了會根據(jù)Path查找NativeLibrary,并且查找native_library_symbol_name符號名,如果查找到,則不會繼續(xù)查找。此時可以證明只要Engine源碼中,不改變該符號名的名稱,則AOT產(chǎn)物替換加載方案為可行。
2. 熱更新Android方案
根據(jù)加載流程分析來看,如果不修改jni代碼,僅僅在java層進行改動,主要是在FlutterApplication與FlutterActivity上進行hook,將對于libapp.so的加載地址進行修改。
2.1 對于FlutterApplication的Hook
由1.1分析可以得出,F(xiàn)lutterApplication執(zhí)行過程中,已經(jīng)對于FlutterLoader進行了初始化,此時FlutterLoader的flutterInfoApplication屬性已經(jīng)填充了值,此時可以執(zhí)行之后,使用Reflect替換掉flutterInfoApplication里的內(nèi)容,將FlutterActivity加載庫的地址指向新的文件地址即可。
方案1:
描述:繼承FlutterApplication,修改flutterInfoApplication的aotSharedLibraryName屬性
侵入性:中
結(jié)果:無效
原因:
1.原flutterApplicationInfo的nativeLibraryDir指向的是/data/app,只讀不可寫權(quán)限且屬于私有文件目錄,無法移動下載的補丁文件到nativeLibraryDir的路徑下。
2.如將aotSharedLibraryName的絕對路徑,通過../強制轉(zhuǎn)成補丁文件目錄,會觸發(fā)jni路徑檢查錯誤。
錯誤信息如下:
library "/storage/emulated/0/Android/data/packagename/files/Download/-712256458372815974.so" ("/storage/emulated/0/Android/data/packagename/files/Download/-712256458372815974.so") needed or dlopened by "/data/app/~~F_tPgp6PDbt9Ug0opdFRsg==/packagename-W65G60j2V9GbWfwGQd_upw==/lib/arm64/libflutter.so" is not accessible for the namespace: [name="classloader-namespace", ld_library_paths="", default_library_paths="/data/app/~~F_tPgp6PDbt9Ug0opdFRsg==/packagename-W65G60j2V9GbWfwGQd_upw==/lib/arm64:/data/app/~~F_tPgp6PDbt9Ug0opdFRsg==/packagename-W65G60j2V9GbWfwGQd_upw==/base.apk!/lib/arm64-v8a", permitted_paths="/data:/mnt/expand:/data/data/packagename"]
從Log中可以看見libflutter.so文件對于加載的路徑,除了default_library_paths的,還有/data:/mnt/expand:/data/data/packagename?這2個路徑。
方案2:
描述:繼承FlutterApplication,修改flutterInfoApplication的aotSharedLibraryName、nativeLibraryDir屬性
侵入性:中
結(jié)果:無效
原因:原flutterApplicationInfo的nativeLibraryDir指向的是/data/app,只讀不可寫權(quán)限且屬于私有文件目錄,如將libflutter.so轉(zhuǎn)移到公共目錄,成本過高。且公共目錄會出現(xiàn)jni路徑檢查錯誤。具體原因可見方案1。
2.2 對于FlutterActivity的Hook
由1.2分析可以得出,即使onCreate執(zhí)行后,也不能完全保證flutterApplicationInfo的屬性被填充,必須等到子線程完成后才會注入數(shù)據(jù),所以不可以通過2.1的操作,對于flutterApplicationInfo通過反射注入自定義路徑。
通過1.2分析startInitialzationComplete方法,可以得出,F(xiàn)lutterActivity的args參數(shù),可以被傳入到方法中,與方法內(nèi)的參數(shù)進行合并,并傳入到j(luò)ni層。并且此時注入到args的路徑,必定順序在源代碼定義lib加載路徑之前,由此可以得出自定義路徑可以替代原路徑進行加載。
方案1:
描述:繼承FlutterActivity,重寫getFlutterShellArgs方法,使得vm args中加入一條指向新文件的路徑
侵入性:低
結(jié)果:有效
原因:通過1.2分析表明,jni層在根據(jù)路徑查找so文件時,如果查找到文件,且符號名正確,將不會繼續(xù)查找,所以修改getFlutterShellArgs傳入自定義的文件路徑,可以完成熱更新的操作。
注意:此時當下載到新文件時,必須將新文件從公共路徑(轉(zhuǎn)移/寫入)到/data/user/0/packageName路徑以下,由上面link的日志可以看出,/data/data路徑是可允許的加載路徑,而/data/data是/data/user/0的軟連接,且data/user/0/packageName路徑下是擁有可讀可寫權(quán)限的。
3.補丁下發(fā)方案設(shè)計
3.1補丁檢查
方案1:
在登錄賬號時觸發(fā)檢測,通過登錄時的接口來判定當時是否存在最新的補丁。
優(yōu)點:結(jié)構(gòu)簡單
缺點:如app不關(guān)閉且不重新登錄賬號,則無法觸發(fā)更新獲取,時效性差。服務(wù)器無法保證即時通知到app進行更新。
流程圖如下:
方案2:
在登錄賬號時建立長鏈接,通過服務(wù)器推送來判定是否存在補丁。
優(yōu)點:即時性高,服務(wù)器可以確保補丁下發(fā)過程中狀態(tài)是否正常。
缺點:結(jié)構(gòu)復雜,依賴于長鏈接的是否穩(wěn)定。
流程圖如下:
3.2 版本管理
用戶將上傳自身的包版本、補丁包版本和包名。
補丁下發(fā)將存在2種模式:
1.全量下發(fā)
上傳補丁后,將選擇要發(fā)布的版本號,然后將上傳補丁的分發(fā)至選擇的版本號,如該版本用戶本地補丁版本低于分發(fā)的補丁號,則將使用下發(fā)的補丁。
2.指定用戶下發(fā)文章來源:http://www.zghlxwxcb.cn/news/detail-470395.html
上傳補丁后,將針對指定用戶下發(fā)補丁。如該版本用戶本地補丁版本低于分發(fā)的補丁號,則將使用下發(fā)的補丁。文章來源地址http://www.zghlxwxcb.cn/news/detail-470395.html
到了這里,關(guān)于Flutter在Android上的熱更新方案的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!