低性能的APP常見(jiàn)的表現(xiàn)有啟動(dòng)/界面切換慢洪灯、動(dòng)畫掉幀坎缭、卡頓、耗電签钩,甚至出現(xiàn)應(yīng)用無(wú)響應(yīng)掏呼、程序崩潰的現(xiàn)象。當(dāng)我們著手解決這些性能問(wèn)題時(shí)铅檩,面對(duì)的第一個(gè)問(wèn)題就是需要找到合適的工具來(lái)檢測(cè)這些問(wèn)題憎夷,用肉眼觀察來(lái)判斷定位這類問(wèn)題是不靠譜的。理想的檢測(cè)工具要能做到兩點(diǎn):
- 一是可以定性的告訴我們應(yīng)用是否有低性能問(wèn)題昧旨,并且能定位到的點(diǎn)拾给,指出哪個(gè)邏輯哪個(gè)方法使用系統(tǒng)資源低效,以便我們針對(duì)具體的問(wèn)題給出對(duì)應(yīng)的優(yōu)化方案兔沃;
- 二是能定量說(shuō)明問(wèn)題點(diǎn)的嚴(yán)重程度蒋得,有具體的數(shù)字來(lái)衡量,這樣我們對(duì)比優(yōu)化前后的測(cè)量數(shù)據(jù)乒疏,就可以清晰地看到優(yōu)化方案的收益和效果额衙。
為了滿足上面兩個(gè)需求,我們需要使用到systrace
和CPU Profiler
這兩個(gè)檢測(cè)工具缰雇。
工具一:systrace
這個(gè)工具的作用從名字‘system trace’系統(tǒng)跟蹤
就能看出一二入偷。我們主要是通過(guò)這個(gè)工具來(lái)記錄分析系統(tǒng)級(jí)函數(shù)的執(zhí)行情況。它從android內(nèi)核收集CPU調(diào)度械哟、存儲(chǔ)器訪問(wèn)疏之、應(yīng)用線程等信息,生成一張html格式的報(bào)表暇咆。這個(gè)工具還會(huì)自動(dòng)分析這些數(shù)據(jù)锋爪,高亮警示開(kāi)發(fā)者注意這些可能有問(wèn)題的地方。比如下圖UI掉幀的問(wèn)題爸业。但是systrace
只提供了系統(tǒng)方法調(diào)用信息其骄,沒(méi)有指明具體是因?yàn)檎{(diào)用我們APP程序哪個(gè)方法導(dǎo)致的,從systrace
中只能定位出大概是在首個(gè)Activity創(chuàng)建的時(shí)候發(fā)生的扯旷。想要獲取關(guān)于APP執(zhí)行的詳細(xì)信息就得使用今天的主角CPU Profiler
了拯爽。
點(diǎn)擊查看systrace更多詳情。
工具二:CPU Profiler
CPU Profiler
可實(shí)時(shí)檢查應(yīng)用的 CPU 使用率和線程運(yùn)行情況钧忽,并記錄函數(shù)跟蹤毯炮,以便我們優(yōu)化和調(diào)試相關(guān)代碼逼肯。簡(jiǎn)單點(diǎn)說(shuō)就是我們可以通過(guò)CPU Profiler查看應(yīng)用在某段時(shí)間里某個(gè)線程執(zhí)行了哪些方法,并且還定量的展示了執(zhí)行這些方法所耗費(fèi)的時(shí)間及其方法的調(diào)用堆棧桃煎。稍有經(jīng)驗(yàn)的程序員都知道應(yīng)用啟動(dòng)慢篮幢、界面切換慢、動(dòng)畫不流暢卡頓等類似問(wèn)題基本都是UI刷新不及時(shí)的表現(xiàn)为迈,UI刷新不及時(shí)就是因?yàn)閁I線程被其他邏輯方法長(zhǎng)時(shí)間占用導(dǎo)致三椿。吶,CPU Profiler
簡(jiǎn)直就是為了解決這個(gè)問(wèn)題而生的葫辐,輕松的分析出在卡頓(或者其他)過(guò)程中主線程都執(zhí)行了哪些耗時(shí)操作搜锰。
Google官方提供的Android開(kāi)發(fā)工具Android Studio附帶了很多開(kāi)發(fā)調(diào)試工具,這其中就包括了一系列的性能分析工具另患。在Android Studio 3.0之前這些工具叫Android Monitor tools纽乱,Android Studio 3.0開(kāi)始成為Android Profiler tools。我現(xiàn)在使用的Android Studio版本是3.2.1昆箕,這篇文章主要介紹的是3.2.1版本Android Profiler tools中的CPU Profiler - CPU分析器鸦列。下面我們來(lái)看下CPU Profiler的使用方法。
一鹏倘、CPU Profiler概覽
1薯嗤、打開(kāi)CPU Profile界面
- 點(diǎn)擊 View > Tool Windows > Android Profiler(也可以點(diǎn)擊工具欄中的 Android Profiler )。
- 在 Android Profiler 工具欄中點(diǎn)擊“+”號(hào)選擇您想要分析的設(shè)備和應(yīng)用進(jìn)程纤泵。 如果您通過(guò) USB 連接了某個(gè)設(shè)備但該設(shè)備未在設(shè)備列表中列出栋烤,請(qǐng)確保您已啟用 USB 調(diào)試砰盐。
- 點(diǎn)擊 CPU 時(shí)間線中的任意位置即可打開(kāi) CPU Profiler。Android Profiler 界面是CPU、內(nèi)存亏推、網(wǎng)絡(luò)阐污、電量4項(xiàng)性能數(shù)據(jù)共享時(shí)間線視圖叔遂,此共享時(shí)間線視圖只顯示時(shí)間線圖表微渠。 要詳細(xì)分析,需要點(diǎn)擊性能數(shù)據(jù)對(duì)應(yīng)的圖表循狰,打開(kāi)詳情視圖窟社。
打開(kāi) CPU Profiler 后,可以看到類似下圖的一些內(nèi)容绪钥。它將立即開(kāi)始顯示應(yīng)用的 CPU 使用率和線程 Activity灿里。
Event 時(shí)間線: 顯示應(yīng)用中在其生命周期不同狀態(tài)間轉(zhuǎn)換的 Activity,并表明用戶與設(shè)備的交互程腹,包括觸摸事件匣吊、按鍵點(diǎn)擊的事件。 如需了解有關(guān) Event 時(shí)間線的更多信息,包括如何啟用它色鸳,請(qǐng)閱讀 啟用高級(jí)分析侣灶。
CPU 時(shí)間線: 顯示應(yīng)用的實(shí)時(shí) CPU 使用率(以占總可用 CPU 時(shí)間的百分比表示)以及應(yīng)用使用的總線程數(shù)。 此時(shí)間線還顯示其他進(jìn)程的 CPU 使用率(如系統(tǒng)進(jìn)程或其他應(yīng)用)缕碎,以便您可以將其與您的應(yīng)用使用率進(jìn)行對(duì)比。 通過(guò)沿時(shí)間線的水平軸移動(dòng)鼠標(biāo)池户,您還可以檢查歷史 CPU 使用率數(shù)據(jù)咏雌。
-
線程活動(dòng)時(shí)間線: 列出屬于應(yīng)用進(jìn)程的每個(gè)線程并使用下面列出的顏色沿時(shí)間線標(biāo)示它們的狀態(tài)。 在記錄一個(gè)函數(shù)跟蹤后校焦,可以從此時(shí)間線中選擇一個(gè)線程以在跟蹤窗格中檢查其數(shù)據(jù)赊抖。
- 綠色區(qū)域: 表示線程處于活動(dòng)狀態(tài)或準(zhǔn)備使用 CPU。 即寨典,它正在“運(yùn)行中”或處于“可運(yùn)行”狀態(tài)氛雪。
- 黃色區(qū)域: 表示線程處于活動(dòng)狀態(tài),但它正在等待一個(gè) I/O 操作(如磁盤或網(wǎng)絡(luò) I/O)耸成,然后才能完成它的工作报亩。
- 灰色區(qū)域: 表示線程正在休眠且沒(méi)有消耗任何 CPU 時(shí)間。 當(dāng)線程需要訪問(wèn)尚不可用的資源時(shí)偶爾會(huì)發(fā)生這種情況井氢。 線程進(jìn)入自主休眠或內(nèi)核將此線程置于休眠狀態(tài)弦追,直到所需的資源可用。
Record按鈕:現(xiàn)在當(dāng)我們與應(yīng)用交互時(shí)花竞,可以通過(guò) CPU Profiler 監(jiān)控 CPU 使用率和線程狀態(tài)了劲件。 不過(guò),如想要了解應(yīng)用執(zhí)行代碼的詳細(xì)信息约急, 我們需要記錄和檢查函數(shù)跟蹤零远。
2、記錄和檢查函數(shù)跟蹤
要開(kāi)始記錄函數(shù)跟蹤厌蔽,從下拉菜單中選擇 Sampled 或 Instrumented 記錄配置牵辣,或選擇您創(chuàng)建的自定義記錄配置,然后點(diǎn)擊 Record 躺枕。 與應(yīng)用交互并在完成后點(diǎn)擊 Stop recording服猪。 分析器將自動(dòng)選擇記錄的時(shí)間范圍,并默認(rèn)在函數(shù)跟蹤窗格中顯示主線程函數(shù)跟蹤信息拐云,如下圖所示罢猪。如果您想檢查另一個(gè)線程的函數(shù)跟蹤,只需從線程活動(dòng)時(shí)間線中選中它叉瘩,函數(shù)跟蹤窗格就會(huì)切換成所選線程的函數(shù)跟蹤信息膳帕。
- 選擇時(shí)間范圍: 用于確定您要在跟蹤窗格中檢查所記錄時(shí)間范圍的哪一部分。 當(dāng)您首次記錄函數(shù)跟蹤時(shí),CPU Profiler 將在 CPU 時(shí)間線中自動(dòng)選擇您的記錄的完整長(zhǎng)度危彩。 如果您想僅檢查所記錄時(shí)間范圍一小部分的函數(shù)跟蹤數(shù)據(jù)攒磨,您可以點(diǎn)擊并拖動(dòng)突出顯示的區(qū)域邊緣以修改其長(zhǎng)度。
- 時(shí)間戳: 用于表示所記錄函數(shù)跟蹤的開(kāi)始和結(jié)束時(shí)間(相對(duì)于分析器從設(shè)備開(kāi)始收集 CPU 使用率信息的時(shí)間)汤徽。 在選擇時(shí)間范圍時(shí)娩缰,您可以點(diǎn)擊時(shí)間戳以自動(dòng)選擇完整記錄,如果您有多個(gè)要進(jìn)行切換的記錄谒府,則此做法尤其有用拼坎。
- 跟蹤窗格: 用于顯示您所選的時(shí)間范圍和線程的函數(shù)跟蹤數(shù)據(jù)。 僅在您至少記錄一個(gè)函數(shù)跟蹤后此窗格才會(huì)顯示完疫。 在此窗格中泰鸡,您可以選擇想如何查看每個(gè)堆疊追蹤(使用跟蹤標(biāo)簽),以及如何測(cè)量執(zhí)行時(shí)間(使用時(shí)間引用下拉菜單)壳鹤。
- 選擇后盛龄,可通過(guò) Top Down 樹(shù)、Bottom Up 樹(shù)芳誓、調(diào)用圖表或火焰圖的形式顯示您的函數(shù)跟蹤余舶。 您可以在下文中了解每個(gè)跟蹤窗格標(biāo)簽的更多信息。
- 從下拉菜單中選擇以下選項(xiàng)之一兆沙,以確定如何測(cè)量每個(gè)函數(shù)調(diào)用的時(shí)間信息:
- Wall clock time:壁鐘時(shí)間表示實(shí)際經(jīng)過(guò)的時(shí)間欧芽。
- Thread time:線程時(shí)間 = 實(shí)際經(jīng)過(guò)的時(shí)間 - 線程未消耗 CPU 資源的時(shí)間。 對(duì)于任何給定函數(shù)葛圃,其線程時(shí)間始終少于或等于其壁鐘時(shí)間千扔。 線程時(shí)間可以更好地了解到線程的實(shí)際 CPU 使用率中有多少是給定函數(shù)消耗的。
二库正、CPU Profiler 使用示例
上面把 CPU Profiler 工具的基本界面介紹了一下曲楚。下面我們通過(guò)兩個(gè)例子來(lái)進(jìn)一步了解下 CPU Profiler 的用法。
示例1:界面切換卡頓
問(wèn)題:首次打開(kāi)播放頁(yè)時(shí)界面卡頓褥符,待優(yōu)化龙誊。
分析:界面卡頓,應(yīng)該數(shù)主線有耗時(shí)操作導(dǎo)致UI刷新不及時(shí)喷楣。所以我們是用CPU Profiler來(lái)定位問(wèn)題趟大。
- AS(Android Studio)連接上手機(jī),手機(jī)上運(yùn)行要分析APP來(lái)到MainActivity铣焊,再在AS的Profiler窗口選擇要調(diào)試的進(jìn)程逊朽,打開(kāi)CPU Profiler。
- 然后在AS上點(diǎn)擊 Record 曲伊,再在手機(jī)上操作跳轉(zhuǎn)到PlayerActivity完成后點(diǎn)擊 Stop recording叽讳。得到如下圖所示信息。
- 仔細(xì)查看主線程Call Chart信息,發(fā)現(xiàn)播放頁(yè)的
onCreate()
里的MultiscreenManager.init();
耗用了絕大多數(shù)時(shí)間岛蚤。仔細(xì)查看這一方法作用是初始化投屏相關(guān)功能邑狸。(初始話方法的具體邏輯去掉了,調(diào)用了sleep方法來(lái)模擬)
Call Chart 標(biāo)簽提供函數(shù)跟蹤的圖形表示形式涤妒,其中单雾,水平軸表示函數(shù)耗費(fèi)的時(shí)間,垂直軸顯示其被調(diào)用者她紫。 對(duì)系統(tǒng) API 的函數(shù)調(diào)用顯示為橙色铁坎,對(duì)應(yīng)用自有函數(shù)的調(diào)用顯示為綠色,對(duì)第三方 API(包括 Java 語(yǔ)言 API)的函數(shù)調(diào)用顯示為藍(lán)色犁苏。
解決方案:問(wèn)題找到了解決就很容易了,投屏功能不是播放頁(yè)啟動(dòng)的必須初始化項(xiàng)目扩所,但是如果后置到用戶點(diǎn)擊投屏功能以后再去初始化投屏SDK围详,則會(huì)影響投屏功能的用戶體驗(yàn)。這種問(wèn)題有一個(gè)國(guó)際標(biāo)準(zhǔn)解決方案祖屏,把該任務(wù)添加到閑時(shí)任務(wù)系統(tǒng)中去助赞。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_player);
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
MultiscreenManager.init();
return false;
}
});
}
CPU Profiler 檢測(cè)時(shí)測(cè)量的函數(shù)耗時(shí)會(huì)大于真正線上包運(yùn)行的時(shí)間。一方面是函數(shù)跟蹤工具會(huì)加入額外的計(jì)時(shí)邏輯袁勺,另一方面它還會(huì)關(guān)閉虛擬機(jī)的JIT功能雹食。所以我們一般是對(duì)比優(yōu)化前后測(cè)量的兩組數(shù)據(jù)來(lái)判斷優(yōu)化的效果。
修改后再測(cè)試下數(shù)據(jù)期丰,onCreate()
方法耗時(shí)明顯降低群叶,投屏sdk初始化邏輯放在主線程空閑的時(shí)候去執(zhí)行的。還有一點(diǎn)注意下钝荡,可能出現(xiàn)從播放頁(yè)打開(kāi)到用戶請(qǐng)求投屏?xí)r街立,主線程一直不空閑,也就是我們的初始化任務(wù)沒(méi)有執(zhí)行情況埠通。所以加到閑時(shí)任務(wù)系統(tǒng)中的任務(wù)還得具備一個(gè)特點(diǎn)赎离,就是如果顯示任務(wù)沒(méi)有執(zhí)行,后續(xù)邏輯也要能正常運(yùn)行端辱。不過(guò)這個(gè)問(wèn)題也很容易解決梁剔,就是在后續(xù)邏輯執(zhí)行前先判斷下是否初始化了。
示例2:應(yīng)用冷啟動(dòng)慢
問(wèn)題:殺掉進(jìn)程后舞蔽,再次啟動(dòng)應(yīng)用慢荣病,待優(yōu)化。
分析:有了上面的經(jīng)驗(yàn)喷鸽,分析這個(gè)問(wèn)題也一樣众雷,先打開(kāi)APP,選擇要調(diào)試的進(jìn)程,點(diǎn)擊record按鈕跟蹤卡頓過(guò)程中函數(shù)調(diào)用信息砾省。好了鸡岗,問(wèn)題來(lái)了,我們這里是要抓取應(yīng)用冷啟動(dòng)過(guò)程函數(shù)調(diào)用信息编兄,但是要用CPU Profiler工具抓取信息得先指定進(jìn)程轩性。應(yīng)用啟動(dòng)前又沒(méi)有進(jìn)程信息可以指定。愁狠鸳,撓頭揣苏,程序員的頭就是這么給撓禿頂?shù)摹?/p>
這個(gè)時(shí)候就該Debug
類出場(chǎng)了。我們可使用 Debug
類精確地控制設(shè)備何時(shí)開(kāi)始和停止記錄函數(shù)跟蹤信息件舵,來(lái)生成一份函數(shù)跟蹤信息文件卸察。然后再使用 Android Studio 或 Traceview 查看各個(gè)跟蹤日志。
Traceview 有點(diǎn)過(guò)時(shí)了铅祸。如果我們使用的是3.2及其更新的Android Studio坑质,就沒(méi)有必要用Traceview了。
在開(kāi)始生成跟蹤日志之前临梗,要確保應(yīng)用有權(quán)限寫入外部存儲(chǔ)WRITE_EXTERNAL_STORAGE
涡扼,以便將跟蹤日志保存至該設(shè)備。創(chuàng)建跟蹤日志盟庞,在想系統(tǒng)開(kāi)始記錄跟蹤數(shù)據(jù)的位置調(diào)用 Debug.startMethodTracing()
吃沪,要停止跟蹤的位置請(qǐng)調(diào)用 Debug.stopMethodTracing()
。系統(tǒng)將在getExternalFilesDir()
目錄下生成 .trace
文件什猖,一般都在 ~/sdcard/Android/data/$packname/files
目錄中票彪。用Android Studio的Device File Explorer工具找到這個(gè)文件雙擊即可打開(kāi)。
public class DymApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
SimpleDateFormat date = new SimpleDateFormat("dd_MM_yyyy_hh_mm_ss");
String logDate = date.format(new Date());
// 在Application創(chuàng)建的時(shí)候開(kāi)始函數(shù)跟蹤
// 傳入的參數(shù)是函數(shù)跟蹤信息文件名不狮,加時(shí)間戳保證文件不會(huì)被覆蓋
Debug.startMethodTracing("sample-" + logDate);
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
try {
// TODO: 2018/12/7 怎么又是這個(gè)倒霉的sleep抹镊,為了測(cè)試APP啟動(dòng)卡頓問(wèn)題
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 在首頁(yè)Activity創(chuàng)建完成停止函數(shù)跟蹤
Debug.stopMethodTracing();
}
}
函數(shù)跟蹤日志生成了,分析就和之前沒(méi)什么兩樣了荤傲。
CPU Profiler 中還有一些信息也是很有用的垮耳,比如Top Down 和 Bottom Up ,這里沒(méi)有講到遂黍,大家可以自己去看下终佛,有什么不明白的,也可以在下面留言討論雾家。
三铃彰、結(jié)束
總的來(lái)說(shuō)就是 CPU Profiler 可以讓我們查看應(yīng)用進(jìn)程中的每個(gè)線程,某段時(shí)間內(nèi)執(zhí)行了哪些函數(shù)芯咧,以及在其執(zhí)行期間每個(gè)函數(shù)消耗的 CPU 資源(文章中耗時(shí)只得就是占用CPU的時(shí)間)牙捉。 還可以使用函數(shù)跟蹤來(lái)識(shí)別調(diào)用方和被調(diào)用方竹揍。 據(jù)此可以確定哪些函數(shù)負(fù)責(zé)調(diào)用常常會(huì)消耗大量特定資源的任務(wù),并嘗試優(yōu)化應(yīng)用代碼以避免不必要的開(kāi)支邪铲。
大家努力芬位,最大限度減少應(yīng)用的 CPU 使用率,向德芙巧克力一樣带到,在各種新舊設(shè)備上都能提供縱享絲滑的用戶體驗(yàn)昧碉。