上一篇文章Android 平臺(tái)側(cè)性能優(yōu)化之應(yīng)用啟動(dòng)采用多個(gè)命令分析了平臺(tái)的cpu/memory等方面對(duì)Email啟動(dòng)慢照成的影響。很遺憾花了許多精力,但依然沒有找出問題所在,這個(gè)坑終于填平了。手動(dòng)撒花_
回頭看來,問題其實(shí)很簡(jiǎn)單,一開始走錯(cuò)方向,導(dǎo)致花了許多精力,不過這個(gè)過程也同樣積累了不少知識(shí)。
本篇文章記錄填坑的過程,重新?lián)Q個(gè)角度分析。
曙光出現(xiàn):
同事在分析另外一個(gè)I/O讀取慢的問題時(shí)發(fā)現(xiàn)我們的設(shè)備是被加密的,得知這個(gè)消息后,我的內(nèi)心是激動(dòng)的。一度以為找到了問題的root cause.
adb shell getprop ro.crypto.state
用上述命令查看問題機(jī)的加密狀態(tài),返回encrypted,果然是已經(jīng)默認(rèn)加密了。感覺曙光出現(xiàn)了,我們的設(shè)備加密時(shí)將/data分區(qū)也進(jìn)行了加密,而應(yīng)用啟動(dòng)的文件正是放在/data分區(qū)下的。按理來講加密后的/data分區(qū)讀寫速度肯定弱于未加密狀態(tài)。問題會(huì)不會(huì)跟此相關(guān)呢?
我們采用的是高通8909平臺(tái)芯片,找到對(duì)應(yīng)的fstab.qcom文件。修改如下:
- /dev/block/bootdevice/by-name/userdata /data ext4 nosuid,nodev,barrier=1,noauto_da_alloc,discard wait,check,forceencrypt=footer
+ /dev/block/bootdevice/by-name/userdata /data ext4 nosuid,nodev,barrier=1,noauto_da_alloc,discard wait,check,encryptable=footer
關(guān)鍵就是修改這一句forceencrypt=footer===>encryptable=footer
forceencrypt表示強(qiáng)制加密,我們改成encryptable意味著加密是讓用戶主動(dòng)去選擇的。
懷著胸有成竹的心情驗(yàn)證修改后的版本。發(fā)現(xiàn)根本沒有起任何作用。whats's the fuck!說不過去啊。
滿以為找到突破口了,結(jié)果希望的火焰還是被無情的澆滅了。
還是IO速度
自己寫了一個(gè)test apk,專門測(cè)試平臺(tái)emcc的讀寫速度。測(cè)試問題機(jī)Log如下:
03-24 20:05:52.126 30578 30727 D Sunny-Test: start call writeData--->
03-24 20:05:52.144 30578 30727 D Sunny-Test-Utils: befWriteTime=93676689579157
03-24 20:05:52.144 30578 30727 D Sunny-Test-Utils: NewBufTime use time--->392292
03-24 20:06:12.656 30578 30727 D Sunny-Test-Utils: initBufData use time--->20512314888
03-24 20:06:12.656 30578 30727 D Sunny-Test-Utils: total create BufData use time--->20512707180
03-24 20:06:12.695 30578 30727 D Sunny-Test-Utils: aftWriteTime=93697240995816
03-24 20:06:12.695 30578 30727 D Sunny-Test-Utils: delta=20551416659
03-24 20:06:12.696 30578 30727 D Sunny-Test: start call writeData--->
03-24 20:06:12.710 30578 30727 D Sunny-Test-Utils: befWriteTime=93697255657795
03-24 20:06:12.710 30578 30727 D Sunny-Test-Utils: NewBufTime use time--->551198
03-24 20:06:33.188 30578 30727 D Sunny-Test-Utils: initBufData use time--->20477974992
03-24 20:06:33.188 30578 30727 D Sunny-Test-Utils: total create BufData use time--->20478526190
03-24 20:06:33.227 30578 30727 D Sunny-Test-Utils: aftWriteTime=93717772564923
03-24 20:06:33.227 30578 30727 D Sunny-Test-Utils: delta=20516907128
在測(cè)試參考機(jī)Log如下:
03-24 06:03:42.766 18541 18576 D Sunny-Test: start call writeData--->
03-24 06:03:42.780 18541 18576 D Sunny-Test-Utils: befWriteTime=7136372545141
03-24 06:03:42.787 18541 18576 D Sunny-Test-Utils: NewBufTime use time--->6635938
03-24 06:03:45.272 18541 18576 D Sunny-Test-Utils: initBufData use time--->2485714686
03-24 06:03:45.272 18541 18576 D Sunny-Test-Utils: total create BufData use time--->2492350624
03-24 06:03:45.359 18541 18576 D Sunny-Test-Utils: aftWriteTime=7138951725036
03-24 06:03:45.359 18541 18576 D Sunny-Test-Utils: delta=2579179895
03-24 06:03:45.360 18541 18576 D Sunny-Test: start call writeData--->
03-24 06:03:45.371 18541 18576 D Sunny-Test-Utils: befWriteTime=7138963657484
03-24 06:03:45.371 18541 18576 D Sunny-Test-Utils: NewBufTime use time--->387188
03-24 06:03:47.898 18541 18576 D Sunny-Test-Utils: initBufData use time--->2526351301
03-24 06:03:47.898 18541 18576 D Sunny-Test-Utils: total create BufData use time--->2526738489
03-24 06:03:47.983 18541 18576 D Sunny-Test-Utils: aftWriteTime=7141575492639
03-24 06:03:47.983 18541 18576 D Sunny-Test-Utils: delta=2611835155
可以看到
問題機(jī)寫入耗時(shí):Sunny-Test-Utils: delta=20516907128
參考機(jī)寫入耗時(shí):Sunny-Test-Utils: delta=2611835155
參考機(jī)比問題機(jī)寫入速度快了一個(gè)數(shù)量級(jí)。再用其他的工具測(cè)試I/O讀寫速度,同樣發(fā)現(xiàn)參考機(jī)比問題機(jī)快。但知道這個(gè)還是沒有辦法解決問題,也沒法量化I/O差異到底會(huì)對(duì)Email的啟動(dòng)速度影響多大。
問題到這一度陷入窘境,迷失了分析方向。是時(shí)候祭出systrace工具了。
插播 systrace 使用簡(jiǎn)介
systrace位于<sdk>/platform-tools/systrace目錄下。
我們主要分析Email的性能問題,因此可以用命令:
python systrace.py --app=com.tct.email gfx view sched dalvik -o email_launch.html
來抓取trace log分析啟動(dòng)過程。systrace 支持的trace類型可以通過:
python systrace.py -l
查看,用Android N下的systrace工具輸出結(jié)果如下:
輸入上述命令,然后啟動(dòng)Email,待主界面展示出來后,根據(jù)提示輸入回車鍵,此時(shí)systrace工具開始生成報(bào)告。
Tips:
systrace.py位于SDK/platform-tools/systrace目錄下。抓取時(shí)最好用系統(tǒng)對(duì)應(yīng)的SDK工具抓取。同時(shí)如果有源碼,也可以用源碼的external/chromium-trace/catapult/systrace/systrace/systrace.py 抓取。同樣注意抓取systrace的手機(jī)跟源碼版本一直。
然后打開chrome,在地址欄里輸入
點(diǎn)擊Load 按鈕,載入email_launch.html文件。顯示入下圖:
查看systrace主要用到4個(gè)快捷鍵:
W | 放大視圖 |
---|---|
S | 縮小視圖 |
A | 右移視圖 |
D | 左移視圖 |
systrace 報(bào)告分析
為了排除apk版本差異的干擾,將同版本的apk分別安裝到問題機(jī)跟參考機(jī)上。然后對(duì)比systrace報(bào)告。
先用TestActivityLaunchTime.py測(cè)試問題機(jī)以及參考機(jī)的啟動(dòng)時(shí)間。腳本獲取地址:https://github.com/guanglixiang/Android_Performance/blob/master/TestActivityLaunchTime.py
問題機(jī):
**************************************************
Test APK version info is:
versionCode=317022801 minSdk=17 targetSdk=24
versionName=v7.0.4.1.0312.0_0228
**************************************************
1 lunch time is 875
2 lunch time is 884
3 lunch time is 897
4 lunch time is 873
5 lunch time is 862
5 launch time average is 878
參考機(jī):
**************************************************
Test APK version info is:
versionCode=317022801 targetSdk=24
versionName=v7.0.4.1.0312.0_0228
versionCode=216030401 targetSdk=23
versionName=v5.2.10.3.0214.0
**************************************************
1 lunch time is 806
2 lunch time is 773
3 lunch time is 782
4 lunch time is 777
5 lunch time is 835
5 launch time average is 794
這里看差距貌似不大,但多次測(cè)試總是發(fā)現(xiàn)參考機(jī)就是比問題機(jī)快那么一點(diǎn)點(diǎn)。從配置上來將問題機(jī)跟參考機(jī)配置差不多,并且問題機(jī)的內(nèi)存還比參考機(jī)大。為什么就是慢那么一點(diǎn)點(diǎn)呢。
下面嘗試通過systrace文件去找原因。
這里我用命令:
./systrace.py gfx input view wm am sm app res dalvik bionic pm database sched freq idle disk mmc load -t 5 -o vJ5E-no-encrypt-no-net_emailv7.html
分別抓取參考機(jī)和問題機(jī)的systrace。終于發(fā)現(xiàn)了問題所在。
從systrace對(duì)比非常明顯的看到參考機(jī)沒有bindApplication的過程,而問題機(jī)卻在這個(gè)過程中消耗了0.226s。
我們知道冷啟動(dòng)應(yīng)用都是需要走bindApplication過程的。
bindApplication過程會(huì):
1.創(chuàng)建應(yīng)用進(jìn)程
2.加載應(yīng)用dex執(zhí)行文件
3.獲取應(yīng)用資源文件
4.makeApplication,初始化JavaContextClassLoader。
相應(yīng)的熱啟動(dòng)是不需要走上述過程的。
為了驗(yàn)證上述分析。可以有個(gè)簡(jiǎn)單的方法,只要先啟動(dòng)Email,然后點(diǎn)擊menu清除最近的應(yīng)用,最后通過ps命令查看email進(jìn)程是否還存在。
透過上面的分析可以確定參考機(jī)的Email應(yīng)用一直都在后臺(tái)運(yùn)行。而測(cè)試測(cè)冷啟動(dòng)case時(shí)只是點(diǎn)擊menu鍵,然后清除所有應(yīng)用,在去啟動(dòng)Email。參考機(jī)的Email逃過了這一劫,清除的過程中,進(jìn)程并沒有被殺死。而問題機(jī)的Email就沒那么幸運(yùn)了,直接被殺的它媽都不認(rèn)識(shí)它了。
解決辦法
由于參考機(jī)將Email配置成了后臺(tái)一直運(yùn)行,相當(dāng)于拿問題機(jī)的冷啟動(dòng)耗時(shí)對(duì)比參考機(jī)的熱啟動(dòng)應(yīng)用耗時(shí)。想要達(dá)到參考機(jī)的啟動(dòng)速度,在平臺(tái)側(cè)可以也可以效仿參考機(jī)將Email配置成后臺(tái)一直運(yùn)行。
修改方法:
在frameworks/base/services/core/java/com/android/server/am/ProcessRecord.java
void kill(String reason, boolean noisy) {
if("com.tct.email".equals(processName)
&& !KILL_APP_REASON_PERMISSIONS_REVOKED.equals(reason)) {
return;
}
//忽略其他code
}
但這種做法需要項(xiàng)目上根據(jù)需求做權(quán)衡。Email應(yīng)用本身會(huì)定時(shí)去check郵件列表,發(fā)起網(wǎng)絡(luò)請(qǐng)求,屬于高耗電程序。一直在后臺(tái)運(yùn)行會(huì)消耗電量。
寫在最后
Email問題陸陸續(xù)續(xù)的分析了兩周多,最開始一心要去從I/O速度上去分析個(gè)結(jié)果出來。沒有去想其它的可能。中間一度想放棄,打算將問題歸結(jié)到I/O,拋給底層同事去check。但心里始終不安,有空了就去想該問題。其實(shí)一開始也抓取過systrace,只是重點(diǎn)放在了分析I/O上,沒有跟參考機(jī)做對(duì)比。沒有對(duì)比就帶來了傷害啊TT
systrace是個(gè)好東西,有空打算專門寫篇針對(duì)該工具的文章。