引言
作為 Android 開發者,想必多多少少要接觸啟動速度優化相關的事情,當使用者越來越多,產品的功能也隨著迭代越來越多,App 逐漸變得臃腫是一件很常見的現象,甚至可以說是不可避免的現象,隨之而來的工作就是優化 App 效能,其中最主要的一項就是啟動速度優化。但本文的主角並不是啟動速度優化,而是啟動時間統計。
一 啟動型別
工欲善其事,必先利其器。想要優化 App 的啟動速度,必須有準確衡量啟動時間的方法,否則優化完之後效果怎樣,自己都不知道,說出去別人也不信服不是。在做 App 啟動時間統計之前,當然必須弄明白有哪些啟動型別,每種啟動型別的特點。通常來說,在安卓中應用的啟動方式分為以下幾種:
- 冷啟動:當啟動應用時,後臺沒有該應用的程式,這時系統會重新建立一個新的程式分配給該應用,這個啟動方式就是冷啟動。冷啟動因為系統會重新建立一個新的程式分配給它,所以會先建立和初始化
Application
類,再建立和初始化MainActivity
類,最後顯示在介面上。 - 熱啟動:當啟動應用時,後臺已有該應用的程式(例:按back鍵、home鍵,應用雖然會退出,但是該應用的程式是依然會保留在後臺,可進入任務列表檢視),所以在已有程式的情況下,這種啟動會從已有的程式中來啟動應用,這個方式叫熱啟動。熱啟動因為會從已有的程式中來啟動,所以熱啟動就不會走
Application
這步了,而是直接走MainActivity
,所以熱啟動的過程不必建立和初始化Application
,因為一個應用從新程式的建立到程式的銷燬,Application
只會初始化一次。 - 首次啟動:首次啟動嚴格來說也是冷啟動,之所以把首次啟動單獨列出來,一般來說,首次啟動時間會比非首次啟動要久,首次啟動會做一些系統初始化工作,如快取目錄的生產,資料庫的建立,SharedPreference的初始化,如果存在多 dex 和外掛的情況下,首次啟動會有一些特殊需要處理的邏輯,而且對啟動速度有很大的影響,所以首次啟動的速度非常重要,畢竟影響使用者對 App 的第一映像。
二 本地啟動時間的統計方式
如果是本地除錯的話,統計啟動時間還是很簡單的,通過命令列方式即可:
1 |
adb shell am start -w packagename/activity |
輸出的結果類似於:
1 2 3 4 5 6 7 8 |
$ 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
到應用第一幀完全顯示這段時間. 就是總的耗時,包括前一個應用Activity
pause 的時間和新應用啟動的時間;ThisTime
表示一連串啟動Activity
的最後一個Activity
的啟動耗時;TotalTime
表示新應用啟動的耗時,包括新程式的啟動和Activity
的啟動,但不包括前一個應用Activity
pause的耗時。
開發者一般只要關心 TotalTime
即可,這個時間才是自己應用真正啟動的耗時。
三 線上啟動時間的統計方式
當 App 發到線上之後,想要統計 App 在使用者手機上的啟動速度,就不能通過命令列的方式進行統計了,基本上都是通過打 Log
的方式將啟動時間傳送上來。那麼在什麼位置加啟動時間統計的 Log
就尤為重要,Log
新增的位置直接決定啟動時間統計的是否準確,同樣也會影響啟動速度優化效果的判斷。要想找到合適準確的位置記錄啟動時間的 Log
,就需要了解應用的啟動流程,和各個生命週期函式的呼叫順序。下面來分析下到底在什麼位置打 Log 記錄啟動時間比較合適。
應用的主要啟動流程
關於 App 啟動流程的文章很多,文章底部有一些啟動流程相關的參考文章,這裡只列出大致流程如下:
- 通過 Launcher 啟動應用時,點選應用圖示後,Launcher 呼叫
startActivity
啟動應用。 - Launcher Activity 最終呼叫
Instrumentation
的execStartActivity
來啟動應用。 Instrumentation
呼叫ActivityManagerProxy
(ActivityManagerService
在應用程式的一個代理物件) 物件的startActivity
方法啟動Activity
。- 到目前為止所有過程都在 Launcher 程式裡面執行,接下來
ActivityManagerProxy
物件跨程式呼叫ActivityManagerService
(執行在system_server
程式)的startActivity
方法啟動應用。 ActivityManagerService
的startActivity
方法經過一系列呼叫,最後呼叫zygoteSendArgsAndGetResult
通過socket
傳送給zygote
程式,zygote
程式會孵化出新的應用程式。zygote
程式孵化出新的應用程式後,會執行ActivityThread
類的main
方法。在該方法裡會先準備好Looper
和訊息佇列,然後呼叫attach
方法將應用程式繫結到ActivityManagerService
,然後進入loop
迴圈,不斷地讀取訊息佇列裡的訊息,並分發訊息。ActivityManagerService
儲存應用程式的一個代理物件,然後ActivityManagerService
通過代理物件通知應用程式建立入口Activity
的例項,並執行它的生命週期函式。
總結過程就是:使用者在 Launcher
程式裡點選應用圖示時,會通知 ActivityManagerService
啟動應用的入口 Activity
, ActivityManagerService
發現這個應用還未啟動,則會通知 Zygote
程式孵化出應用程式,然後在這個應用程式裡執行 ActivityThread
的 main
方法。應用程式接下來通知 ActivityManagerService
應用程式已啟動,ActivityManagerService
儲存應用程式的一個代理物件,這樣 ActivityManagerService
可以通過這個代理物件控制應用程式,然後 ActivityManagerService
通知應用程式建立入口 Activity
的例項,並執行它的生命週期函式。
生命週期函式執行流程
上面的啟動流程是 Android 提供的機制,作為開發者我們需要清楚或者至少了解其中的過程和原理,但我們並不能在這過程中做什麼文章,我們能做的恰恰是從上述過程中最後一步開始,即 ActivityManagerService
通過代理物件通知應用程式建立入口 Activity
的例項,並執行它的生命週期函式開始,我們的啟動時間統計以及啟動速度優化也是從這裡開始。下面是 Main Activity 的啟動流程:
1 2 3 4 5 6 7 8 9 10 |
-> Application 建構函式 -> Application.attachBaseContext() -> Application.onCreate() -> Activity 建構函式 -> Activity.setTheme() -> Activity.onCreate() -> Activity.onStart -> Activity.onResume -> Activity.onAttachedToWindow -> Activity.onWindowFocusChanged |
如果打 Log 記錄 App 的啟動時間,那麼至少要記錄兩個點,一個起始時間點,一個結束時間點。
起始時間點
起始時間點比較容易記錄:如果記錄冷啟動啟動時間一般可以在 Application.attachBaseContext()
開始的位置記錄起始時間點,因為在這之前 Context
還沒有初始化,一般也幹不了什麼事情,當然這個是要視具體情況來定,其實只要保證在 App 的具體業務邏輯開始執行之前記錄起始時間點即可。如果記錄熱啟動啟動時間點可以在 Activity.onRestart()
中記錄起始時間點。
結束時間點
結束時間點理論上要選在 App 顯示出第一屏介面的時候,但是在什麼位置 App 顯示出第一屏介面呢?網上很多文章說在 Activity
的 onResume
方法執行完成之後,Activity 就對使用者可見了,實際上並不是,一個 Activity
走完onCreate
onStart
onResume
這幾個生命週期之後,只是完成了應用自身的一些配置,比如 Activity
主題設定 window
屬性的設定 View
樹的建立,但是其實後面還需要各個 View
執行 measure
layout
draw
等。所以在 OnResume
中記錄結束時間點的 Log 並不準確,大家可以注意一下上面流程中最後一個函式 Activity.onWindowFocusChanged
,下面是它的註釋:
1 2 3 4 5 6 7 |
/** *Called when the current {<a href="http://www.jobbole.com/members/57845349">@link</a> 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. ... */ |
通過註釋我們可以看到,這個函式是判斷 activity
是否可見的最佳位置,所以我們可以在 Activity.onWindowFocusChanged
記錄應用啟動的結束時間點,不過需要注意的是該函式,在 Activity
焦點發生變化時就會觸發,所以要做好判斷,去掉不需要的情況。
總結
花了很多篇幅介紹啟動時間統計,我覺得還是有必要的,還是那句話:工欲善其事,必先利其器,準備工作做的充分,做事自然有理有據。
參考:
https://www.zhihu.com/question/35487841
http://blog.adisonhyh.com/blog/2015/03/27/androidxiang-mu-xing-neng-you-hua-zhi-qi-dong-shi-jian/
http://www.woaitqs.cc/android/2016/06/21/activity-service.html
http://blog.csdn.net/zhaokaiqiang1992/article/details/49428287
http://www.cloudchou.com/android/post-805.html
http://www.jianshu.com/p/72059201b10a