硬件加速這個詞每當被說起,不少人都會感興趣。這個詞給大部分人的概念大體有兩個:快速、不穩定。對不少人來講,硬件加速彷佛是一個只可遠觀而不可褻玩的高端科技:是,我據說它很牛逼,但我不敢「亂」用,由於我怕 hold 不住。html
今天我試着就把硬件加速的外衣脫掉(並無),聊一聊它的原理和應用:java
本篇是 「HenCoder Android 開發進階」自定義 View 部分的最後一篇:硬件加速。android
若是你沒據說過 HenCoder,也能夠看看這個:
HenCoder:給高級 Android 工程師的進階手冊緩存
在正式開始以前須要說明一下,做爲繪製部分的最後一期,本期內容只是爲了內容的完整性作一個補充,由於以前好幾期的內容裏都有涉及硬件加速的技術點,而一些讀者由於不瞭解硬件加速而產生了一些疑問。因此僅僅從難度上來說,這期的內容並不難,而且本期的大部份內容你均可以從這兩個頁面中找到:ide
下面進入正題。post
所謂硬件加速,指的是把某些計算工做交給專門的硬件來作,而不是和普通的計算工做同樣交給 CPU 來處理。這樣不只減輕了 CPU 的壓力,並且因爲有了「專人」的處理,這份計算工做的速度也被加快了。這就是「硬件加速」。測試
而對於 Android 來講,硬件加速有它專屬的意思:在 Android 裏,硬件加速專指把 View 中繪製的計算工做交給 GPU 來處理。進一步地再明確一下,這個「繪製的計算工做」指的就是把繪製方法中的那些 Canvas.drawXXX()
變成實際的像素這件事。優化
在硬件加速關閉的時候,Canvas
繪製的工做方式是:把要繪製的內容寫進一個 Bitmap
,而後在以後的渲染過程當中,這個 Bitmap
的像素內容被直接用於渲染到屏幕。這種繪製方式的主要計算工做在於把繪製操做轉換爲像素的過程(例如由一句 Canvas.drawCircle()
來得到一個具體的圓的像素信息),這個過程的計算是由 CPU 來完成的。大體就像這樣:動畫
而在硬件加速開啓時,Canvas
的工做方式改變了:它只是把繪製的內容轉換爲 GPU 的操做保存了下來,而後就把它交給 GPU,最終由 GPU 來完成實際的顯示工做。大體是這樣:ui
如圖,在硬件加速開啓時,CPU 作的事只是把繪製工做轉換成 GPU 的操做,這個工做量相對來講是很是小的。
從上面的圖中能夠看出,硬件加速開啓後,繪製的計算工做由 CPU 轉交給了 GPU。不過這怎麼就能起到「加速」做用,讓繪製變快了呢?
硬件加速可以讓繪製變快,主要有三個緣由:
其中前兩點能夠總結爲一句:用了 GPU,繪製就是快。緣由很直觀,再也不多說。
關於第三點,它的原理我大體說一下:
前面說到,在硬件加速關閉時,繪製內容會被 CPU 轉換成實際的像素,而後直接渲染到屏幕。具體來講,這個「實際的像素」,它是由 Bitmap
來承載的。在界面中的某個 View 因爲內容發生改變而調用 invalidate()
方法時,若是沒有開啓硬件加速,那麼爲了正確計算 Bitmap
的像素,這個 View
的父 View、父 View 的父 View 乃至一直向上直到最頂級 View,以及全部和它相交的兄弟 View
,都須要被調用 invalidate()
來重繪。一個 View 的改變使得大半個界面甚至整個界面都重繪一遍,這個工做量是很是大的。
而在硬件加速開啓時,前面說過,繪製的內容會被轉換成 GPU 的操做保存下來(承載的形式稱爲 display list,對應的類也叫作 DisplayList
),再轉交給 GPU。因爲全部的繪製內容都沒有變成最終的像素,因此它們之間是相互獨立的,那麼在界面內容發生改變的時候,只要把發生了改變的 View 調用 invalidate()
方法以更新它所對應的 GPU 操做就好,至於它的父 View 和兄弟 View,只須要保持原樣。那麼這個工做量就很小了。
正是因爲上面的緣由,硬件加速不只是因爲 GPU 的引入而提升了繪製效率,還因爲繪製機制的改變,而極大地提升了界面內容改變時的刷新效率。
因此把上面的三條壓縮總結一下,硬件加速更快的緣由有兩條:
若是僅僅是這樣,硬件加速只有好處沒有缺陷,那你們都沒必要關心硬件加速了,這篇文章也不會出現:既然是好東西就用唄,關心那麼多原理幹嘛?
可事實就是,硬件加速不僅是好處,也有它的限制:受到 GPU 繪製方式的限制,Canvas
的有些方法在硬件加速開啓式會失效或沒法正常工做。好比,在硬件加速開啓時, clipPath()
在 API 18 及以上的系統中才有效。具體的 API 限制和 API 版本的關係以下圖:
因此,若是你的自定義控件中有自定義繪製的內容,最好參照一下這份表格,確保你的繪製操做能夠正確地在全部用戶的手機裏可以正常顯示,而不是隻在你的運行了最新版本 Android 系統的 Nexus 或 Pixel 裏測試一遍沒問題就發佈了。當心被祭天。
不過有一點能夠放心的是,全部的原生自帶控件,都沒有用到 API 版本不兼容的繪製操做,能夠放心使用。因此你只要檢查你寫的自定義繪製就好。
在以前幾期的內容裏我提到過幾回,若是你的繪製操做不支持硬件加速,你須要手動關閉硬件加速來繪製界面,關閉的方式是經過這行代碼:
view.setLayerType(LAYER_TYPE_SOFTWARE, null);複製代碼
有很多人都有過疑問:什麼是 layer type?若是這個方法是硬件加速的開關,那麼它的參數爲何不是一個 LAYER_TYPE_SOFTWARE
來關閉硬件加速以及一個 LAYER_TYPE_HARDWARE
來打開硬件加速這麼兩個參數,而是三個參數,在 SOFTWARE
和 HARDWARE
以外還有一個 LAYER_TYPE_NONE
?難道還能既不用軟件繪製,也不用硬件繪製嗎?
事實上,這個方法的原本做用並非用來開關硬件加速的,只是當它的參數爲 LAYER_TYPE_SOFTWARE
的時候,能夠「順便」把硬件加速關掉而已;而且除了這個方法以外,Android 並無提供專門的 View 級別的硬件加速開關,因此它就「順便」成了一個開關硬件加速的方法。
setLayerType()
這個方法,它的做用其實就是名字裏的意思:設置 View Layer 的類型。所謂 View Layer,又稱爲離屏緩衝(Off-screen Buffer),它的做用是單獨啓用一塊地方來繪製這個 View ,而不是使用軟件繪製的 Bitmap 或者經過硬件加速的 GPU。這塊「地方」多是一塊單獨的 Bitmap
,也多是一塊 OpenGL 的紋理(texture,OpenGL 的紋理能夠簡單理解爲圖像的意思),具體取決於硬件加速是否開啓。採用什麼來繪製 View 不是關鍵,關鍵在於當設置了 View Layer 的時候,它的繪製會被緩存下來,並且緩存的是最終的繪製結果,而不是像硬件加速那樣只是把 GPU 的操做保存下來再交給 GPU 去計算。經過這樣更進一步的緩存方式,View 的重繪效率進一步提升了:只要繪製的內容沒有變,那麼不管是 CPU 繪製仍是 GPU 繪製,它們都不用從新計算,而只要只用以前緩存的繪製結果就能夠了。
多說一句,其實這個離屏緩衝(Off-screen Buffer),更準確的說應該叫作離屏緩存(Off-screen Cache)會更合適一點。緣由在上面這一段裏已經說過了,由於它實際上是緩存而不是緩衝。(這段話僅表明我的意見)
基於這樣的原理,在進行移動、旋轉等(無需調用 invalidate()
)的屬性動畫的時候開啓 Hardware Layer 將會極大地提高動畫的效率,由於在動畫過程當中 View 自己並無發生改變,只是它的位置或角度改變了,而這種改變是能夠由 GPU 經過簡單計算就完成的,並不須要重繪整個 View。因此在這種動畫的過程當中開啓 Hardware Layer,可讓原本就依靠硬件加速而變流暢了的動畫變得更加流暢。實現方式大概是這樣:
view.setLayerType(LAYER_TYPE_HARDWARE, null);
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotationY", 180);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
view.setLayerType(LAYER_TYPE_NONE, null);
}
});
animator.start();複製代碼
或者若是是使用 ViewPropertyAnimator
,那麼更簡單:
view.animate()
.rotationY(90)
.withLayer(); // withLayer() 能夠自動完成上面這段代碼的複雜操做複製代碼
不過必定要注意,只有你在對 translationX
translationY
rotation
alpha
等無需調用 invalidate()
的屬性作動畫的時候,這種方法才適用,由於這種方法自己利用的就是當界面不發生時,緩存未更新所帶來的時間的節省。因此簡單地說——
這種方式不適用於基於自定義屬性繪製的動畫。必定記得這句話。
另外,除了用於關閉硬件加速和輔助屬性動畫這兩項功能外,Layer 還能夠用於給 View 增長一些繪製效果,例如設置一個 ColorMatrixColorFilter
來讓 View 變成黑白的:
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.setSaturation(0);
Paint paint = new Paint();
paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
view.setLayerType(LAYER_TYPE_HARDWARE, paint);複製代碼
另外,因爲設置了 View Layer 後,View 在初次繪製時以及每次 invalidate()
後重繪時,須要進行兩次的繪製工做(一次繪製到 Layer,一次從 Layer 繪製到顯示屏),因此其實它的每次繪製的效率是被下降了的。因此必定要慎重使用 View Layer,在須要用到它的時候再去使用。
本期內容就到這裏,就像開頭處我說的,本期只是做爲一個完整性的補充,並無太多重要或高難度的東西,我也沒有準備視頻或太多的截圖或動圖來作說明。慣例總結一下:
硬件加速指的是使用 GPU 來完成繪製的計算工做,代替 CPU。它從工做分攤和繪製機制優化這兩個角度提高了繪製的速度。
硬件加速可使用 setLayerType()
來關閉硬件加速,但這個方法實際上是用來設置 View Layer 的:
LAYER_TYPE_SOFTWARE
時,使用軟件來繪製 View Layer,繪製到一個 Bitmap,並順便關閉硬件加速;LAYER_TYPE_HARDWARE
時,使用 GPU 來繪製 View Layer,繪製到一個 OpenGL texture(若是硬件加速關閉,那麼行爲和 VIEW_TYPE_SOFTWARE
一致);LAYER_TYPE_NONE
時,關閉 View Layer。View Layer 能夠加速無 invalidate()
時的刷新效率,但對於須要調用 invalidate()
的刷新沒法加速。
View Layer 繪製所消耗的實際時間是比不使用 View Layer 時要高的,因此要慎重使用。