HenCoder Android 自定义 View 1-8 硬件加速
硬件加速這個詞每當被提及,很多人都會感興趣。這個詞給大部分人的概念大致有兩個:快速、不穩定。對很多人來說,硬件加速似乎是一個只可遠觀而不可褻玩的高端科技:是,我聽說它很牛逼,但我不敢「亂」用,因為我怕 hold 不住。
今天我試著就把硬件加速的外衣脫掉(并沒有),聊一聊它的原理和應用:
本篇是 「HenCoder Android 開發進階」自定義 View 部分的最后一篇:硬件加速。
如果你沒聽說過 HenCoder,也可以看看這個:?
HenCoder:給高級 Android 工程師的進階手冊
概念
在正式開始之前需要說明一下,作為繪制部分的最后一期,本期內容只是為了內容的完整性做一個補充,因為之前好幾期的內容里都有涉及硬件加速的技術點,而一些讀者因為不了解硬件加速而產生了一些疑問。所以僅僅從難度上來講,這期的內容并不難,并且本期的大部分內容你都可以從這兩個頁面中找到:
下面進入正題。
所謂硬件加速,指的是把某些計算工作交給專門的硬件來做,而不是和普通的計算工作一樣交給 CPU 來處理。這樣不僅減輕了 CPU 的壓力,而且由于有了「專人」的處理,這份計算工作的速度也被加快了。這就是「硬件加速」。
而對于 Android 來說,硬件加速有它專屬的意思:在 Android 里,硬件加速專指把 View 中繪制的計算工作交給 GPU 來處理。進一步地再明確一下,這個「繪制的計算工作」指的就是把繪制方法中的那些?Canvas.drawXXX()?變成實際的像素這件事。
原理
在硬件加速關閉的時候,Canvas?繪制的工作方式是:把要繪制的內容寫進一個?Bitmap,然后在之后的渲染過程中,這個?Bitmap?的像素內容被直接用于渲染到屏幕。這種繪制方式的主要計算工作在于把繪制操作轉換為像素的過程(例如由一句?Canvas.drawCircle()?來獲得一個具體的圓的像素信息),這個過程的計算是由 CPU 來完成的。大致就像這樣:
而在硬件加速開啟時,Canvas?的工作方式改變了:它只是把繪制的內容轉換為 GPU 的操作保存了下來,然后就把它交給 GPU,最終由 GPU 來完成實際的顯示工作。大致是這樣:
如圖,在硬件加速開啟時,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 Layer
在之前幾期的內容里我提到過幾次,如果你的繪制操作不支持硬件加速,你需要手動關閉硬件加速來繪制界面,關閉的方式是通過這行代碼:
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() { @Overridepublic 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 的:
View Layer 可以加速無?invalidate()?時的刷新效率,但對于需要調用?invalidate()?的刷新無法加速。
View Layer 繪制所消耗的實際時間是比不使用 View Layer 時要高的,所以要慎重使用。
總結
以上是生活随笔為你收集整理的HenCoder Android 自定义 View 1-8 硬件加速的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 腾讯TBS加载网页无法自适应记录
- 下一篇: 第一次写,python爬虫图片,操作ex