安卓(Android)開發(fā)之統(tǒng)計(jì)App啟動(dòng)時(shí)間
前言
作為 Android 開發(fā)者,想必多多少少要接觸啟動(dòng)速度優(yōu)化相關(guān)的事情,當(dāng)用戶越來越多,產(chǎn)品的功能也隨著迭代越來越多,App 逐漸變得臃腫是一件很常見的現(xiàn)象,甚至可以說是不可避免的現(xiàn)象,隨之而來的工作就是優(yōu)化 App 性能,其中最主要的一項(xiàng)就是啟動(dòng)速度優(yōu)化。但本文的主角并不是啟動(dòng)速度優(yōu)化,而是啟動(dòng)時(shí)間統(tǒng)計(jì)。
一、啟動(dòng)類型
工欲善其事,必先利其器。想要優(yōu)化 App 的啟動(dòng)速度,必須有準(zhǔn)確衡量啟動(dòng)時(shí)間的方法,否則優(yōu)化完之后效果怎樣,自己都不知道,說出去別人也不信服不是。在做 App 啟動(dòng)時(shí)間統(tǒng)計(jì)之前,當(dāng)然必須弄明白有哪些啟動(dòng)類型,每種啟動(dòng)類型的特點(diǎn)。通常來說,在安卓中應(yīng)用的啟動(dòng)方式分為以下幾種:
1、冷啟動(dòng):當(dāng)啟動(dòng)應(yīng)用時(shí),后臺(tái)沒有該應(yīng)用的進(jìn)程,這時(shí)系統(tǒng)會(huì)重新創(chuàng)建一個(gè)新的進(jìn)程分配給該應(yīng)用,這個(gè)啟動(dòng)方式就是冷啟動(dòng)。冷啟動(dòng)因?yàn)橄到y(tǒng)會(huì)重新創(chuàng)建一個(gè)新的進(jìn)程分配給它,所以會(huì)先創(chuàng)建和初始化 Application
類,再創(chuàng)建和初始化 MainActivity
類,最后顯示在界面上。
2、熱啟動(dòng):當(dāng)啟動(dòng)應(yīng)用時(shí),后臺(tái)已有該應(yīng)用的進(jìn)程(例:按back鍵、home鍵,應(yīng)用雖然會(huì)退出,但是該應(yīng)用的進(jìn)程是依然會(huì)保留在后臺(tái),可進(jìn)入任務(wù)列表查看),所以在已有進(jìn)程的情況下,這種啟動(dòng)會(huì)從已有的進(jìn)程中來啟動(dòng)應(yīng)用,這個(gè)方式叫熱啟動(dòng)。熱啟動(dòng)因?yàn)闀?huì)從已有的進(jìn)程中來啟動(dòng),所以熱啟動(dòng)就不會(huì)走 Application
這步了,而是直接走 MainActivity
,所以熱啟動(dòng)的過程不必創(chuàng)建和初始化 Application
,因?yàn)橐粋€(gè)應(yīng)用從新進(jìn)程的創(chuàng)建到進(jìn)程的銷毀,Application
只會(huì)初始化一次。
3、首次啟動(dòng):首次啟動(dòng)嚴(yán)格來說也是冷啟動(dòng),之所以把首次啟動(dòng)單獨(dú)列出來,一般來說,首次啟動(dòng)時(shí)間會(huì)比非首次啟動(dòng)要久,首次啟動(dòng)會(huì)做一些系統(tǒng)初始化工作,如緩存目錄的生產(chǎn),數(shù)據(jù)庫(kù)的建立,SharedPreference的初始化,如果存在多 dex 和插件的情況下,首次啟動(dòng)會(huì)有一些特殊需要處理的邏輯,而且對(duì)啟動(dòng)速度有很大的影響,所以首次啟動(dòng)的速度非常重要,畢竟影響用戶對(duì) App 的第一映像。
二、本地啟動(dòng)時(shí)間的統(tǒng)計(jì)方式
如果是本地調(diào)試的話,統(tǒng)計(jì)啟動(dòng)時(shí)間還是很簡(jiǎn)單的,通過命令行方式即可:
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)用第一幀完全顯示這段時(shí)間. 就是總的耗時(shí),包括前一個(gè)應(yīng)用 Activity pause
的時(shí)間和新應(yīng)用啟動(dòng)的時(shí)間;
ThisTime
表示一連串啟動(dòng) Activity
的最后一個(gè) Activity
的啟動(dòng)耗時(shí);
TotalTime
表示新應(yīng)用啟動(dòng)的耗時(shí),包括新進(jìn)程的啟動(dòng)和 Activity
的啟動(dòng),但不包括前一個(gè)應(yīng)用Activity
pause的耗時(shí)。
開發(fā)者一般只要關(guān)心 TotalTime 即可,這個(gè)時(shí)間才是自己應(yīng)用真正啟動(dòng)的耗時(shí)。
三、線上啟動(dòng)時(shí)間的統(tǒng)計(jì)方式
當(dāng) App 發(fā)到線上之后,想要統(tǒng)計(jì) App 在用戶手機(jī)上的啟動(dòng)速度,就不能通過命令行的方式進(jìn)行統(tǒng)計(jì)了,基本上都是通過打 Log 的方式將啟動(dòng)時(shí)間發(fā)送上來。那么在什么位置加啟動(dòng)時(shí)間統(tǒng)計(jì)的 Log 就尤為重要,Log 添加的位置直接決定啟動(dòng)時(shí)間統(tǒng)計(jì)的是否準(zhǔn)確,同樣也會(huì)影響啟動(dòng)速度優(yōu)化效果的判斷。要想找到合適準(zhǔn)確的位置記錄啟動(dòng)時(shí)間的 Log,就需要了解應(yīng)用的啟動(dòng)流程,和各個(gè)生命周期函數(shù)的調(diào)用順序。下面來分析下到底在什么位置打 Log 記錄啟動(dòng)時(shí)間比較合適。
應(yīng)用的主要啟動(dòng)流程
關(guān)于 App 啟動(dòng)流程的文章很多,文章底部有一些啟動(dòng)流程相關(guān)的參考文章,這里只列出大致流程如下:
1、通過 Launcher
啟動(dòng)應(yīng)用時(shí),點(diǎn)擊應(yīng)用圖標(biāo)后,Launcher
調(diào)用 startActivity
啟動(dòng)應(yīng)用。
2、Launcher Activity
最終調(diào)用 Instrumentation
的 execStartActivity
來啟動(dòng)應(yīng)用。
3、Instrumentation
調(diào)用 ActivityManagerProxy
(ActivityManagerService 在應(yīng)用進(jìn)程的一個(gè)代理對(duì)象) 對(duì)象的 startActivity
方法啟動(dòng) Activity
。
4、到目前為止所有過程都在 Launcher
進(jìn)程里面執(zhí)行,接下來 ActivityManagerProxy
對(duì)象跨進(jìn)程調(diào)用 ActivityManagerService
(運(yùn)行在 system_server 進(jìn)程)的 startActivity
方法啟動(dòng)應(yīng)用。
5、ActivityManagerService
的 startActivity
方法經(jīng)過一系列調(diào)用,最后調(diào)用 zygoteSendArgsAndGetResult
通過 socket
發(fā)送給 zygote
進(jìn)程,zygote
進(jìn)程會(huì)孵化出新的應(yīng)用進(jìn)程。
6、zygote 進(jìn)程孵化出新的應(yīng)用進(jìn)程后,會(huì)執(zhí)行 ActivityThread
類的 main
方法。在該方法里會(huì)先準(zhǔn)備好 Looper
和消息隊(duì)列,然后調(diào)用 attach
方法將應(yīng)用進(jìn)程綁定到 ActivityManagerService
,然后進(jìn)入 loop
循環(huán),不斷地讀取消息隊(duì)列里的消息,并分發(fā)消息。
7、ActivityManagerService
保存應(yīng)用進(jìn)程的一個(gè)代理對(duì)象,然后 ActivityManagerService
通過代理對(duì)象通知應(yīng)用進(jìn)程創(chuàng)建入口 Activity
的實(shí)例,并執(zhí)行它的生命周期函數(shù)。
總結(jié)過程就是:用戶在 Launcher
程序里點(diǎn)擊應(yīng)用圖標(biāo)時(shí),會(huì)通知 ActivityManagerService
啟動(dòng)應(yīng)用的入口 Activity
, ActivityManagerService
發(fā)現(xiàn)這個(gè)應(yīng)用還未啟動(dòng),則會(huì)通知 Zygote
進(jìn)程孵化出應(yīng)用進(jìn)程,然后在這個(gè)應(yīng)用進(jìn)程里執(zhí)行 ActivityThread
的 main
方法。應(yīng)用進(jìn)程接下來通知 ActivityManagerService
應(yīng)用進(jìn)程已啟動(dòng),ActivityManagerService
保存應(yīng)用進(jìn)程的一個(gè)代理對(duì)象,這樣 ActivityManagerService
可以通過這個(gè)代理對(duì)象控制應(yīng)用進(jìn)程,然后 ActivityManagerService
通知應(yīng)用進(jìn)程創(chuàng)建入口 Activity 的實(shí)例,并執(zhí)行它的生命周期函數(shù)。
生命周期函數(shù)執(zhí)行流程
上面的啟動(dòng)流程是 Android 提供的機(jī)制,作為開發(fā)者我們需要清楚或者至少了解其中的過程和原理,但我們并不能在這過程中做什么文章,我們能做的恰恰是從上述過程中最后一步開始,即 ActivityManagerService
通過代理對(duì)象通知應(yīng)用進(jìn)程創(chuàng)建入口 Activity
的實(shí)例,并執(zhí)行它的生命周期函數(shù)開始,我們的啟動(dòng)時(shí)間統(tǒng)計(jì)以及啟動(dòng)速度優(yōu)化也是從這里開始。下面是 Main Activity 的啟動(dòng)流程:
-> 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 的啟動(dòng)時(shí)間,那么至少要記錄兩個(gè)點(diǎn),一個(gè)起始時(shí)間點(diǎn),一個(gè)結(jié)束時(shí)間點(diǎn)。
起始時(shí)間點(diǎn)
起始時(shí)間點(diǎn)比較容易記錄:如果記錄冷啟動(dòng)啟動(dòng)時(shí)間一般可以在 Application.attachBaseContext()
開始的位置記錄起始時(shí)間點(diǎn),因?yàn)樵谶@之前 Context
還沒有初始化,一般也干不了什么事情,當(dāng)然這個(gè)是要視具體情況來定,其實(shí)只要保證在 App 的具體業(yè)務(wù)邏輯開始執(zhí)行之前記錄起始時(shí)間點(diǎn)即可。如果記錄熱啟動(dòng)啟動(dòng)時(shí)間點(diǎn)可以在 Activity.onRestart()
中記錄起始時(shí)間點(diǎn)。
結(jié)束時(shí)間點(diǎn)
結(jié)束時(shí)間點(diǎn)理論上要選在 App 顯示出第一屏界面的時(shí)候,但是在什么位置 App 顯示出第一屏界面呢?網(wǎng)上很多文章說在 Activity
的 onResume
方法執(zhí)行完成之后,Activity
就對(duì)用戶可見了,實(shí)際上并不是,一個(gè) Activity
走完onCreate onStart onResume
這幾個(gè)生命周期之后,只是完成了應(yīng)用自身的一些配置,比如 Activity
主題設(shè)置 window 屬性的設(shè)置 View
樹的建立,但是其實(shí)后面還需要各個(gè) View
執(zhí)行 measure layout draw
等。所以在 OnResume
中記錄結(jié)束時(shí)間點(diǎn)的 Log 并不準(zhǔn)確,大家可以注意一下上面流程中最后一個(gè)函數(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. ... */
通過注釋我們可以看到,這個(gè)函數(shù)是判斷 activity
是否可見的最佳位置,所以我們可以在 Activity.onWindowFocusChanged
記錄應(yīng)用啟動(dòng)的結(jié)束時(shí)間點(diǎn),不過需要注意的是該函數(shù),在 Activity
焦點(diǎn)發(fā)生變化時(shí)就會(huì)觸發(fā),所以要做好判斷,去掉不需要的情況。
總結(jié)
以上就是關(guān)于安卓(Android)開發(fā)之統(tǒng)計(jì)App啟動(dòng)時(shí)間的全部?jī)?nèi)容,本文的內(nèi)容小編覺得還是很重要的,還是那句話:工欲善其事,必先利其器,準(zhǔn)備工作做的充分,做事自然有理有據(jù)。希望本文的內(nèi)容對(duì)大家有所幫助。
- Android從Fragment跳轉(zhuǎn)到其他Activity的簡(jiǎn)單實(shí)例
- Android開發(fā)使用Activity嵌套多個(gè)Fragment實(shí)現(xiàn)橫豎屏切換功能的方法
- Android 中Fragment與Activity通訊的詳解
- Android中Activity和Fragment傳遞數(shù)據(jù)的兩種方式
- 詳解Android activity與fragment之間的通信交互
- Android鬧鐘啟動(dòng)時(shí)間設(shè)置無(wú)效問題的解決方法
- 準(zhǔn)確測(cè)量 Android 應(yīng)用中 Activity 和 Fragment 的啟動(dòng)時(shí)間的詳細(xì)過程
相關(guān)文章
Input系統(tǒng)分發(fā)策略及其應(yīng)用示例詳解
這篇文章主要為大家介紹了Input系統(tǒng)分發(fā)策略及其應(yīng)用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02Android中深入學(xué)習(xí)對(duì)象的四種引用類型
這篇文章主要介紹Android中深入學(xué)習(xí)對(duì)象的四種引用類型,Java中,一切被視為對(duì)象,引用則是用來操縱對(duì)象的;在JDK1.2就把對(duì)象引用分為四種級(jí)別,從而使程序能更靈活控制它的生命周期,級(jí)別由高到底依次為強(qiáng)引用、軟引用、弱引用、虛引用,需要的朋友可以參考一下2021-10-10Android 狀態(tài)欄的設(shè)置適配問題詳解
這篇文章主要介紹了Android 狀態(tài)欄的設(shè)置適配問題詳解的相關(guān)資料,需要的朋友可以參考下2017-06-06Android Studio實(shí)現(xiàn)簡(jiǎn)單購(gòu)物車功能
這篇文章主要為大家詳細(xì)介紹了Android Studio實(shí)現(xiàn)簡(jiǎn)單購(gòu)物車,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07AndroidApk混淆編譯時(shí),報(bào)告java.io.IOException...錯(cuò)誤解決辦法
這篇文章主要介紹了 AndroidApk混淆編譯時(shí),報(bào)告Error:Execution failed for task ‘:gviews:transformClassesAndResourcesWithProguardForRelease’.錯(cuò)誤解決辦法的相關(guān)資料,需要的朋友可以參考下2017-03-03實(shí)例詳解android studio如何導(dǎo)入.so文件的方法
通過實(shí)例給大家詳細(xì)講解了如何在android studio如何導(dǎo)入.so文件以及中間遇到的問題解決辦法,需要的讀者們可以仔細(xì)學(xué)習(xí)一下。2017-12-12Android中的SQLite數(shù)據(jù)庫(kù)簡(jiǎn)介
SQLite是Android系統(tǒng)采用的一種開源的輕量級(jí)的關(guān)系型的數(shù)據(jù)庫(kù)。這篇文章主要介紹了Android中的SQLite數(shù)據(jù)庫(kù)簡(jiǎn)介,需要的朋友可以參考下2017-03-03Android自定義View葉子旋轉(zhuǎn)完整版(六)
這篇文章主要為大家詳細(xì)介紹了Android自定義View葉子旋轉(zhuǎn)完整版,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03