如需轉(zhuǎn)載請(qǐng)?jiān)u論或簡(jiǎn)信傅是,并注明出處瑞信,未經(jīng)允許不得轉(zhuǎn)載
系列文章
- App啟動(dòng)優(yōu)化(一)冷啟動(dòng)和熱啟動(dòng)
- App啟動(dòng)優(yōu)化(二)啟動(dòng)時(shí)間測(cè)量
- App啟動(dòng)優(yōu)化(三)啟動(dòng)優(yōu)化方案
目錄
前言
在進(jìn)行啟動(dòng)優(yōu)化之前拄丰,我們應(yīng)該先了解如何對(duì)啟動(dòng)的時(shí)間進(jìn)行測(cè)量,這樣就能更加準(zhǔn)確的衡量我們優(yōu)化的成果
adb命令測(cè)量
adb shell am start -W packagename/首屏Activity
Tips:這個(gè)命令可以打開(kāi)Launcher Activity擦剑,如果在打開(kāi)別的Activity的時(shí)候遇到問(wèn)題蜡坊,可以在清單文件中配置Activity的exported = true
- ThisTime:一般和TotalTime時(shí)間一樣,除非在應(yīng)用啟動(dòng)時(shí)開(kāi)了一個(gè)透明的Activity預(yù)先處理一些事翅阵,再顯示出Activity歪玲,這樣將比TotalTime小
- TotalTime:應(yīng)用的啟動(dòng)時(shí)間,包括創(chuàng)建進(jìn)程+Application初始化+Activity初始化到界面顯示
- WaitTime:一般比TotalTime大點(diǎn)掷匠,包括系統(tǒng)響應(yīng)的耗時(shí)
這三個(gè)時(shí)間不好理解滥崩,我們可以把整個(gè)過(guò)程分解,如下所示:
前一個(gè)應(yīng)用的
activity
的onPause()
系統(tǒng)調(diào)用AMS耗時(shí)
第一個(gè)
activity
啟動(dòng)耗時(shí)第一個(gè)
activity
的onPause()
耗時(shí)第二個(gè)
activity
啟動(dòng)耗時(shí)
ThisTime表示5
TotalTime表示3讹语、4钙皮、5總共的耗時(shí)
WaitTime則表示所有的操作耗時(shí),即1顽决、2短条、3、4擎值、5所有的耗時(shí)
開(kāi)發(fā)者一般只要關(guān)心 TotalTime 即可慌烧,這個(gè)時(shí)間才是自己應(yīng)用真正啟動(dòng)的耗時(shí)
使用adb命令測(cè)量啟動(dòng)時(shí)間的特點(diǎn):
- 應(yīng)用的啟動(dòng)過(guò)程往往不只一個(gè)
Activity
,有可能是先進(jìn)入一個(gè)啟動(dòng)頁(yè)鸠儿,然后再?gòu)膯?dòng)頁(yè)打開(kāi)真正的首頁(yè)屹蚊。某些情況下還有可能中間經(jīng)過(guò)更多的Activity
,這個(gè)時(shí)候需要將多個(gè)Activity
的時(shí)間加起來(lái) - 將多個(gè)
Activity
啟動(dòng)時(shí)間加起來(lái)并不完全等于用戶感知的啟動(dòng)時(shí)間进每。例如在啟動(dòng)頁(yè)可能是先等待某些初始化完成或者某些動(dòng)畫(huà)播放完畢后再進(jìn)入首頁(yè)汹粤。使用命令行統(tǒng)計(jì)的方式只是計(jì)算了Activity
的啟動(dòng)以及初始化時(shí)間,并不能體現(xiàn)這種等待任務(wù)的時(shí)間 - 這種方式只能在線下使用田晚,不能再線上使用
手動(dòng)打點(diǎn)測(cè)量(推薦)
手動(dòng)打點(diǎn)就是啟動(dòng)時(shí)埋點(diǎn)嘱兼,啟動(dòng)結(jié)束埋點(diǎn),計(jì)算二者差值贤徒。這種方式比較精確芹壕,可以帶到線上測(cè)量用戶的啟動(dòng)時(shí)間,所以推薦使用這種方式
那么什么時(shí)候算啟動(dòng)開(kāi)始接奈,什么時(shí)候算啟動(dòng)結(jié)束呢踢涌?
我們先寫(xiě)一個(gè)測(cè)量啟動(dòng)時(shí)間的工具類
public class LaunchTimer {
private static long sTime;
public static void startRecord() {
sTime = System.currentTimeMillis();
}
public static void endRecord() {
endRecord("");
}
public static void endRecord(String msg) {
long cost = System.currentTimeMillis() - sTime;
LogUtils.i(msg + " cost " + cost + "ms");
}
}
啟動(dòng)時(shí)間點(diǎn)
啟動(dòng)時(shí)間點(diǎn)比較好記錄,我們一般會(huì)在Application attachBaseContext()
開(kāi)始記錄序宦,因?yàn)樵谶@之前 Context
還沒(méi)有初始化睁壁,一般也干不了什么事情,當(dāng)然這個(gè)是要視具體情況來(lái)定,其實(shí)只要保證在 App 的具體業(yè)務(wù)邏輯開(kāi)始執(zhí)行之前記錄起始時(shí)間點(diǎn)即可
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
LauncherTimer.startRecord();
}
結(jié)束時(shí)間點(diǎn)
我們究竟要在哪里進(jìn)行啟動(dòng)結(jié)束的記錄呢潘明?
一個(gè) Activity
走完onCreate
onStart
onResume
這幾個(gè)生命周期之后行剂,完成了應(yīng)用自身的一些配置,比如 Activity
主題設(shè)置 window
屬性的設(shè)置 View
樹(shù)的建立,但是其實(shí)后面還需要各個(gè) View
執(zhí)行 measure
layout
draw
等钳降。Activity
的首幀時(shí)間實(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.
...
*/
我們可以在onWindowFocusChanged
中進(jìn)行打點(diǎn),用于統(tǒng)計(jì)應(yīng)用從啟動(dòng)到首幀繪制的時(shí)間
private boolean mHasRecord;
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (!mHasRecord) {
mHasRecord = true;
LaunchTimer.endRecord("onWindowFocusChanged");
}
}
但是實(shí)際上對(duì)于用戶來(lái)說(shuō)牲阁,onWindowFocusChanged()
所統(tǒng)計(jì)出的首幀時(shí)間固阁,并不能夠代表這個(gè)頁(yè)面真正被展現(xiàn)出來(lái)了,為什么呢城菊?舉個(gè)例子备燃,假設(shè)我們的APP首頁(yè)是一個(gè)列表,這個(gè)列表的數(shù)據(jù)一般是從網(wǎng)絡(luò)上獲取的凌唬,那么用戶看到的”首幀“并齐,往往就是網(wǎng)絡(luò)請(qǐng)求成功后列表的第一個(gè)Item展示出來(lái)的時(shí)間。我們做性能優(yōu)化主要是為了能更好的改善用戶的體驗(yàn)客税,而不僅僅是為了數(shù)據(jù)上面的好看况褪,所以在做啟動(dòng)優(yōu)化的時(shí)候,我們不僅僅要考慮慮onWindowFocusChanged()
前的過(guò)程更耻,也要考慮onWindowFocusChanged()
后一直到用戶真正看到頁(yè)面的過(guò)程
正確的解答方式是我們要等真實(shí)的數(shù)據(jù)展示出來(lái)测垛,這樣才算啟動(dòng)結(jié)束了,這里我們采用Feed第一條數(shù)據(jù)的展示作為啟動(dòng)結(jié)束
public class HomeAdapter extends RecyclerView.Adapter {
....
//只需要記錄一次
private boolean mHasRecorded;
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
......
return viewholder;
}
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
if (position == 0 && !mHasRecorded) {
mHasRecorded = true;
holder.itemView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
//注銷監(jiān)聽(tīng)
holder.itemView.getViewTreeObserver().removeOnPreDrawListener(this);
//啟動(dòng)結(jié)束
LauncherTimer.endRecord("feedShow");
return true;
}
});
}
.....
}
@Override
public int getItemCount() {
return datas.size();
}
}
通過(guò)這種方式測(cè)量出來(lái)的啟動(dòng)時(shí)間和通過(guò)onWindowFocusChanged()
測(cè)量的啟動(dòng)時(shí)間一般差距是比較大的秧均,大家也可以自己實(shí)踐一下
Tips:addOnDrawListener要求API 16食侮,所以這里使用了addOnPreDrawListener
總結(jié)
本文我們介紹了啟動(dòng)優(yōu)化時(shí)間的測(cè)量,有adb和手動(dòng)打點(diǎn)兩種方式目胡,adb方式的缺點(diǎn)是測(cè)量結(jié)果和實(shí)際表現(xiàn)有偏差和不能帶到線上使用锯七,所以這里我推薦使用手動(dòng)打點(diǎn)的方式進(jìn)行啟動(dòng)時(shí)間測(cè)量,因?yàn)槲覀冏鰡?dòng)優(yōu)化要從真實(shí)用戶體驗(yàn)的角度出發(fā)誉己,而不是僅僅追求數(shù)據(jù)上的結(jié)果