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