上一篇文章Android 平臺側性能優(yōu)化之應用啟動采用多個命令分析了平臺的cpu/memory等方面對Email啟動慢照成的影響。很遺憾花了許多精力,但依然沒有找出問題所在,這個坑終于填平了。手動撒花_
回頭看來,問題其實很簡單,一開始走錯方向,導致花了許多精力,不過這個過程也同樣積累了不少知識。
本篇文章記錄填坑的過程,重新?lián)Q個角度分析。
曙光出現(xiàn):
同事在分析另外一個I/O讀取慢的問題時發(fā)現(xiàn)我們的設備是被加密的,得知這個消息后,我的內心是激動的。一度以為找到了問題的root cause.
adb shell getprop ro.crypto.state
用上述命令查看問題機的加密狀態(tài),返回encrypted,果然是已經默認加密了。感覺曙光出現(xiàn)了,我們的設備加密時將/data分區(qū)也進行了加密,而應用啟動的文件正是放在/data分區(qū)下的。按理來講加密后的/data分區(qū)讀寫速度肯定弱于未加密狀態(tài)。問題會不會跟此相關呢?
我們采用的是高通8909平臺芯片,找到對應的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
關鍵就是修改這一句forceencrypt=footer===>encryptable=footer
forceencrypt表示強制加密,我們改成encryptable意味著加密是讓用戶主動去選擇的。
懷著胸有成竹的心情驗證修改后的版本。發(fā)現(xiàn)根本沒有起任何作用。whats's the fuck!說不過去啊。
滿以為找到突破口了,結果希望的火焰還是被無情的澆滅了。
還是IO速度
自己寫了一個test apk,專門測試平臺emcc的讀寫速度。測試問題機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
在測試參考機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
可以看到
問題機寫入耗時:Sunny-Test-Utils: delta=20516907128
參考機寫入耗時:Sunny-Test-Utils: delta=2611835155
參考機比問題機寫入速度快了一個數(shù)量級。再用其他的工具測試I/O讀寫速度,同樣發(fā)現(xiàn)參考機比問題機快。但知道這個還是沒有辦法解決問題,也沒法量化I/O差異到底會對Email的啟動速度影響多大。
問題到這一度陷入窘境,迷失了分析方向。是時候祭出systrace工具了。
插播 systrace 使用簡介
systrace位于<sdk>/platform-tools/systrace目錄下。
我們主要分析Email的性能問題,因此可以用命令:
python systrace.py --app=com.tct.email gfx view sched dalvik -o email_launch.html
來抓取trace log分析啟動過程。systrace 支持的trace類型可以通過:
python systrace.py -l
查看,用Android N下的systrace工具輸出結果如下:

輸入上述命令,然后啟動Email,待主界面展示出來后,根據提示輸入回車鍵,此時systrace工具開始生成報告。

Tips:
systrace.py位于SDK/platform-tools/systrace目錄下。抓取時最好用系統(tǒng)對應的SDK工具抓取。同時如果有源碼,也可以用源碼的external/chromium-trace/catapult/systrace/systrace/systrace.py 抓取。同樣注意抓取systrace的手機跟源碼版本一直。
然后打開chrome,在地址欄里輸入

點擊Load 按鈕,載入email_launch.html文件。顯示入下圖:

查看systrace主要用到4個快捷鍵:
| W | 放大視圖 |
|---|---|
| S | 縮小視圖 |
| A | 右移視圖 |
| D | 左移視圖 |
systrace 報告分析
為了排除apk版本差異的干擾,將同版本的apk分別安裝到問題機跟參考機上。然后對比systrace報告。
先用TestActivityLaunchTime.py測試問題機以及參考機的啟動時間。腳本獲取地址:https://github.com/guanglixiang/Android_Performance/blob/master/TestActivityLaunchTime.py
問題機:
**************************************************
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
參考機:
**************************************************
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
這里看差距貌似不大,但多次測試總是發(fā)現(xià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
分別抓取參考機和問題機的systrace。終于發(fā)現(xiàn)了問題所在。


從systrace對比非常明顯的看到參考機沒有bindApplication的過程,而問題機卻在這個過程中消耗了0.226s。
我們知道冷啟動應用都是需要走bindApplication過程的。
bindApplication過程會:
1.創(chuàng)建應用進程
2.加載應用dex執(zhí)行文件
3.獲取應用資源文件
4.makeApplication,初始化JavaContextClassLoader。
相應的熱啟動是不需要走上述過程的。
為了驗證上述分析。可以有個簡單的方法,只要先啟動Email,然后點擊menu清除最近的應用,最后通過ps命令查看email進程是否還存在。

透過上面的分析可以確定參考機的Email應用一直都在后臺運行。而測試測冷啟動case時只是點擊menu鍵,然后清除所有應用,在去啟動Email。參考機的Email逃過了這一劫,清除的過程中,進程并沒有被殺死。而問題機的Email就沒那么幸運了,直接被殺的它媽都不認識它了。
解決辦法
由于參考機將Email配置成了后臺一直運行,相當于拿問題機的冷啟動耗時對比參考機的熱啟動應用耗時。想要達到參考機的啟動速度,在平臺側可以也可以效仿參考機將Email配置成后臺一直運行。
修改方法:
在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
}
但這種做法需要項目上根據需求做權衡。Email應用本身會定時去check郵件列表,發(fā)起網絡請求,屬于高耗電程序。一直在后臺運行會消耗電量。
寫在最后
Email問題陸陸續(xù)續(xù)的分析了兩周多,最開始一心要去從I/O速度上去分析個結果出來。沒有去想其它的可能。中間一度想放棄,打算將問題歸結到I/O,拋給底層同事去check。但心里始終不安,有空了就去想該問題。其實一開始也抓取過systrace,只是重點放在了分析I/O上,沒有跟參考機做對比。沒有對比就帶來了傷害啊TT
systrace是個好東西,有空打算專門寫篇針對該工具的文章。