安卓(Android)開發(fā)之統(tǒng)計App啟動時間
前言
作為 Android 開發(fā)者,想必多多少少要接觸啟動速度優(yōu)化相關(guān)的事情,當(dāng)用戶越來越多,產(chǎn)品的功能也隨著迭代越來越多,App 逐漸變得臃腫是一件很常見的現(xiàn)象,甚至可以說是不可避免的現(xiàn)象,隨之而來的工作就是優(yōu)化 App 性能,其中最主要的一項就是啟動速度優(yōu)化。但本文的主角并不是啟動速度優(yōu)化,而是啟動時間統(tǒng)計。
一、啟動類型
工欲善其事,必先利其器。想要優(yōu)化 App 的啟動速度,必須有準(zhǔn)確衡量啟動時間的方法,否則優(yōu)化完之后效果怎樣,自己都不知道,說出去別人也不信服不是。在做 App 啟動時間統(tǒng)計之前,當(dāng)然必須弄明白有哪些啟動類型,每種啟動類型的特點。通常來說,在安卓中應(yīng)用的啟動方式分為以下幾種:
1、冷啟動:當(dāng)啟動應(yīng)用時,后臺沒有該應(yīng)用的進(jìn)程,這時系統(tǒng)會重新創(chuàng)建一個新的進(jìn)程分配給該應(yīng)用,這個啟動方式就是冷啟動。冷啟動因為系統(tǒng)會重新創(chuàng)建一個新的進(jìn)程分配給它,所以會先創(chuàng)建和初始化 Application 類,再創(chuàng)建和初始化 MainActivity 類,最后顯示在界面上。
2、熱啟動:當(dāng)啟動應(yīng)用時,后臺已有該應(yīng)用的進(jìn)程(例:按back鍵、home鍵,應(yīng)用雖然會退出,但是該應(yīng)用的進(jìn)程是依然會保留在后臺,可進(jìn)入任務(wù)列表查看),所以在已有進(jìn)程的情況下,這種啟動會從已有的進(jìn)程中來啟動應(yīng)用,這個方式叫熱啟動。熱啟動因為會從已有的進(jìn)程中來啟動,所以熱啟動就不會走 Application 這步了,而是直接走 MainActivity,所以熱啟動的過程不必創(chuàng)建和初始化 Application,因為一個應(yīng)用從新進(jìn)程的創(chuàng)建到進(jìn)程的銷毀,Application 只會初始化一次。
3、首次啟動:首次啟動嚴(yán)格來說也是冷啟動,之所以把首次啟動單獨列出來,一般來說,首次啟動時間會比非首次啟動要久,首次啟動會做一些系統(tǒng)初始化工作,如緩存目錄的生產(chǎn),數(shù)據(jù)庫的建立,SharedPreference的初始化,如果存在多 dex 和插件的情況下,首次啟動會有一些特殊需要處理的邏輯,而且對啟動速度有很大的影響,所以首次啟動的速度非常重要,畢竟影響用戶對 App 的第一映像。
二、本地啟動時間的統(tǒng)計方式
如果是本地調(diào)試的話,統(tǒng)計啟動時間還是很簡單的,通過命令行方式即可:
adb shell am start -w packagename/activity
輸出的結(jié)果類似于:
$ adb shell am start -W com.speed.test/com.speed.test.HomeActivity
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.speed.test/.HomeActivity }
Status: ok
Activity: com.speed.test/.HomeActivity
ThisTime: 496
TotalTime: 496
WaitTime: 503
Complete
WaitTime 返回從 startActivity 到應(yīng)用第一幀完全顯示這段時間. 就是總的耗時,包括前一個應(yīng)用 Activity pause 的時間和新應(yīng)用啟動的時間;
ThisTime 表示一連串啟動 Activity 的最后一個 Activity 的啟動耗時;
TotalTime 表示新應(yīng)用啟動的耗時,包括新進(jìn)程的啟動和 Activity 的啟動,但不包括前一個應(yīng)用Activity pause的耗時。
開發(fā)者一般只要關(guān)心 TotalTime 即可,這個時間才是自己應(yīng)用真正啟動的耗時。
三、線上啟動時間的統(tǒng)計方式
當(dāng) App 發(fā)到線上之后,想要統(tǒng)計 App 在用戶手機(jī)上的啟動速度,就不能通過命令行的方式進(jìn)行統(tǒng)計了,基本上都是通過打 Log 的方式將啟動時間發(fā)送上來。那么在什么位置加啟動時間統(tǒng)計的 Log 就尤為重要,Log 添加的位置直接決定啟動時間統(tǒng)計的是否準(zhǔn)確,同樣也會影響啟動速度優(yōu)化效果的判斷。要想找到合適準(zhǔn)確的位置記錄啟動時間的 Log,就需要了解應(yīng)用的啟動流程,和各個生命周期函數(shù)的調(diào)用順序。下面來分析下到底在什么位置打 Log 記錄啟動時間比較合適。
應(yīng)用的主要啟動流程
關(guān)于 App 啟動流程的文章很多,文章底部有一些啟動流程相關(guān)的參考文章,這里只列出大致流程如下:
1、通過 Launcher 啟動應(yīng)用時,點擊應(yīng)用圖標(biāo)后,Launcher 調(diào)用 startActivity 啟動應(yīng)用。
2、Launcher Activity 最終調(diào)用 Instrumentation 的 execStartActivity 來啟動應(yīng)用。
3、Instrumentation 調(diào)用 ActivityManagerProxy (ActivityManagerService 在應(yīng)用進(jìn)程的一個代理對象) 對象的 startActivity 方法啟動 Activity。
4、到目前為止所有過程都在 Launcher 進(jìn)程里面執(zhí)行,接下來 ActivityManagerProxy 對象跨進(jìn)程調(diào)用 ActivityManagerService (運行在 system_server 進(jìn)程)的 startActivity 方法啟動應(yīng)用。
5、ActivityManagerService 的 startActivity 方法經(jīng)過一系列調(diào)用,最后調(diào)用 zygoteSendArgsAndGetResult 通過 socket 發(fā)送給 zygote 進(jìn)程,zygote 進(jìn)程會孵化出新的應(yīng)用進(jìn)程。
6、zygote 進(jìn)程孵化出新的應(yīng)用進(jìn)程后,會執(zhí)行 ActivityThread 類的 main 方法。在該方法里會先準(zhǔn)備好 Looper 和消息隊列,然后調(diào)用 attach 方法將應(yīng)用進(jìn)程綁定到 ActivityManagerService,然后進(jìn)入 loop 循環(huán),不斷地讀取消息隊列里的消息,并分發(fā)消息。
7、ActivityManagerService 保存應(yīng)用進(jìn)程的一個代理對象,然后 ActivityManagerService 通過代理對象通知應(yīng)用進(jìn)程創(chuàng)建入口 Activity 的實例,并執(zhí)行它的生命周期函數(shù)。
總結(jié)過程就是:用戶在 Launcher 程序里點擊應(yīng)用圖標(biāo)時,會通知 ActivityManagerService 啟動應(yīng)用的入口 Activity, ActivityManagerService 發(fā)現(xiàn)這個應(yīng)用還未啟動,則會通知 Zygote 進(jìn)程孵化出應(yīng)用進(jìn)程,然后在這個應(yīng)用進(jìn)程里執(zhí)行 ActivityThread 的 main 方法。應(yīng)用進(jìn)程接下來通知 ActivityManagerService 應(yīng)用進(jìn)程已啟動,ActivityManagerService 保存應(yīng)用進(jìn)程的一個代理對象,這樣 ActivityManagerService 可以通過這個代理對象控制應(yīng)用進(jìn)程,然后 ActivityManagerService 通知應(yīng)用進(jìn)程創(chuàng)建入口 Activity 的實例,并執(zhí)行它的生命周期函數(shù)。
生命周期函數(shù)執(zhí)行流程
上面的啟動流程是 Android 提供的機(jī)制,作為開發(fā)者我們需要清楚或者至少了解其中的過程和原理,但我們并不能在這過程中做什么文章,我們能做的恰恰是從上述過程中最后一步開始,即 ActivityManagerService 通過代理對象通知應(yīng)用進(jìn)程創(chuàng)建入口 Activity 的實例,并執(zhí)行它的生命周期函數(shù)開始,我們的啟動時間統(tǒng)計以及啟動速度優(yōu)化也是從這里開始。下面是 Main Activity 的啟動流程:
-> Application 構(gòu)造函數(shù) -> Application.attachBaseContext() -> Application.onCreate() -> Activity 構(gòu)造函數(shù) -> Activity.setTheme() -> Activity.onCreate() -> Activity.onStart -> Activity.onResume -> Activity.onAttachedToWindow -> Activity.onWindowFocusChanged
如果打 Log 記錄 App 的啟動時間,那么至少要記錄兩個點,一個起始時間點,一個結(jié)束時間點。
起始時間點
起始時間點比較容易記錄:如果記錄冷啟動啟動時間一般可以在 Application.attachBaseContext() 開始的位置記錄起始時間點,因為在這之前 Context 還沒有初始化,一般也干不了什么事情,當(dāng)然這個是要視具體情況來定,其實只要保證在 App 的具體業(yè)務(wù)邏輯開始執(zhí)行之前記錄起始時間點即可。如果記錄熱啟動啟動時間點可以在 Activity.onRestart() 中記錄起始時間點。
結(jié)束時間點
結(jié)束時間點理論上要選在 App 顯示出第一屏界面的時候,但是在什么位置 App 顯示出第一屏界面呢?網(wǎng)上很多文章說在 Activity 的 onResume 方法執(zhí)行完成之后,Activity 就對用戶可見了,實際上并不是,一個 Activity 走完onCreate onStart onResume 這幾個生命周期之后,只是完成了應(yīng)用自身的一些配置,比如 Activity 主題設(shè)置 window 屬性的設(shè)置 View 樹的建立,但是其實后面還需要各個 View 執(zhí)行 measure layout draw等。所以在 OnResume 中記錄結(jié)束時間點的 Log 并不準(zhǔn)確,大家可以注意一下上面流程中最后一個函數(shù) Activity.onWindowFocusChanged,下面是它的注釋:
/**
*Called when the current {@link Window} of the activity gains or loses
* focus. This is the best indicator of whether this activity is visible
* to the user. The default implementation clears the key tracking
* state, so should always be called.
...
*/
通過注釋我們可以看到,這個函數(shù)是判斷 activity 是否可見的最佳位置,所以我們可以在 Activity.onWindowFocusChanged 記錄應(yīng)用啟動的結(jié)束時間點,不過需要注意的是該函數(shù),在 Activity 焦點發(fā)生變化時就會觸發(fā),所以要做好判斷,去掉不需要的情況。
總結(jié)
以上就是關(guān)于安卓(Android)開發(fā)之統(tǒng)計App啟動時間的全部內(nèi)容,本文的內(nèi)容小編覺得還是很重要的,還是那句話:工欲善其事,必先利其器,準(zhǔn)備工作做的充分,做事自然有理有據(jù)。希望本文的內(nèi)容對大家有所幫助。
相關(guān)文章
Android 單例模式實現(xiàn)可復(fù)用數(shù)據(jù)存儲的詳細(xì)過程
本文介紹了如何使用單例模式實現(xiàn)一個可復(fù)用的數(shù)據(jù)存儲類,該類可以存儲不同類型的數(shù)據(jù),并提供統(tǒng)一的接口來訪問這些數(shù)據(jù),通過雙重檢查鎖定機(jī)制,該類在多線程環(huán)境下是線程安全的,感興趣的朋友跟隨小編一起看看吧2025-02-02
詳解Android的OkHttp包編寫異步HTTP請求調(diào)用的方法
OkHttp支持Callback異步回調(diào)來實現(xiàn)線程的非阻塞,下面我們就來詳解Android的OkHttp包編寫異步HTTP請求調(diào)用的方法,需要的朋友可以參考下2016-07-07

