阿里P7移動互聯網架構師進階視頻(每日更新中)免費學習請點擊:https://space.bilibili.com/474380680
本篇文章將先從如下三個內容來介紹啓動速度與執行效率優化:android
一般來講,在安卓中應用的啓動方式分爲兩種:冷啓動和熱啓動。算法
一、冷啓動:當啓動應用時。後臺沒有該應用的進程,這時系統會又一次建立一個新的進程分配給該應用,這個啓動方式就是冷啓動。二、熱啓動:當啓動應用時,後臺已有該應用的進程(例:按back鍵、home鍵,應用盡管會退出,但是該應用的進程是依舊會保留在後臺,可進入任務列表查看)。因此在已有進程的狀況下。這樣的啓動會從已有的進程中來啓動應用。這個方式叫熱啓動。shell
一、冷啓動:冷啓動因爲系統會又一次建立一個新的進程分配給它。因此會先建立和初始化Application類,再建立和初始化MainActivity類(包含一系列的測量、佈局、繪製),最後顯示在界面上。緩存
二、熱啓動:熱啓動因爲會從已有的進程中來啓動,因此熱啓動就不會走Application這步了,而是直接走MainActivity(包含一系列的測量、佈局、繪製)。因此熱啓動的過程僅僅需要建立和初始化一個MainActivity便可了。而沒必要建立和初始化Application,因爲一個應用重新進程的建立到進程的銷燬。Application僅僅會初始化一次。架構
上面說的啓動是點擊app的啓動圖標來啓動的。而第二種方式是進入近期使用的列表界面來啓動應用,這樣的不該該叫啓動,應該叫恢復。app
在安卓系統上,應用在沒有進程的狀況下,應用的啓動都是這樣一個流程:當點擊app的啓動圖標時。安卓系統會從Zygote進程中fork建立出一個新的進程分配給該應用。以後會依次建立和初始化Application類、建立MainActivity類、載入主題樣式Theme中的windowBackground等屬性設置給MainActivity以及配置Activity層級上的一些屬性、再inflate佈局、當onCreate/onStart/onResume方法都走完了後最後才進行contentView的measure/layout/draw顯示在界面上,因此直到這裏,應用的第一次啓動纔算完畢,這時候咱們看到的界面也就是所說的第一幀。異步
因此,總結一下,應用的啓動流程例如如下:ide
Application的構造器方法——>attachBaseContext()——>onCreate()——>Activity的構造方法——>onCreate()——>配置主題中背景等屬性——>onStart()——>onResume()——>測量佈局繪製顯示在界面上。
在上面這個啓動流程中,不論什麼一個地方有耗時操做都會拖慢咱們應用的啓動速度,而應用啓動時間是用毫秒度量的。對於毫秒級別的快慢度量咱們仍是需要去精確的測量到究竟應用啓動花了多少時間。而依據這個時間來作衡量。函數
從點擊應用的啓動圖標開始建立出一個新的進程直到咱們看到了界面的第一幀,這段時間就是應用的啓動時間。工具
咱們要測量的也就是這段時間。測量這段時間可以經過adb shell命令的方式進行測量,這樣的方法測量的最爲精確。命令爲:
adb shell am start -W [packageName]/[packageName.MainActivity]
運行成功後將返回三個測量到的時間:
一、ThisTime:通常和TotalTime時間同樣。除非在應用啓動時開了一個透明的Activity預先處理一些事再顯示出主Activity,這樣將比TotalTime小。
二、TotalTime:應用的啓動時間。包含建立進程+Application初始化+Activity初始化到界面顯示。
三、WaitTime:通常比TotalTime大點,包含系統影響的耗時。
如下是測量一個應用冷啓動和熱啓動的時間:
冷啓動:
熱啓動:
可以看到在進程已經存在的狀況下。僅僅需要又一次初始化MainActivity。這樣的啓動比較快。只是大多數狀況下應用的啓動都是冷啓動。因爲用戶都會在任務列表中手動關閉遺留的應用進程。
針對冷啓動時候的一些耗時,如上測得這個應用算是中型的app,在冷啓動的時候耗時已經快700ms了,假設項目再大點在Application中配置了不少其它的初始化操做,這樣將可能達到1s,這樣每次啓動都明顯感受延遲。因此在進行應用初始化的時候採取如下策略:
一、在Application的構造器方法、attachBaseContext()、onCreate()方法中不要進行耗時操做的初始化,一些數據預取放在異步線程中,可以採取Callable實現。
二、對於sp的初始化,因爲sp的特性在初始化時候會對數據全部讀出來存在內存中,因此這個初始化放在主線程中不合適,反而會延遲應用的啓動速度,對於這個仍是需要放在異步線程中處理。
三、對於MainActivity,因爲在獲取到第一幀前。需要對contentView進行測量佈局繪製操做,儘可能下降佈局的層次。考慮StubView的延遲載入策略。固然在onCreate、onStart、onResume方法中避免作耗時操做。
遵循上面三種策略可明顯提升app啓動速度。
對於應用的啓動時間,僅僅能是儘可能的避免一些耗時的、非必要的操做在主線程中,這樣相對可以縮減一部分啓動的耗時,另一方面在等待第一幀顯示的時間裏,可以增長一些配置以增長體驗,比方增長Activity的background,這個背景會在顯示第一幀前提早顯示在界面上。
一、先爲主界面單獨寫一個主題style,設置一張待顯示的圖片,這裏我設置了一個顏色,而後在manifest中設置給MainActivity:
<style name="AppTheme.Launcher"> <item name="android:windowBackground">@drawable/bule</item> </style> //... <activity android:name=".MainActivity" android:label="@string/app_name" android:theme="@style/AppTheme.Launcher"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
二、而後在MainActivity中載入佈局前把AppTheme又一次設置給MainActivity:
@Override protected void onCreate(Bundle savedInstanceState) { setTheme(R.style.AppTheme); super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); }
這樣在啓動時會先顯示background,而後待界面繪製完畢再顯示主界面
把啓動白屏的背景換成一張圖片
<item name="android:windowBackground">@color/background_material_light</item>
換成
<item name="android:windowBackground">@drawable/xx.png</item>
這種是僞優化,並無解決加載速度。
把啓動白屏背景變透明
<item name="android:windowIsTranslucent">true</item>
style下面加上一句,意思是把啓動背景變透明,這種
這兩種方法最好新建一個樣式,只在引導頁中引用,若是在application節點中引用,會致使全部的頁面啓動都會有這種效果。
<style name="AppTheme.Launcher"> <item name="android:windowBackground">@drawable/xx</item> </style> <Activity Theme="AppTheme.Launcher"">...</Activity>
如何在引導頁啓動後恢復樣式呢?
在引導頁的onCreate(...)
{ setTheme(R.style.AppTheme); super.onCreate(...); }
用戶對卡頓的感知, 主要來源於界面的刷新. 而界面的性能主要是依賴於設備的UI渲染性能. 若是咱們的UI設計過於複雜, 或是實現不夠友好,計算繪製算法不夠優化, 設備又不給力, 界面就會像卡住了同樣, 給用戶卡頓的感受.
若是你的應用界面出現卡頓不流暢的狀況,不用懷疑,這很大緣由是你沒有在16ms完成你的工做。沒錯,16ms要完成你的工做,再慢點,用戶就會感受到卡頓,也許就會在屏幕對面開始吐槽你的APP,而後狠心把你辛辛苦苦開發出來的APP給卸載掉,打住,跑偏了!
Android 在不一樣的版本都會優化「UI的流暢性」問題,可是直到在android 4.1版本中作了有效的優化,這就是Project Butter。
Project Butter 加入了三個核心元素: VSYNC、Triple Buffer 和 Choreographer。其中,VSYNC 是理解Project Buffer的核心。
VSYNC:產生一箇中斷信號
Triple Buffer:當雙 Buffer 不夠使用時,該系統可分配第三塊 Buffer
Choreographer:這個用來接受一個 VSYNC 信號來統一協調UI更新
接下來咱們就逐個去解析這3個核心元素:
在瞭解VSYNC以前,咱們首先來了解一下咱們在 xml 寫的一個佈局是如何加載到Acitivty/Fragment中並最終 display 呢?,我相信這個過程大部分程序猿也並非很關心,由於 Android 底層都爲爲咱們搞定這一部分的處理。可是若是要了解16ms原則,咱們簡單瞭解下這個過程是很是有必要的。先看我簡單畫的一個圖:
從上面的圖能夠看出,CPU 會先把 Layout 中的 UI 組件計算成 polygons(多邊形)和 textures(紋理),而後通過 OpenGL ES 處理(這個處理過程很是複雜,感興趣的童鞋能夠繼續耕耘)。OpenGL ES處理完後再交給 GPU 進行柵格化渲染,渲染後 GPU 再將數據傳送給屏幕,由屏幕進行繪製顯示。
Activity 的界面之因此能夠被繪製到屏幕上其中有一個很重要的過程就是 柵格化(Resterization),柵格化簡單來講就是將向量圖轉化爲機器能夠識別的位圖的一個過程。其中很複雜也比較很耗時,GPU 就是用來加快柵格化的速度。瞭解了這個過程後,咱們在來理解 VSYNC
VSYNC 這個概念出來好久了,Vertical Synchronization,就是所謂的「垂直同步」。在 Android 中也沿用了這個概念,咱們也能夠把它理解爲「幀同步」。這個用來幹嗎的呢,就是爲了保證 CPU、GPU 生成幀的速度和 Display 刷新的速度保持一致。
Android 系統每 16ms(更準確的是大概16.6ms) 就會發出一次 VSYNC信號觸發 UI 渲染更新。大約屏幕一秒刷新60次,也就是說要求 CPU 和 GPU 每秒要有處理 60 幀的能力,一幀花費的時間在 16ms 內。
這個方案的原理主要是經過 Choreographer 類設置它的 FrameCallback 函數,當每一幀被渲染時會觸發回調 FrameCallback, FrameCallback 回調 void doFrame (long frameTimeNanos) 函數。一次界面渲染會回調 doFrame 方法,若是兩次 doFrame 之間的間隔大於 16.6ms 說明發生了卡頓。
若是你平時注意卡頓的日誌信息,那麼下面這個段log就不會陌生了
if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) { Log.i(TAG, "Skipped " + skippedFrames + " frames! " + "The application may be doing too much work on its main thread."); SKIPPED_FRAME_WARNING_LIMIT 的默認值是 30,也就說當咱們的程序卡頓大於 30 時會打印這條 log 信息
那麼在 Android系統中,是如何利用 VSYNC 工做的呢?
一句話總結:在 VSYNC 開始發出信號時,CPU和GPU已經就開始準備下一幀的數據了,趕在下個 VSYNC 信號到來時,GPU 渲染完成,及時傳送數據給屏幕,Display 繪製顯示完成。不出什麼意外的話,每一幀都會這麼井井有理進行着,在這種理想狀態下,用戶就會體驗到如絲般順滑的感受了。固然你也不會看到這篇博客了,囧!
上面總結的一句話,若是用更專業的術語來講就是一個名詞,雙緩衝機制
雙緩衝技術一直貫穿整個 Android 系統。由於實際上幀的數據就是保存在兩個 Buffer 緩衝區中,A 緩衝用來顯示當前幀,那麼 B 緩衝就用來緩存下一幀的數據,同理,B顯示時,A就用來緩衝!這樣就能夠作到一邊顯示一邊處理下一幀的數據。
這樣看起來貌似沒什麼問題,一切都是咱們的掌控中。可是,因爲某些緣由,好比咱們應用代碼上邏輯處理過於負責或者過於複雜的佈局,過分繪製(Overdraw),UI線程的複雜運算,頻繁的GC等,致使下一幀繪製的時間超過了16ms,那麼問題就來了,這時候用戶就不爽了,由於用戶很明顯感知到了卡頓的出現,也就是所謂的丟幀狀況。以下圖所示:
ok,下面咱們來認真分析一下爲何會出現丟幀的狀況:
一、當 Display 顯示第 0 幀數據時,此時 CPU 和 GPU 已經開始渲染第 1 幀畫面,並將數據緩存在緩衝 B 中。可是因爲某些緣由,就好像上面說的,致使系統處理該幀數據耗時過長或者未能及時處理該幀數據。
二、當 VSYNC 信號來時,Display 向 B 緩衝要數據,這時候 B 就藍瘦香菇了,由於緩衝 B 的數據還沒準備好,B緩衝區這時候是被鎖定的,Display 表示你沒準備好,我咋辦呢,無奈,只能繼續顯示以前緩衝 A 的那一幀,此時緩衝 A 的數據也不能被清空和交換數據。這種狀況就是所謂的「丟幀」,也被稱做「廢幀」;當第 1 幀數據(即緩衝 B 數據)準備完成後,它並不會立刻被顯示,而是要等待下一個 VSYNC,Display 刷新後,這時用戶纔看到畫面的更新。
三、當某一處丟幀後,大機率會影響後面的繪製也出現丟幀,最走給用戶感受就是卡頓了。最嚴重的直接形成ANR。
既然丟幀的狀況不可避免,Android 團隊從未放棄對這塊的優化處理,因而便出現了Triple Buffer(三緩衝機制)
在三倍緩衝機制中,系統這個時候會建立一個緩衝 C,用來緩衝下一幀的數據。也就是說在顯示完緩衝B中那一幀後,下一幀就是顯示緩衝 C 中的了。這樣雖然仍是不能避免會出現卡頓的狀況,可是 Android 系統仍是盡力去彌補這種缺陷,最終儘量給用平滑的動效體驗。
下面咱們就如下幾種狀況致使卡頓問題進行分析處理。
界面性能取決於 UI 渲染性能. 咱們能夠理解爲 UI 渲染的整個過程是由 CPU 和 GPU 兩個部分協同完成的。
其中, CPU 負責UI佈局元素的 Measure, Layout, Draw 等相關運算執行. GPU 負責柵格化(rasterization), 將UI元素繪製到屏幕上。
若是咱們的 UI 佈局層次太深, 或是自定義控件的 onDraw 中有複雜運算, CPU 的相關運算就可能大於16ms, 致使卡頓。
解決方案:
咱們須要藉助 Hierarchy Viewer 這個工具來幫咱們分析佈局了. Hierarchy Viewer 不只能夠以圖形化樹狀結構的形式展現出UI層級, 還對每一個節點給出了三個小圓點, 以指示該元素 Measure, Layout, Draw 的耗時及性能。
Overdraw: 用來描述一個像素在屏幕上多少次被重繪在一幀上.
通俗的說: 理想狀況下, 每屏每幀上, 每一個像素點應該只被繪製一次, 若是有屢次繪製, 就是 Overdraw, 過分繪製了。 常見的就是:繪製了多重背景或者繪製了不可見的UI元素.
解決方案:
Android系統提供了可視化的方案來讓咱們很方便的查看overdraw的現象:
在」系統設置」–>」開發者選項」–>」調試GPU過分繪製」中開啓調試:
此時界面可能會有五種顏色標識:
overdraw indicator
原色: 沒有overdraw
藍色: 1次overdraw
綠色: 2次overdraw
粉色: 3次overdraw
紅色: 4次及4次以上的overdraw
通常來講, 藍色是可接受的, 是性能優的.
UI線程的複雜運算會形成UI無響應, 固然更多的是形成UI響應停滯, 卡頓。產生ANR已是卡頓的極致了。
解決方案:
關於運算阻塞致使的卡頓的分析, 可使用 Traceview 這個工具。
上面說的都是處理上的CPU, GPU 相關的. 實際上內存緣由也可能會形成應用不流暢, 卡頓的。
爲何說頻繁的 GC 會致使卡頓呢?
簡而言之, 就是執行 GC 操做的時候,任何線程的任何操做都會須要暫停,等待 GC 操做完成以後,其餘操做纔可以繼續運行, 故而若是程序頻繁 GC, 天然會致使界面卡頓。
致使頻繁GC有兩個緣由:
內存抖動(Memory Churn), 即大量的對象被建立又在短期內立刻被釋放。
瞬間產生大量的對象會嚴重佔用 Young Generation 的內存區域, 當達到閥值, 剩餘空間不夠的時候, 也會觸發 GC。即便每次分配的對象須要佔用不多的內存,可是他們疊加在一塊兒會增長 Heap 的壓力, 從而觸發更多的 GC。
解決方案:
通常來講瞬間大量產生對象通常是由於咱們在代碼的循環中 new 對象, 或是在 onDraw 中建立對象等。
仍是是儘可能不要在循環中大量的使用局部變量。因此說這些地方是咱們尤爲須要注意的。
阿里P7移動互聯網架構師進階視頻(每日更新中)免費學習請點擊:https://space.bilibili.com/474380680
參考:https://www.jianshu.com/p/e5b...
https://blog.csdn.net/zhangga...