1. 程式人生 > >Android 效能優化之應用啟動

Android 效能優化之應用啟動

寫在前面

最近工作轉到Android 效能優化方向,剛轉過來,相關經驗缺乏,紀錄一個目前讓人惱火的問題。非常遺憾,本文到目前為止還未能提供解決問題的優化方案,也沒有明確定位到導致效能問題的瓶頸所在。就像解數學題一樣,花費了大把時間,然並卵。之所以寫它,兩個目的:
- 這個過程中,自己還是有一定收穫的,且做紀錄。
- 丟擲問題,希望能與更多人一起討論效能優化的方法。

問題描述

冷啟動email應用相對於參考機慢。
參考機OS: AndroidM
問題機OS: AndroidN
參考機平均耗時:1.7s
問題機平均耗時:2.0s

問題分析

Email應用版本自身的影響

先檢視email版本是否相同,使用命令:

adb shell dumpsys package com.tct.email|grep version

得到問題機email版本—> versionName=v7.0.4.1.0312.0_0228
參考機email版本—> versionName=v5.2.10.3.0214.0
參考機與對比機email版本不同.因平臺Email應用與Exchange應用聯絡緊密,一併檢視Exchange版本。
問題機exchange版本—> versionName=v7.0.4.1.0312.0_0228
參考機exchange版本—>versionName=v5.2.10.3.0214.0

使用用命令:

adb shell am start -W -S com.tct.email/.activity.Welcome

檢視Email的啟動時間
為了方便統計,編寫一個簡單的指令碼封裝上述命令。指令碼簡短,在此貼出。

#!/usr/bin/env python
'''
@author: [email protected]
@summary: this script use to test launch activity time
'''

from commands import getoutput
from time import sleep
from __builtin__ import
int LAUNCH_PACKAGE = 'com.tct.email' LAUNCH_ACTIVITY = 'com.tct.email/.activity.Welcome' TEST_COUNT = 5 #測試次數 SLEEP_TIME = 10 #每次啟動的時間間隔 LOG_LINE = "**************************************************" def getVerInfo(): getVerCmd = "adb shell dumpsys package %s | grep version" % (LAUNCH_PACKAGE) verinfo = getoutput(getVerCmd) print LOG_LINE print "Test APK version info is:" print verinfo.strip() print LOG_LINE def getLaunchTime(): getLaunchTimeCmd = "adb shell am start -W -S %s |grep TotalTime|awk '{print $2}'" % (LAUNCH_ACTIVITY) totalTime = 0 for i in range(1, TEST_COUNT + 1): time = getoutput(getLaunchTimeCmd) print "%s lunch time is %s" % (i, time) totalTime += int(time) if (i < TEST_COUNT): sleep(SLEEP_TIME) i = i + 1 average = totalTime / TEST_COUNT print "%s launch time average is %s" % (TEST_COUNT, average) def main(): getVerInfo() getLaunchTime() if __name__ == '__main__': main()

其輸出結果如下:

指令碼執行結果圖

問題機 參考機
1 750
2 782
3 770
4 754
5 781

從結果看對比機明顯好於問題機,那麼這種差異是否由apk自身版本不一致導致呢?
為了回答這個問題,將問題機的emal安裝到參考機再次測試。
使用命令:

adb shell pm path com.tct.email

找到email的apk路徑
使用命令:

adb pull /system/app/Email/Email.apk

將問題機的Email拉出來。
使用命令:

adb install -r Email.apk

將剛拉出來的Email安裝至參考機,此時再次測試得到如下表格:

問題機 參考機 參考機+問題機的Email
1 750 674
2 782 649
3 770 649
4 754 633
5 781 671
均值 767 655

為了直觀表述,根據上述資料生成柱狀圖如下:
對比圖
從上述柱狀圖看Email應用的版本對最終測試資料的影響很大,本以為可以確定是APK版本差異導致的問題,然而最後測試“問題機+參考機Email”的組合發現啟動依然慢,資料沒有整理,就不貼出來了。

由於無法確定是APK版本差異導致的問題,因此該問題就劃歸在平臺側分析。

平臺影響

cpu差異

通過命令:

adb shell getprop ro.product.cpu.abi

可以看系統是32還是64位
問題機:armeabi-v7a,armeabi
參考機:armeabi-v7a
通過命令:

adb shell cat /proc/cpuinfo

檢視cpu資訊,二者cup一樣均為4核:

Hardware    : Qualcomm Technologies, Inc MSM8909
Revision    : 0000
Serial      : 0000000000000000
Processor   : ARMv7 Processor rev 5 (v7l)

排除CPU影響。

記憶體差異:

通過命令:

adb shell getprop dalvik.vm.heapstartsize

檢視系統分配給應用冷啟動的初始堆空間
問題機:16m
參考機:8m
排除該項。
通過命令:

adb shell cat /proc/meminfo

檢視系統總體的記憶體設定情況,得到下圖:
記憶體設定對比圖

利用excel生成柱狀圖直觀的看看各項值的差異,內容較多,只擷取差異最大的一部分。

記憶體差異項

可以很直觀的看到參考機和問題機上Committed_AS差異較大,其他各項二者相當,問題機都略高於參考機。尤其總記憶體大小問題機還比參考機高。好多項的具體含義我也不清楚,直觀感覺差異項最大的最可能包含著潛在的問題。
那麼這個 Committed_AS是表徵什麼的呢?

原來Committed_AS 表示所有程序已經申請的記憶體總大小,(注意是已經申請的,不是已經分配的),如果 Committed_AS 超>過 CommitLimit 就表示發生了 overcommit,超出越多表示 overcommit 越嚴重。Committed_AS 的含義換一種說法就是,如果要絕對保證不發生OOM (out of memory) 需要多少實體記憶體。
在打個比方,餐館有10張餐桌,假設1張餐桌只能接納1組客人。那麼一下來了100組客人,Committed_AS的含義餐館至少需要100張餐桌才能保證這些客人都能及時用餐。透過它能看到餐館的忙碌程度。

因為問題機的MemTot是大於參考機的,因此該項的差異不太可能是平臺側影響效能的主因,故而排除。

通過命令

adb shell getprop|grep dalvik

檢視問題機dalvik預設配置

[dalvik.vm.appimageformat]: [lz4]
[dalvik.vm.dex2oat-Xms]: [128m]
[dalvik.vm.dex2oat-Xmx]: [512m]
[dalvik.vm.heapgrowthlimit]: [128m]
[dalvik.vm.heapmaxfree]: [8m]
[dalvik.vm.heapminfree]: [512k]
[dalvik.vm.heapsize]: [256m]
[dalvik.vm.heapstartsize]: [32m]
[dalvik.vm.heaptargetutilization]: [0.75]
[dalvik.vm.image-dex2oat-Xms]: [128m]
[dalvik.vm.image-dex2oat-Xmx]: [128m]
[dalvik.vm.isa.arm.features]: [default]
[dalvik.vm.isa.arm.variant]: [cortex-a7]
[dalvik.vm.stack-trace-file]: [/data/anr/traces.txt]
[dalvik.vm.usejit]: [false]
[dalvik.vm.usejitprofiles]: [false]
[dalvik.vm.zygotemaxfailedboots]: [5]
[persist.sys.dalvik.vm.lib.2]: [libart.so]
[ro.dalvik.vm.native.bridge]: [0

這裡各項預設值跟參考機相比也沒發現異常。
猜測因記憶體差異導致的問題到這一步未找到有效證據。

I/O 讀寫速度差異

利用命令

adb shell vmstat -n 1

檢視Email啟動瞬間系統層的變化。得到如下輸出。關於vmstat的資訊,可以點選這裡

===================================問題機=====================================
procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa
0  0      0 269536  70424 1009964   0    0     0     0    0  388  1  1 99  0
*2  0      0 271228  70432 1009956   0    0     0   104    0 2653 14 17 68  0
*6  0      0 252056  70444 1010004   0    0     0   160    0 8102 35 23 42  0
0  0      0 248168  70444 1010004   0    0     0     0    0 2487 11  3 86  0


===================================參考機=====================================
procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
r b swpd free buff cache si so bi bo in cs us sy id wa
0 0 29596 89580 55240 781332 0 0 0 0 0 829 2 2 97 0
*6 0 29596 90784 55268 781304 0 0 0 800 0 4580 0 0 0 0
*1 0 29596 74996 55268 781316 0 0 0 128 0 5332 33 12 55 0
0 0 29596 76220 55268 781368 0 0 0 0 0 2118 0 0 100 0

以上輸出開頭加*的行是啟動Email時對應的變化(*是為了方便描述,自行新增的)。
下面來挨個過一遍。
procs欄裡的r(running),它表示執行佇列中的程序數,即多少個程序分配到了CPU. 二者相差不大,看不出問題。
b(blocked) 被阻塞的程序數,二者均為0,表示沒有程序被阻塞。
memory欄裡的swpd。
特意在linux論壇查了下其含義,看看其中某段解釋:

Note that a swap usage rate of zero is highly desirable because it means that your existing RAM can handle the load that’s being put on it without resorting to writing memory pages to disk. In other words, anytime your system has to access swap, you will be taking a huge performance hit – access times in RAM are measured in nanoseconds; access times on hard drives are measured in milliseconds. That’s about 6 orders of magnitude worse, and you should avoid it if at all possible.
To say it another way, Linux treats swap space as a resource of last resort. If the demands you are putting on your machine are such that RAM is overloaded and cannot handle it, then your system will write out memory pages to disk, but as mentioned this is only done when RAM is entirely overloaded. Back in the days were 64Mg was considered leading edge, it was pretty common for swap to be called into service, but these days, where 256Mg if not 512Mg of RAM is standard, it’s increasingly infrequent that swap gets used.
Overall, I’d consider zero swap usage to be a compliment to how you’ve set up your system. That being said, reserving some swap space (256Mg max, regardless of the RAM) is a useful thing, just in case. – J.W.

swpd表示已經使用的虛擬記憶體大小. 如果大於0,表示手機實體記憶體不足,需要將部分記憶體內容轉移到磁碟上。如果不是應用記憶體洩漏的原因,就是當前後臺執行的任務過多了。問題機為0,這個是個好的現象,說明問題機至少不存在記憶體吃緊的情況。

memory欄裡的free表示空閒記憶體大小,同樣問題機大於參考機,這也是好現象。
memory欄裡的buff/cache兩臺機器也沒有異常值排除。

swap欄裡的si,表示每秒從磁碟(disk)讀入的記憶體大小(/s). 如果這個值大於0,表示實體記憶體不足或者記憶體洩漏,需要查詢耗記憶體的程序。
swap欄裡的so,表示每秒從記憶體寫入磁碟的記憶體大小,他的資料不為0,表示當前記憶體不夠用,需要將部分記憶體的內容騰出來到磁碟空間。
上述兩項值在問題機和參考機上都為0.說明這裡不是問題所在,故而排除。

io欄裡的bi表示手機裡塊裝置每秒接收的塊數量,對應寫操作。而bo正好相反表示每秒傳送的塊數量,對應讀操作。這裡看到bi均為0,表示問題機和參考機在啟動Email的階段都沒有寫入操作,但bo值參考機明顯大於問題機。這裡可能是個問題,想象這樣一個場景,啟動Email時需要讀取執行的code段,而問題機一次讀取的code段少於參考機,那麼相應的問題機在這段時間可執行的code段也就少於參考機,進而有可能影響最終的啟動速度。
為了驗證該猜想,調整I/O讀寫速度在來看該問題。
使用命令:

adb shell echo 1024 > /sys/block/mmcblk0/queue/max_sectors_kb

將max_sectors_kb擴大一倍,之前是512. max_sectors_kb值代表傳送到磁碟的請求數。然而結果依然沒有提升。想不通為何。

system欄裡的in(interrupts) 表示在delay的時間內(預設為1秒)系統產生的中斷數,二者均為0,排除
system欄裡的cs (context switch) 表示在delay的時間內(預設為1秒)系統上下文切換的次數. 例如程序切換,要進行上下文切換;執行緒的切換,要進行上下文切,程式碼執行進入核心空間,上下文也要切換。這個值越小越好,太大了要考慮降低執行緒或者程序數目。因為上下文切換很耗資源,次數過多表示CPU大部分浪費在上下文切換而不是執行程式內容,CPU沒有充分利用。該項資料參考機是好於問題機的。

cpu欄裡的us表示使用者空間執行任務所佔CPU的時間百分比。看不出問題排除。
cpu欄裡的sy表示kernel空間執行任務佔用的CPU時間比 , 如果太高,表示系統呼叫時間過長,例如IO操作頻繁。這裡問題機明顯高於參考機,提示存在問題。
cpu欄裡的id表示空閒CPU佔用的時間比。一般來說,id + us + sy 接近 100%
cpu欄裡的wa表示CPU等待IO完成的時間佔比,該值若大,對系統的流程性衝擊大。問題機和參考機都為0,看不出問題。

寫在最後

問題還在持續分析中,如果看到這篇文章的你,有什麼好的排查方向,或者排查方法,歡迎一起討論。

相關推薦

Android 效能優化應用啟動

寫在前面 最近工作轉到Android 效能優化方向,剛轉過來,相關經驗缺乏,紀錄一個目前讓人惱火的問題。非常遺憾,本文到目前為止還未能提供解決問題的優化方案,也沒有明確定位到導致效能問題的瓶頸所在。就像解數學題一樣,花費了大把時間,然並卵。之所以寫它

Android效能優化 App啟動原理分析及速度和時間優化

應用的啟動速度緩慢這是很多開發者都遇到的一個問題,比如啟動緩慢導致的黑屏,白屏問題,大部分的答案都是做一個透明的主題,或者是做一個Splash介面,但是這並沒有從根本上解決這個問題。那麼如何從根本上解決這個問題或者做到一定程度的緩解? 一、應用的啟動方式 1、冷啟動:

Android效能優化啟動優化

1.什麼是冷啟動[啟動時間比較長]:在應用啟動前,系統沒有該應用的任何程序資訊。2.什麼是熱啟動[啟動時間比較短] :使用者按了返回鍵,又馬上重新啟動了此應用。3.冷啟動會走application這個類,熱啟動就不會走application這個類4.冷啟動流程5.冷啟動優化 

Android——效能優化SparseArray

相信大家都用過HashMap用來存放鍵值對,最近在專案中使用HashMap的時候發現,有時候 IDE 會提示我這裡的HashMap可以用SparseArray或者SparseIntArray等等來代替。 SparseArray(稀疏陣列).它是Android內部特有的api,標準的jdk是沒有這

Android效能優化較精確的獲取影象顯示到螢幕上的時間

轉載自:http://blog.desmondyao.com/android-show-time/ 這兩天我的包工頭歪龍木·靈魂架構師·王半仙·Yrom給我派了一個活:統計App冷啟動時間。這個任務看上去不難,但是要求統計出來的時間要準,要特別準。 意思就是,我必須要按Activity繪製到

Android效能測試啟動時間

          冷啟動是Android效能測試中的重要指標,即應用從程序未建立到完全啟動的時間,一般要求時長<1.5s,過長需要考慮優化。 獲取冷啟動時間的方法: 1.用命令列  adb shell am start

Android效能優化圖片壓縮優化

1 分類Android圖片壓縮結合多種壓縮方式,常用的有尺寸壓縮、質量壓縮、取樣率壓縮以及通過JNI呼叫libjpeg庫來進行壓縮。 參考此方法:Android-BitherCompress 備註:對於資源圖片直接使用:tiny壓縮 2 質量壓縮(1)原理:保持畫素的前提下改變圖片的位深及透明度,(即:通

Android效能優化apk瘦身技巧

隨著專案迭代,新功能的增加。回導致apk越大。那麼在下載安裝過程中。使用者耗費的流量越多。 安裝等待的時間也會越長。這就意味著下載轉化率會越低。那麼如何apk瘦身呢? 理解APK結構 在討論怎麼減小Apk體積之前,理解一個應用的APK結構是非常有幫助的。一個ap

效能優化App啟動時間

App啟動模式分類 1.冷啟動 冷啟動狀態:系統不存在該應用的程序。啟動應用才能創建出應用的程序。 一般是中應用在開機後或者系統停止後的第一次啟動過程。因為系統和應用在冷啟動時需要做跟多的工作 所以減少

Android效能優化佈局優化

          佈局優化可以通過減少佈局層級來提高,儘量減少使用效能低的佈局,LineaLayout的效率最高,在可以使用LinearLayout或者RelativeLayout時,選擇LinearLayout。因為RelativeLayout測量較為複雜,需要測量水平和

Android效能優化實現擁有Looper的執行緒--HandlerThread

1 HandlerThread 1.1 定義   HandlerThread是能夠新建擁有Looper的Thread,這個Looper能夠用來新建其他的Handler。HandlerThread本質是一個執行緒,線上程內部,程式碼是序列處理的。(執行緒中

Android 效能優化String篇

Android 效能優化之 String篇 關於String相關知識都是老掉牙的東西了,但我們經常可能在不經意的String 字串拼接的情況下浪費記憶體,影響效能,也常常會成為觸發記憶體OOM的最後一步。 所以本文對String字串進行深度解析,有

Android效能優化:XML佈局檔案優化

Android中XML佈局檔案的使用非常頻繁,在載入XML佈局的時候,如果對XML檔案其進行優化,將會提高載入的效率。 HierarchyViewer工具 再開始介紹之前先說一下HierarchyViewer工具的使用。 不合理的佈局會使我們的應用程式

Android 效能優化——管理應用的記憶體

請保持淡定,分析程式碼,記住:效能很重要。 隨機存取儲存器(RAM)在任何軟體開發環境中都是一個很寶貴的資源。這一點在實體記憶體通常很有限的移動作業系統上,顯得尤為突出。儘管 Android Runtime(ART)和 Dalvik 虛擬機器都扮演了常

Android效能優化提高ListView效能的技巧

ListView優化一直是一個老生常談的問題,不管是面試還是平常的開發中,ListView永遠不會被忽略掉,那麼這篇文章我們來看看如何最大化的優化ListView的效能。 1.在adapter中的getView方法中儘量少使用邏輯 2.盡最大可能避免GC 3

Android 效能優化記憶體洩漏檢測以及記憶體優化(中)

Android 記憶體洩漏檢測   通過上篇部落格我們瞭解了 Android JVM/ART 記憶體的相關知識和洩漏的原因,再來歸類一下記憶體洩漏的源頭,這裡我們簡單將其歸為一下三類:自身編碼引起由專案開發人員自身的編碼造成;第三方程式碼引起這裡的第三

Android 效能優化記憶體檢測、卡頓優化、耗電優化、APK瘦身

導語 自2008年智慧時代開始,Android作業系統一路高歌,10年智慧機發展之路,如今 Android 9.0 代號P  都發布了,Android系統性能已經非常流暢了。但是,到了各大廠商手裡,改原始碼自定系統,使得Android原生系統變得魚龍混雜。另外,到了不同層次的

Android效能優化被忽視的Memory Leaks

起因 寫部落格就像講故事,得有起因,經過,結果,人物,地點和時間。今天就容我給大家講一個故事。人物呢,肯定是我了。故事則發生在最近的這兩天,地點在coder君上班的公司。那天無意中我發現了一個奇怪的現象,隨著我點開我們App的頁面,Memory Monitor

Android 效能優化記憶體洩漏的檢測與修復

在 Android 開發中, 記憶體優化是APP效能優化中很重要的一個部分. 而在記憶體優化中, 最重要的就是修復記憶體洩漏問題. 本文就來介紹一下記憶體洩漏的基本概念以及常用的檢測手段. 1. 什麼是記憶體洩漏 簡單來說, 當一個物件不再被使用時,

Android效能優化Android安裝包大小優化

  隨著應用業務功能的日益複雜,使用的第三方庫和SDK也會日益增加,這樣導致的直接後果就是安裝包的體積日漸增加,除了使用外掛化的手段,減少安裝包的體積,我們還可以從以下幾個方面著手,進行安裝包大小的優化。   程式碼混淆   ProGuard是一個開源的Java程式碼混淆器