天天看點

都2020年了Andoid還能如何性能優化(1)—— 啟動速度優化一.啟動類型二.如何檢測啟動耗時三.啟動優化進階方法

一.啟動類型

冷啟動

指程序死亡的情況下,從點選應用圖示到UI界面完全顯示且使用者可操作的全部過程。

大緻流程:

Click Event -> IPC -> Process.start -> ActivityThread -> bindApplication -> LifeCycle -> ViewRootImpl

使用者點選桌面圖示,這個點選事件它會觸發一個IPC的操作,之後便會執行到Process的start方法中,這個方法是用于程序建立的,接着,便會執行到ActivityThread的main方法,這個方法可以看做是我們單個App程序的入口,相當于Java程序的main方法,在其中會執行消息循環的建立與主線程Handler的建立,建立完成之後,就會執行到 bindApplication 方法,在這裡使用了反射去建立 Application以及調用了 Application相關的生命周期,Application結束之後,便會執行Activity的生命周期,在Activity生命周期結束之後,最後,就會執行到 ViewRootImpl,這時才會進行真正的一個頁面的繪制。

熱啟動

即程序存活情況下,點選桌面圖示,應用從背景切換到前台

二.如何檢測啟動耗時

1.檢視Logcat

在Android Studio Logcat中過濾關鍵字“Displayed”,可以看到對應的冷啟動耗時日志。

2.adb shell

使用adb shell擷取應用的啟動時間

// 其中的AppstartActivity全路徑可以省略前面的packageName
adb shell am start -W [packageName]/[AppstartActivity全路徑]

           

執行後會得到三個時間:ThisTime、TotalTime和WaitTime,詳情如下:

ThisTime

表示最後一個Activity啟動耗時。

TotalTime

表示所有Activity啟動耗時。

WaitTime

表示AMS啟動Activity的總耗時。

一般來說,隻需檢視得到的TotalTime,即應用的啟動時間,其包括 建立程序 + Application初始化 + Activity初始化到界面顯示 的過程。

特點:

1、線下使用友善,不能帶到線上。

2、非嚴謹、精确時間。

3.AOP(Aspect Oriented Programming) 打點

具體AOP可以自行上網查找文章

下面以統計統計Application中的所有方法耗時為例子

@Aspect
public class ApplicationAop {

    @Around("call (* com.json.chao.application.BaseApplication.**(..))")
    public void getTime(ProceedingJoinPoint joinPoint) {
    Signature signature = joinPoint.getSignature();
    String name = signature.toShortString();
    long time = System.currentTimeMillis();
    try {
        joinPoint.proceed();
    } catch (Throwable throwable) {
        throwable.printStackTrace();
    }
    Log.i(TAG, name + " cost" +     (System.currentTimeMillis() - time));
    }
}

           

在上述代碼中,我們需要注意 不同的Action類型其對應的方法入參是不同的,具體的差異如下所示:

當Action為Before、After時,方法入參為JoinPoint。

當Action為Around時,方法入參為ProceedingPoint。

Around和Before、After的最大差別:

ProceedingPoint不同于JoinPoint,其提供了proceed方法執行目标方法。

4.使用TraceView

這個的使用參考我以前寫的文章 《Android性能優化系列之App啟動優化》

三.啟動優化進階方法

啟動優化一些常用的方法參考《Android性能優化系列之App啟動優化》,這裡不再贅述,這裡講一些進階的方法

1.定制一套APP啟動架構

常見的啟動優化,我們會将一些sdk或者子產品的初始化進行并發的進行,但這些工作之間可能存在前後依賴的關系,是以我們又需要想辦法保證他們執行順序的正确性,是以需要通過啟動架構,為各個任務建立依賴關系,最終構成一個有向無環圖。對于可以并發的任務,會通過線程池最大程度提升啟動速度。

目前開源的啟動架構有:

阿裡的alpha:https://github.com/alibaba/alpha

美團的AppInit: https://github.com/laohong/AppInit

具體原理,感興趣的可以check源碼來看

2.I/O 優化

SharedPreference 在初始化的時候還是要全部資料一起解析。如果它的資料量超過

1000 條,啟動過程解析時間可能就超過 100 毫秒。如果隻解析啟動過程用到的資料項則會很大程度減少解析時間,啟動過程适合使用随機讀寫的資料結構。

解決方式:可以将 ArrayMap 改造成支援随機讀寫、延時解析的資料存儲方式。具體實作後續将出文章講解。

3.資料重排

Linux 檔案 I/O 流程

都2020年了Andoid還能如何性能優化(1)—— 啟動速度優化一.啟動類型二.如何檢測啟動耗時三.啟動優化進階方法

Linux 檔案系統從磁盤讀檔案的時候,會以 block 為機關去磁盤讀取,一般 block 大小是4KB。也就是說一次磁盤讀寫大小至少是 4KB,然後會把 4KB 資料放到頁緩存 Page Cache 中。如果下次讀取檔案資料已經在頁緩存中,那就不會發生真實的磁盤 I/O,而是直接從頁緩存中讀取,大大提升了讀的速度。是以上面的例子,我們雖然讀了 1000 次,但事實上隻會發生一次磁盤 I/O,其他的資料都會在頁緩存中得到。

Dex 檔案用的到的類和安裝包 APK 裡面各種資源檔案一般都比較小,但是讀取非常頻繁。我們可以利用系統這個機制将它們按照讀取順序重新排列,減少真實的磁盤 I/O 次數。

類重排

啟動過程類加載順序可以通過複寫 ClassLoader 得到。

class GetClassLoader extends PathClassLoader 
{ 
public Class<?> findClass(String name) { // 将 name 記錄到檔案 writeToFile(name,"coldstart_classes.txt");
 return super.findClass(name); 
 }
  }
           

具體實作可以參考 ReDex 的Interdex,調整類在 Dex 中的排列順序,可以利用 010 Editor 檢視修改後的效果。

資源檔案重排

修改 Kernel 源碼,單獨編譯一個特殊的 ROM。這樣做的目的

有三個:

1)統計。統計應用啟動過程加載了安裝包中哪些資源檔案,比如 assets、drawable、layout 等。跟類重排一樣,我們可以得到一個資源加載的順序清單。

2)度量。在完成資源順序重排後,我們需要确定是否真正生效。比如有哪些資源檔案加載了,它是發生真實的磁盤 I/O,還是命中了 Page Cache。

3)自動化。任何代碼送出都有可能改變啟動過程中類和資源的加載順序,如果完全依靠人工手動處理,這個事情很難持續下去。通過定制 ROM 的一些埋點和配合的工具,我們可以将它們放到自動化流程當中。

事實上如果僅僅為了統計,我們也可以使用 Hook 的方式。下面是利用 Frida 實作獲得Android 資源加載順序的方法

resourceImpl.loadXmlResourceParser.implementation=function(a,b,c,d){ 
	send('file:'+a)
 	return this.loadXmlResourceParser(a,b,c,d) 
 }
 resourceImpl.loadDrawableForCookie.implementation=function(a,b,c,d,e){ 
 	send("file:"+a)
 	return this.loadDrawableForCookie(a,b,c,d,e) 
 }
           

調整安裝封包件排列需要修改 7zip 源碼實作支援傳入檔案清單順序,同樣最後可以利用010 Editor 檢視修改後的效果。

類的加載

在加載類的過程有一個 verify class 的步驟,它需要校驗方法的每一個指令,是一個比較耗時的操作。

都2020年了Andoid還能如何性能優化(1)—— 啟動速度優化一.啟動類型二.如何檢測啟動耗時三.啟動優化進階方法

我們可以通過 Hook 來去掉 verify 這個步驟,這對啟動速度有幾十毫秒的優化。其實最大的優化場景在于首次和覆寫安裝時。以 Dalvik 平台為例,一個 2MB 的 Dex

正常需要 350 毫秒,将 classVerifyMode 設為 VERIFY_MODE_NONE 後,隻需要 150毫秒,節省超過 50% 的時間。

但是 ART 平台要複雜很多,Hook 需要相容幾個版本。而且在安裝時大部分 Dex 已經優化好了,去掉 ART 平台的 verify 隻會對動态加載的 Dex 帶來一些好處。Atlas 中的dalvik_hack可以通過下面的方法去掉 verify,但是目前沒有支援 ART 平台。

這個黑科技可以大大降低首次啟動的速度,代價是對後續運作會産生輕微的影響。同時也要考慮相容性問題,暫時不建議在 ART 平台使用。

最後附上redex位址:https://github.com/facebook/redex

啟動階段抑制GC

啟動時CG抑制,允許堆一直增長,直到手動或OOM停止GC抑制。(空間換時間)

前提條件

1、裝置廠商沒有加密記憶體中的Dalvik庫檔案。

2、裝置廠商沒有改動Google的Dalvik源碼。

實作原理

1、首先,在源碼級别找到抑制GC的修改方法,例如改變跳轉分支。

2、然後,在二進制代碼裡找到 A 分支條件跳轉的"指令指紋",以及用于改變分支的二進制代碼,假設為 override_A。

3、最後,應用啟動後掃描記憶體中的 libdvm.so,根據"指令指紋"定位到修改位置,并使用 override_A 覆寫。

缺點

需要白名單覆寫所有裝置,但維護成本高。

5.0 以下Multidex預加載優化

安裝或者更新後首次 MultiDex 花費的時間過于漫長,我們需要進行Multidex的預加載優化。

優化步驟

1、啟動時單獨開一個程序去異步進行Multidex的第一次加載,即Dex提取和Dexopt操作。

2、此時,主程序Application進入while循環,不斷檢測Multidex操作是否完成。

3、執行到Multidex時,則已經發現提取并優化好了Dex,直接執行。MultiDex執行完之後主程序Application繼續執行ContentProvider初始化和Application的onCreate方法。

注意

5.0以上預設使用ART,在安裝時已将Class.dex轉換為oat檔案了,無需優化,是以應判斷隻有在主程序及SDK 5.0以下才進行Multidex的預加載。

參考:

抖音BoostMultiDex優化實踐:Android低版本上APP首次啟動時間減少80%

繼續閱讀