Android應用UI性能分析
在使用App時會發現有些界面啟動卡頓、動畫不流暢、列表等滑動時也會卡頓出現這種情況,可以考慮對UI性能分析。
首先要清楚卡頓的原因,有以下幾種情況:
1. 人為在UI線程中做輕微耗時操作,導致UI線程卡頓;
2. 布局Layout過于復雜,無法在16ms內完成渲染;( Android系統每隔16ms會發出VSYNC信號重繪我們的界面(Activity).為什么是16ms, 因為Android設定的刷新率是60FPS(Frame Per Second), 也就是每秒60幀的刷新率, 約合16ms刷新一次.這就意味著, 我們需要在16ms內完成下一次要刷新的界面的相關運算, 以便界面刷新更新. 然而, 如果我們無法在16ms內完成此次運算會怎樣呢?例如, 假設我們更新屏幕的背景圖片, 需要24ms來做這次運算. 當系統在第一個16ms時刷新界面, 然而我們的運算還沒有結束, 無法繪出圖片. 當系統隔16ms再發一次VSYNC信息重繪界面時, 用戶才會看到更新后的圖片. 也就是說用戶是32ms后看到了這次刷新(注意, 并不是24ms). 這就是傳說中的丟幀(dropped frame),丟幀給用戶的感覺就是卡頓, 而且如果運算過于復雜, 丟幀會更多, 導致界面常常處于停滯狀態, 卡到爆.)
3. 同一時間動畫執行的次數過多,導致CPU或GPU負載過重;
4. View過度繪制,導致某些像素在同一幀時間內被繪制多次,從而使CPU或GPU負載過重;
5. View頻繁的觸發measure、layout,導致measure、layout累計耗時過多及整個View頻繁的重新渲染;
6. 內存頻繁觸發GC過多(同一幀中頻繁創建內存),導致暫時阻塞渲染操作;
7. 冗余資源及邏輯等導致加載和執行緩慢;
8. 臭名昭著的ANR;
如何分析?
分析UI卡頓我們一般都借助工具,通過工具一般都可以直觀的分析出問題原因,從而反推尋求優化方案,具體如下細說各種強大的工具
1. 使用HierarchyViewer分析UI性能
我們可以通過SDK提供的工具HierarchyViewer來進行UI布局復雜程度及冗余等分析
通過命令啟動HierarchyViewer
接下來Hierarchy window窗口打開:
一個Activity的View樹,通過這個樹可以分析出View嵌套的冗余層級,以及每個View在繪制的使用時長也有表示。
2. 使用Lint進行資源及冗余UI布局等優化
冗余資源及邏輯等也可能會導致加載和執行緩慢,這可以使用Link工具,來發現優化這些問題的
在AndroidStudio 1.4版本中使用Lint最簡單的辦法:就是將鼠標放在代碼區點擊右鍵->Analyze->Inspect Code–>界面選擇你要檢測的模塊->點擊確認開始檢測,等待一下后會發現如下結果:
如果存在冗余的UI層級嵌套,會進行高亮顯示, 我們根據提示可以點擊跳進去進行優化處理掉的。
3. 使用Memory監測及GC打印與Allocation Tracker進行UI卡頓分析
由于Android系統會依據內存中不同的內存數據類型分別執行不同的GC操作,常見應用開發中導致GC頻繁執行的原因主要可能是因為短時間內有大量頻繁的對象創建與釋放操作,也就是俗稱的內存抖動現象,或者短時間內已經存在大量內存暫用介于閾值邊緣,接著每當有新對象創建時都會導致超越閾值觸發GC操作
如何查看?
Android Studio 工具提供內存查看器:
根據內存抖動現象,查看log日志進行分析:
如何看到,這種不停的大面積打印GC導致所有線程暫停的操作必定會導致UI視覺的卡頓,所以我們要避免此類問題的出現,具體的常見優化方式如下:
- 檢查代碼,盡量避免有些頻繁觸發的邏輯方法中存在大量對象分配;
- 盡量避免在多次for循環中頻繁分配對象;
- 避免在自定義View的onDraw()方法中執行復雜的操作及創建對象(譬如Paint的實例化操作不要寫在onDraw()方法中等);
- 對于并發下載等類似邏輯的實現盡量避免多次創建線程對象,而是交給線程池處理。
有了上面說明GC導致的性能后我們就該定位分析問題了,我們可以通過運行DDMS->Allocation Tracker標簽打開一個新窗口,然后點擊Start Tracing按鈕,接著運行你想分析的代碼,運行完畢后點擊GetAllocations按鈕就能夠看見一個已分配對象的列表,如下:
UI布局優化措施有哪些?
盡可能的減少布局的嵌套層級(Google建議View樹的高度不宜超過10層。)
以前我們用Eclipse寫代碼時,自動生成的模板是以LinearLayout為根節點的,但是后面變成了RelativeLayout為根節點。
RelativeLayout可以讓視圖樹的層級少,但是LinearLayout的測量效率要高。
如果使用RelativeLayout,需要盡量避免嵌套;如果使用LinearLayout,保證層級不能太深。使用HierarchyViewer工具分析視圖樹,剔除沒有用到的布局元素
不用設置不必要的背景,避免過度繪制,比如父控件設置了背景色,子控件完全將父控件給覆蓋的情況下,那么父控件就沒必要設置背景了
<include>標簽
編寫頁面布局是可以將通用的UI提取出來,使用的時候使用<include>標簽將其引入。但是<include>標簽并不能減少布局層級,只能增加代碼可讀性。-
<merge>標簽
問:使用merge標簽有哪些優點?
答:<merge/>標簽可以用于減少View樹的層次來優化Android的布局,使用<include>包含布局的時候,系統會自動忽略merge層級。問:merge標簽有什么缺點么?
答:1. <merge/>只能作為XML布局的根標簽使用。
2.當Inflate以<merge/>開頭的布局文件時,必須指定一個父ViewGroup,并且必須設定attachToRoot為true。問:什么情況考慮使用Merge標簽?
答:1.子視圖不需要指定任何針對父視圖的布局屬性,例子中TextView僅僅需要直接添加到父視圖上用于顯示就行。
2.假如需要在LinearLayout里面嵌入一個布局(或者視圖),而恰恰這個布局(或者視圖)的根節點也是LinearLayout,這樣就多了一層沒有用的嵌套,無疑這樣只會拖慢程序速度。而這個時候如果我們使用merge根標簽就可以避免那樣的問題。 -
<ViewStub>標簽
問:使用ViewStub標簽有哪些優點?
答:<ViewStub>標簽可以實現對一個view進行延遲加載,是一個輕量級的布局,它聲明在xml布局中,在Activity加載布局時,被它包裹的View看不見也不占布局位置,最重要的是:它不會實例化里邊的View,也不會產生繪制的消耗,直到它被主動inflate.問:使用ViewStub有啥需要注意的嗎?
答:1. 在要渲染的布局中并不支持<merge/>標簽。
2.ViewStub.infalte方法不能調用兩次,否則會出現異常。(因為一旦ViewStub visible/inflated,則ViewStub將從視圖框架中移除,其id也會失效)問:ViewStub標簽和View.GONE的區別?
答:ViewStub標簽只有在顯示時才去渲染整個布局,但是View.GONE在初始化布局時就已經添加在布局樹上了。
<ViewStub>用法:
((ViewStub)findViewById(R.id.viewstub)).setVisibility(View.VISIBLE);
// or
View importPanel = ((ViewStub) findViewById(R.id.viewstub)).inflate();
Android性能優化之App應用啟動分析與優化
App啟動方式
通常來說, 一個App啟動也會分如下兩種不同的狀態:
1)冷啟動
當啟動應用時,后臺沒有該應用的進程,這時系統會重新創建一個新的進程分配給該應用,這個啟動方式就是冷啟動。冷啟動因為系統會重新創建一個新的進程分配給它,所以會先創建和初始化Application類,再創建和初始化MainActivity類(包括一系列的測量、布局、繪制),最后顯示在界面上。2.)熱啟動
當啟動應用時,后臺已有該應用的進程(例:按back鍵、home鍵,應用雖然會退出,但是該應用的進程是依然會保留在后臺,可進入任務列表查看),所以在已有進程的情況下,這種啟動會從已有的進程中來啟動應用,這個方式叫熱啟動。熱啟動因為會從已有的進程中來啟動,所以熱啟動就不會走Application這步了,而是直接走MainActivity(包括一系列的測量、布局、繪制),所以熱啟動的過程只需要創建和初始化一個MainActivity就行了,而不必創建和初始化Application,因為一個應用從新進程的創建到進程的銷毀,Application只會初始化一次。
啟動時間的測量
關于Activity啟動時間的定義
對于Activity來說,啟動時,首先執行的是onCreate()、onStart()、onResume()這些生命周期函數,但即使這些生命周期方法回調結束了,應用也不算已經完全啟動,還需要等View樹全部構建完畢,一般認為,setContentView中的View全部顯示結束了,算作是應用完全啟動了。
那么這個時間,實際上是Activity啟動,到Layout全部顯示的過程,但是要注意,這里并不包括數據的加載,因為很多App在加載時會使用懶加載模式,即數據拉取后,再刷新默認的UI。
基于上面的啟動流程我們盡量做到如下幾點
- Application的創建過程中盡量少的進行耗時操作
- 首屏Activity的渲染
關于Application
Application是程序的主入口,特別是很多第三方SDK都會需要在Application的onCreate里面做很多初始化操作,一般來說我們可以將這些初始化放在一個單獨的線程中處理, 為了方便今后管理,
優化的方法,無非是通過以下幾個方面:
1) 異步初始化
這個很簡單,就是讓App在onCreate里面盡可能的少做事情,而利用手機的多核特性,盡可能的利用多線程,例如一些第三方框架的初始化,如果能放線程,就盡量的放入線程中,最簡單的,你可以直接new Thread(),當然,你也可以通過公共的線程池來進行異步的初始化工作,這個是最能夠壓縮啟動時間的方式2) 后臺任務
使用IntentService不同于Service, 它是工作在后臺線程的.3) 界面預加載
當系統加載一個Activity的時候,onCreate()是一個耗時過程,那么在這個過程中,系統為了讓用戶能有一個比較好的體驗,實際上會先繪制一些初始界面,類似于PlaceHolder。
系統首先會讀取當前Activity的Theme,然后根據Theme中的配置來繪制,當Activity加載完畢后,才會替換為真正的界面。所以,Google官方提供的解決方案,就是通過android:windowBackground屬性,來進行加載前的配置,同時,這里不僅可以配置顏色,還能配置圖片.
自定義View如何優化內存?
- 不建議在draw或者layout的過程中去實例化對象