序
為了加速應用冷啟動過程且不過度涉及業務改動,本文從虛擬機加載類的過程中找到優化項,且與業界的方案作了對比,并實現了半自動化的分析功能。類在使用或實例化之前需要被加載到虛擬機中并進行初始化。整個過程如下圖所示:主要由LoadingClass和InitializingClass兩部分組合。
LoadingClass旨在把Class從Dex加載到虛擬機中,但不涉及類的使用或執行流程。InitializingClass旨在保證使用類前已經經過了初始化流程,此流程嵌入類的使用或執行過程中。
加載類
DefineClass主要通過SetupClass、InsertClass以及LoadClass將一個類加載到虛擬機中,最后返回mirror:Class對象指針。
SetupClass:設置類的訪問標志以及ClassLoader。
InsertClass:將類插入到對應ClassLoader的ClassTable中,以便查找。
LoadClass:將類的屬性及方法加載到類中。
類初始化
類的屬性或方法在使用前必須經過類的初始化。
InitializeClass:核驗類、初始化父類、接口方法以及靜態屬性。
VerifyClass:核驗類的合法性,在下一節詳細分析。
核驗類
VerifyClass使用VerifyClassUsingOatFile或PerformClassVerification方法之一去核查Class。其中PerformClassVerification就包含了Systrace中耗時VerifyClass的Tag,如下圖所示:
VerifyClassUsingOatFile:通過Oat文件中的Class狀態位去核驗Class,當狀態位等于kStatusVerified時,核查流程到此為止,直接快速返回。否則需要進入耗時的PerformClassVerification流程。
PerformClassVerification:主要核驗類中的直接方法和虛方法。
ComputeWidthsAndCountOps:判斷PC值與dalvik指令數是否相等。
ScanTryCatchBlocks:檢查Try語句開始地址、結束地址以及try開始操作符的合法性。檢查catch中handler語句開始操作符的合法性。
VerifyInstructions:檢查各種dalvik指令,同時將GC檢查點插入到括號、switch、throw指令中。
VerifyCodeFlow:檢查每條dalvik指令的寄存器以及參數的合法性。
提前發現
從上面的分析可以看出,應該盡可能讓核查走VerifyClassUsingOatFile流程,即通過Oat文件狀態位核查成功。Oat文件中類的狀態位是什么以及為什么狀態位不等于kStatusVerified是問題的突破點。
通過oatdump命令去dump相應的odex文件,可以查看類的狀態位,操作方式如下:
VLOG默認是不會被打印的,需要動態開啟,開啟的方式可以通過:art::gLogVerbosity.class_linker = true而打開,因為本項目需要看到dex2oat和其他進程的打印情況,本人是在系統源碼中進行編譯生成的so,然后,通過ptrace注入so到Zygote的,此方法需要root設備,如果只需要查看本進程,應不需要這么麻煩,具體方法還未探索,但思路應該是一致的。舉例如下,本人碰到的問題是AppCompat包中的類不能被核驗通過。
解決方案
將Runtime對象中的verify_設置成verifier::VerifyMode::kNone。
需要通過Runtime對象首地址遍歷查找verify_屬性,魔改廠商可能帶來兼容性問題。
缺少VerifyClass過程,可能會后置發現非法指令問題。
對zygote中值verify_進行修改將造成cow內存消耗。
將多出EnsureSkipAccessChecksMethods一步處理邏輯,將類中每個函數flag進行修改,此處邏輯沒有對單個類進行處理,所以,每個類的每個函數的flag都將被無謂修改,如下圖所示:
直面問題本身,通過VLOG的輸出信息,去修正源碼,具體到本案例,是由于AppCompat庫中使用了系統不支持的語句,如下圖所示:
本App運行環境是在8.1(API27)上,TextView沒有方法setFirstBaselineToTopHeight,所以,因為指令非法導致類核驗失敗。(注意Build.VERSION.SDK_INT是不會被編譯優化的,它本身是final類型,但它的取值是等于SystemProperties.getInt(“ro.build.version.sdk”, 0),所以,必須運行時,才能確定)。本人嘗試了如下方法:
將系統源碼sdk中的Build.VERSION.SDK_INT值設置成27進行編譯出新的sdk,然后,將此sdk覆蓋源生的android.jar,希望編譯時將appcompat中的Build.VERSION.SDK_INT 》= 28判斷邏輯優化掉,但實際aar不會參與sdk的編譯,此項只能優化項目自身的邏輯。
將appcompat源碼下載下來,去掉非法指令,重新編譯成aar使用。
直接在android8.1源碼中編譯support v7包使用。
以上兩種方法,能定制自己所需的aar,甚至能裁剪資源,但碰到了致命的問題:新生成的aar不能發布到maven了,這樣的話,需要推動業務修改包名,另一個問題是,如果是項目中的第三方aar依賴了appcompat的話,問題又會出現。所以,最終通過制作ASM插件,將Build.VERSION.SDK_INT值設置成固定27,問題解決了,且使得本項目中apk size減少了22K。
如果是應用需要兼容多個不同版本的ROM,也可以按照ROM版本的不同,使用App Bundle下發“最合適”的App。
平臺化
為了降低方案實施難度,現已將方案平臺化,只要將apk拖入網頁中即可看到類核驗不通過的原因。
編輯:lyn
-
源碼
+關注
關注
8文章
647瀏覽量
29293 -
虛擬機
+關注
關注
1文章
919瀏覽量
28285
發布評論請先 登錄
相關推薦
評論