Android自定義View(八) -- 硬件加速
前面學(xué)習(xí)的內(nèi)容:
Android自定義View(一) -- 初識(shí)
Android自定義View(二) -- Paint詳解
Android自定義View(三) -- drawText()
Android自定義View(四) -- Canvas
Android自定義View(五) -- 繪制順序
Android自定義View(六) -- 屬性動(dòng)畫(上)
Android自定義View(七) -- 屬性動(dòng)畫(下)
今天學(xué)習(xí)自定義View部分的最有一篇:硬件加速因?yàn)闊o(wú)法錄制GIF,所以本篇內(nèi)容基本為原博
本文計(jì)劃根據(jù)HenCoder系列文章進(jìn)行學(xué)習(xí),所以代碼風(fēng)格及博文素材可能會(huì)摘自其中
硬件加速經(jīng)常被提及,很多人感興趣,這個(gè)詞給人的概念大概有兩種:快速、不穩(wěn)定。
對(duì)很多人來(lái)說(shuō),硬件加速似乎是一個(gè)只可遠(yuǎn)觀而不可褻玩的高科技:是,聽說(shuō)很牛逼,但是不敢亂用,甚至不知道什么時(shí)候使用
今天就試著把硬件加速的原理和應(yīng)用,好好了解一下:
1.硬件加速的本質(zhì)和原理;
2.硬件加速在Android中的應(yīng)用;
3.硬件加速在Android正宗的限制。
概念
在正式開始之前需要說(shuō)明一下,作為繪制部分的最后一期,本期內(nèi)容只是為了內(nèi)容的完整性做一個(gè)補(bǔ)充,因?yàn)橹昂脦灼诘膬?nèi)容里都有涉及硬件加速的技術(shù)點(diǎn),而一些讀者因?yàn)椴涣私庥布铀俣a(chǎn)生了一些疑問(wèn)。所以僅僅從難度上來(lái)講,這期的內(nèi)容并不難,并且本期的大部分內(nèi)容你都可以從這兩個(gè)頁(yè)面中找到:
下面進(jìn)入正題。
所謂硬件加速,指的是把某些計(jì)算工作交給專門的硬件來(lái)做,而不是和普通的計(jì)算工作一樣交給 CPU 來(lái)處理。這樣不僅減輕了 CPU 的壓力,而且由于有了「專人」的處理,這份計(jì)算工作的速度也被加快了。這就是「硬件加速」。
而對(duì)于 Android 來(lái)說(shuō),硬件加速有它專屬的意思:在 Android 里,硬件加速專指把 View 中繪制的計(jì)算工作交給 GPU 來(lái)處理。進(jìn)一步地再明確一下,這個(gè)「繪制的計(jì)算工作」指的就是把繪制方法中的那些 Canvas.drawXXX() 變成實(shí)際的像素這件事。
原理
在硬件加速關(guān)閉的時(shí)候,Canvas 繪制的工作方式是:把要繪制的內(nèi)容寫進(jìn)一個(gè) Bitmap,然后在之后的渲染過(guò)程中,這個(gè) Bitmap 的像素內(nèi)容被直接用于渲染到屏幕。這種繪制方式的主要計(jì)算工作在于把繪制操作轉(zhuǎn)換為像素的過(guò)程(例如由一句 Canvas.drawCircle() 來(lái)獲得一個(gè)具體的圓的像素信息),這個(gè)過(guò)程的計(jì)算是由 CPU 來(lái)完成的。大致就像這樣:

而開啟硬件加速后,Canvas的工作方式改變了:它把繪制的內(nèi)容轉(zhuǎn)為GPU的操作保存下來(lái),然后交給GPU來(lái)完成顯示工作。大致過(guò)程:

如圖,硬件加速開啟時(shí),CPU的工作是把繪制工作轉(zhuǎn)換為GPU的操作,這個(gè)工作量相對(duì)來(lái)說(shuō)還是非常小的。
怎么「加速」了
從上圖可以看出,開啟硬件加速后,繪制的計(jì)算工作有CPU交給GPU,不過(guò)這怎么就能起到加速作用,讓繪制變快了呢?
硬件加速能夠讓繪制變快,主要有三個(gè)原因:
本來(lái)CPU的工作,分?jǐn)傄徊糠纸oGPU,自然可以提高效率;
相對(duì)于CPU來(lái)說(shuō),GPU自身的設(shè)計(jì)本來(lái)就對(duì)于很多常見類型內(nèi)容的計(jì)算(例如簡(jiǎn)單的圓形、方形)具有優(yōu)勢(shì);
由于繪制流程的不同;硬件加速在界面內(nèi)容發(fā)生重繪的時(shí)候繪制流程可以得到優(yōu)化,避免一些重復(fù)操作,從而大幅提升繪制效率。
其中前兩點(diǎn)可以總結(jié)為一句話:用了GPU,繪制就是快,原因很直觀,不再多說(shuō)。
關(guān)于第三點(diǎn),它的原理大致說(shuō)一下:
前面說(shuō)到,關(guān)閉硬件加速時(shí),繪制內(nèi)容會(huì)被CPU轉(zhuǎn)為實(shí)際的像素,然后直接渲染到屏幕,具體來(lái)說(shuō),這個(gè)[實(shí)際的像素],是由bitmap承載的,在界面的某個(gè)View由于內(nèi)容發(fā)生改變而調(diào)用invalidat()方法時(shí),如果沒有開啟硬件加速,為了正確計(jì)算bitmap的像素,這個(gè)View的父View、父View的父View乃至一直向上知道最頂級(jí)的View,以及所有和它相交的View,都需要被調(diào)用invalidate()來(lái)重繪,一個(gè)View的改變使得大半個(gè)界面甚至整個(gè)界面重繪一遍,這個(gè)工作量是非常大的。
而在開啟硬件加速時(shí),前面說(shuō)過(guò),繪制的內(nèi)容會(huì)被轉(zhuǎn)換成GPU的操作保存下來(lái)(承載的形式成為displaylist,對(duì)應(yīng)的類也叫作DisplayList),再轉(zhuǎn)交給GPU。由于所有繪制的內(nèi)容都沒有變成最終的像素,所以它們之間是相互獨(dú)立的,那么在界面內(nèi)容發(fā)生改變時(shí),只需把發(fā)生了改變的View調(diào)用invalidate()方法以更新它所對(duì)應(yīng)的GPU就好,至于它的父View和兄弟View,只需要保持原樣,那么這個(gè)工作量就很小了。
正是由于上面的原因,硬件加速不僅是由于GPU的引入提高效率,而且因?yàn)?strong>繪制機(jī)制的改變,而極大的提高了界面內(nèi)容改變時(shí)的刷新效率
所以把上面三條壓縮總結(jié)一下,硬件加速更快的原因有兩條:
用了GPU,繪制更快了
繪制機(jī)制的改變,導(dǎo)致界面內(nèi)容改變時(shí)的刷新效率極大提高。
限制
如果僅僅是這樣,硬件加速只有好處沒有壞處,那大家不必要關(guān)心其他問(wèn)題,直接使用就行了,那這篇文章也沒有必要了,既然是好東西,關(guān)心那么多原理干嘛?
可事實(shí)就是,硬件加速不止有好處,也有限制:收到GPU繪制方式的限制,Canvas的有些方法在硬件加速開啟時(shí)會(huì)失效或者無(wú)法正常工作,比如:開啟硬件加速,clipPath()在 API 18及以上系統(tǒng)中才有效,具體的 API 限制和 API 版本的關(guān)系如下圖:

所以,如果你對(duì)自定義控件有自定義繪制的內(nèi)容,最好參照一下表格,確保你的繪制操作可以正確地在所有用戶手機(jī)中正常顯示,而不是只在你最新Android系統(tǒng)的 Nexus 或 Pixel 里測(cè)試一遍沒問(wèn)題就發(fā)布。那就小心被祭天了。
不過(guò)有一點(diǎn)可以放心的是,所有的原生自帶控件,都沒有用到 API 版本不兼容的繪制操作,可以放心使用。所以你只要檢查你寫的自定義繪制就好。
View Layer
在之前幾期的內(nèi)容里我提到過(guò)幾次,如果你的繪制操作不支持硬件加速,你需要手動(dòng)關(guān)閉硬件加速來(lái)繪制界面,關(guān)閉的方式是通過(guò)這行代碼:
view.setLayerType(LAYER_TYPE_SOFTWARE, null);
很多人有過(guò)疑問(wèn):什么是layer type?如果這個(gè)方法是關(guān)閉硬件加速的開關(guān),那么它的參數(shù)為什么不是一個(gè)LAYER_TYPE_SOFTWARE來(lái)關(guān)閉硬件加速以及一個(gè)LAYER_TYPE_HARDWARE來(lái)開啟硬件加速,這兩個(gè)參數(shù),而是三個(gè)參數(shù),第三個(gè)參數(shù)為L(zhǎng)AYER_TYPE_NONE,難道還能既不用軟件繪制又不用硬件繪制嗎?

事實(shí)上,view.setLayerType(LAYER_TYPE_SOFTWARE, null)這個(gè)方法的作用并不是關(guān)閉硬件加速,只是當(dāng)它的參數(shù)為L(zhǎng)AYER_TYPE_SOFTWARE的時(shí)候,可以順便把硬件加速關(guān)掉而已;并且除了這個(gè)方法外,Android并沒有提供專門的View級(jí)別的硬件加速開關(guān),所以它就順便成了一個(gè)開關(guān)硬件加速的方法。
setLayerType() 這個(gè)方法,它的作用其實(shí)就是字面的意思:設(shè)置View Layer的類型。所謂ViewLayer,又稱為離屏緩沖(off-screen Buffer),它的作用就是單獨(dú)啟用一塊地方來(lái)繪制這個(gè)View,而不是使用繪制軟件的Bitmap或者通過(guò)硬件加速的GPU,這塊地方可能是一塊單獨(dú)的bitmap,也可能是一塊OpenGL的紋理(texture,OpenGL的紋理可以簡(jiǎn)單理解為圖像的意思),具體取決于硬件加速是否開啟。采取什么來(lái)繪制View不是關(guān)鍵,關(guān)鍵在于當(dāng)設(shè)置了View Layer的時(shí)候,它的繪制會(huì)被緩存下來(lái),而且緩存的是最終的繪制結(jié)果,而不是像硬件加速那樣只是把GPU的操作保存下來(lái)再交給GPU去計(jì)算。通過(guò)這樣更進(jìn)一步的緩存方式,View的重繪效率進(jìn)一步提高了:只要繪制的內(nèi)容沒變,那么不論是CPU繪制還是GPU繪制,都不用重新計(jì)算,只要用之前緩存的結(jié)果就可以了。
多說(shuō)一句,其實(shí)這個(gè)離屏緩沖(Off-screen Buffer),更準(zhǔn)確的說(shuō)應(yīng)該叫做離屏緩存(Off-screen Cache)會(huì)更合適一點(diǎn)。原因在上面這一段里已經(jīng)說(shuō)過(guò)了,因?yàn)樗鋵?shí)是緩存而不是緩沖。(這段話僅代表個(gè)人意見)
基于這樣的原理,在進(jìn)行移動(dòng)、旋轉(zhuǎn)等(無(wú)需調(diào)用 invalidate())的屬性動(dòng)畫的時(shí)候開啟 Hardware Layer 將會(huì)極大地提升動(dòng)畫的效率,因?yàn)樵趧?dòng)畫過(guò)程中 View 本身并沒有發(fā)生改變,只是它的位置或角度改變了,而這種改變是可以由 GPU 通過(guò)簡(jiǎn)單計(jì)算就完成的,并不需要重繪整個(gè) View。所以在這種動(dòng)畫的過(guò)程中開啟 Hardware Layer,可以讓本來(lái)就依靠硬件加速而變流暢了的動(dòng)畫變得更加流暢。實(shí)現(xiàn)方式大概是這樣:
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,那么更簡(jiǎn)單:
view.animate()
.rotationY(90)
.withLayer(); // withLayer() 可以自動(dòng)完成上面這段代碼的復(fù)雜操作
不過(guò)一定要注意,只有你在對(duì) translationX translationY rotation alpha 等無(wú)需調(diào)用 invalidate() 的屬性做動(dòng)畫的時(shí)候,這種方法才適用,因?yàn)檫@種方法本身利用的就是當(dāng)界面不發(fā)生時(shí),緩存未更新所帶來(lái)的時(shí)間的節(jié)省。所以簡(jiǎn)單地說(shuō)——
這種方式不適用于基于自定義屬性繪制的動(dòng)畫。一定記得這句話。
另外,除了用于關(guān)閉硬件加速和輔助屬性動(dòng)畫這兩項(xiàng)功能外,Layer 還可以用于給 View 增加一些繪制效果,例如設(shè)置一個(gè) ColorMatrixColorFilter 來(lái)讓 View 變成黑白的:
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.setSaturation(0);
Paint paint = new Paint();
paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
view.setLayerType(LAYER_TYPE_HARDWARE, paint);
另外,由于設(shè)置了ViewLayer后,View在初次繪制時(shí)以及每次invalidate()后重繪時(shí),需要進(jìn)行兩次的繪制工作(一次繪制到Layer,一次從Layer繪制到顯示屏),所以其實(shí)它的每次繪制的效率是被降低了的,所以一定要慎重使用View Layer,在需要用到它的時(shí)候再去使用。
總結(jié)
本期內(nèi)容就是這些,就像文章開始說(shuō)的,本期知識(shí)是作為一個(gè)完整的補(bǔ)充,并么有太多重要或者高難度的東西,我也沒有準(zhǔn)備視頻或者太多的截圖或者GIF 去說(shuō)明,總結(jié)一下:
硬件加速指使用GPU來(lái)完成繪制的計(jì)算工作,代替CPU,它從工作分?jǐn)偤屠L制機(jī)制優(yōu)化兩個(gè)角度提升了繪制速度。
硬件加速可以使用setLayerType()來(lái)關(guān)閉硬件加速,但這個(gè)方法其實(shí)是用來(lái)設(shè)置View Layer的:
參數(shù)為
LAYER_TYPE_SOFTWARE時(shí),使用軟件來(lái)繪制View Layer,繪制到一個(gè)Bitmap,并順便關(guān)閉硬件加速;參數(shù)為
LAYER_TYPE_HARDWARE時(shí),使用GPU來(lái)繪制View Layer,繪制到一個(gè)OpenGL texture(如果硬件加速關(guān)閉,那么行為和LAYER_TYPE_SOFTWARE一致);參數(shù)為
LAYER_TYPE_NONE時(shí),關(guān)閉View Layer。
View Layer 可以加速無(wú) invalidate() 時(shí)的刷新效率,但對(duì)于需要調(diào)用 invalidate() 的刷新無(wú)法加速。
View Layer 繪制所消耗的實(shí)際時(shí)間是比不使用 View Layer 時(shí)要高的,所以要慎重使用。