# HenCoder Android 自定義 View 1-8 硬件加速

硬件加速這個詞每當被說起,不少人都會感興趣。這個詞給大部分人的概念大體有兩個:快速、不穩定。對不少人來講,硬件加速彷佛是一個只可遠觀而不可褻玩的高端科技:是,我據說它很牛逼,但我不敢「亂」用,由於我怕 hold 不住。html

今天我試着就把硬件加速的外衣脫掉(並無),聊一聊它的原理和應用:java

  1. 硬件加速的本質和原理;
  2. 硬件加速在 Android 中的應用;
  3. 硬件加速在 Android 中的限制。

本篇是 「HenCoder Android 開發進階」自定義 View 部分的最後一篇:硬件加速。android

若是你沒據說過 HenCoder,也能夠看看這個:
HenCoder:給高級 Android 工程師的進階手冊緩存

概念

在正式開始以前須要說明一下,做爲繪製部分的最後一期,本期內容只是爲了內容的完整性作一個補充,由於以前好幾期的內容裏都有涉及硬件加速的技術點,而一些讀者由於不瞭解硬件加速而產生了一些疑問。因此僅僅從難度上來說,這期的內容並不難,而且本期的大部份內容你均可以從這兩個頁面中找到:ide

  1. Hardware Acceleration | Android Developers
  2. Google I/O 2011: Accelerated Android Rendering

下面進入正題。post

所謂硬件加速,指的是把某些計算工做交給專門的硬件來作,而不是和普通的計算工做同樣交給 CPU 來處理。這樣不只減輕了 CPU 的壓力,並且因爲有了「專人」的處理,這份計算工做的速度也被加快了。這就是「硬件加速」。測試

而對於 Android 來講,硬件加速有它專屬的意思:在 Android 裏,硬件加速專指把 View 中繪製的計算工做交給 GPU 來處理。進一步地再明確一下,這個「繪製的計算工做」指的就是把繪製方法中的那些 Canvas.drawXXX() 變成實際的像素這件事。優化

原理

在硬件加速關閉的時候,Canvas 繪製的工做方式是:把要繪製的內容寫進一個 Bitmap,而後在以後的渲染過程當中,這個 Bitmap 的像素內容被直接用於渲染到屏幕。這種繪製方式的主要計算工做在於把繪製操做轉換爲像素的過程(例如由一句 Canvas.drawCircle() 來得到一個具體的圓的像素信息),這個過程的計算是由 CPU 來完成的。大體就像這樣:動畫

而在硬件加速開啓時,Canvas 的工做方式改變了:它只是把繪製的內容轉換爲 GPU 的操做保存了下來,而後就把它交給 GPU,最終由 GPU 來完成實際的顯示工做。大體是這樣:ui

如圖,在硬件加速開啓時,CPU 作的事只是把繪製工做轉換成 GPU 的操做,這個工做量相對來講是很是小的。

怎麼就「加速」了?

從上面的圖中能夠看出,硬件加速開啓後,繪製的計算工做由 CPU 轉交給了 GPU。不過這怎麼就能起到「加速」做用,讓繪製變快了呢?

硬件加速可以讓繪製變快,主要有三個緣由:

  1. 原本由 CPU 本身來作的事,分攤給了 GPU 一部分,天然能夠提升效率;
  2. 相對於 CPU 來講,GPU 自身的設計原本就對於不少常見類型內容的計算(例如簡單的圓形、簡單的方形)具備優點;
  3. 因爲繪製流程的不一樣,硬件加速在界面內容發生重繪的時候繪製流程能夠獲得優化,避免了一些重複操做,從而大幅提高繪製效率。

其中前兩點能夠總結爲一句:用了 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 的引入而提升了繪製效率,還因爲繪製機制的改變,而極大地提升了界面內容改變時的刷新效率。

因此把上面的三條壓縮總結一下,硬件加速更快的緣由有兩條:

  1. 用了 GPU,繪製變快了;
  2. 繪製機制的改變,致使界面內容改變時的刷新效率極大提升。

限制

若是僅僅是這樣,硬件加速只有好處沒有缺陷,那你們都沒必要關心硬件加速了,這篇文章也不會出現:既然是好東西就用唄,關心那麼多原理幹嘛?

可事實就是,硬件加速不僅是好處,也有它的限制:受到 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 來打開硬件加速這麼兩個參數,而是三個參數,在 SOFTWAREHARDWARE 以外還有一個 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 的:

  1. 參數爲 LAYER_TYPE_SOFTWARE 時,使用軟件來繪製 View Layer,繪製到一個 Bitmap,並順便關閉硬件加速;
  2. 參數爲 LAYER_TYPE_HARDWARE 時,使用 GPU 來繪製 View Layer,繪製到一個 OpenGL texture(若是硬件加速關閉,那麼行爲和 VIEW_TYPE_SOFTWARE 一致);
  3. 參數爲 LAYER_TYPE_NONE 時,關閉 View Layer。

View Layer 能夠加速無 invalidate() 時的刷新效率,但對於須要調用 invalidate() 的刷新沒法加速。

View Layer 繪製所消耗的實際時間是比不使用 View Layer 時要高的,因此要慎重使用。

相關文章
相關標籤/搜索