????????所謂硬件加速赏寇,指的是把某些計算工作交給專門的硬件來做饼灿,而不是和普通的計算工作一樣交給CPU 來處理悔橄。這樣不僅減輕了 CPU 的壓力绒疗,而且由于有了「專人」的處理侵歇,這份計算工作的速度也被加快了。這就是「硬件加速」吓蘑。
? ??????而對于 Android 來說惕虑,硬件加速有它專屬的意思:在 Android 里,硬件加速專指把View 中繪制的計算工作交給 GPU 來處理磨镶。進一步地再明確一下溃蔫,這個「繪制的計算工作」指的就是把繪制方法中的那些Canvas.drawXXX() 變成實際的像素這件事。
? ? 原理
? ??????在硬件加速關閉的時候琳猫, Canvas 繪制的工作方式是:把要繪制的內容寫進一個Bitmap 伟叛,然后在之后的渲染過程中,這個 Bitmap 的像素內容被直接用于渲染到屏幕脐嫂。這種繪制方式的主要計算工作在于把繪制操作轉換為像素的過程(例如由一句Canvas.drawCircle() 來獲得一個具體的圓的像素信息)痪伦,這個過程的計算是由CPU 來完成的侄榴。大致就像這樣:
????????而在硬件加速開啟時,Canvas 的工作方式改變了:它只是把繪制的內容轉換為GPU 的操作保存了下來网沾,然后就把它交給 GPU,最終由 GPU 來完成實際的顯示工作蕊爵。大致是這樣:
怎么就「加速」了?
從上面的圖中可以看出会放,硬件加速開啟后饲齐,繪制的計算工作由 CPU 轉交給了GPU。不過這怎么就能起到「加速」作用咧最,讓繪制變快了呢捂人?
硬件加速能夠讓繪制變快,主要有三個原因:
1. 本來由 CPU 自己來做的事矢沿,分攤給了 GPU 一部分滥搭,自然可以提高效率;
2. 相對于 CPU 來說捣鲸,GPU 自身的設計本來就對于很多常見類型內容的計算(例如簡單的圓形瑟匆、簡單的方形)具有優(yōu)勢;
3. 由于繪制流程的不同栽惶,硬件加速在界面內容發(fā)生重繪的時候繪制流程可以得到優(yōu)化愁溜,避免了一些重復操作,從而大幅提升繪制效率外厂。
其中前兩點可以總結為一句:用了 GPU冕象,繪制就是快。原因很直觀酣衷,不再多說交惯。
關于第三點,它的原理我大致說一下:
????????前面說到穿仪,在硬件加速關閉時席爽,繪制內容會被 CPU 轉換成實際的像素,然后直接渲 染到屏幕啊片。具體來說只锻,這個「實際的像素」,它是由 Bitmap 來承載的紫谷。在界面中的某個 View 由于內容發(fā)生改變而調用 invalidate() 方法時齐饮,如果沒有開啟硬件加速捐寥,那么為了正確計算 Bitmap 的像素,這個 View 的父 View祖驱、父 View 的父View 乃至一直向上直到最頂級 View握恳,以及所有和它相交的兄弟 View ,都需要被調用 invalidate() 來重繪捺僻。一個 View 的改變使得大半個界面甚至整個界面都重繪一遍乡洼,這個工作量是非常大的。
?? ? ? ?而在硬件加速開啟時匕坯,前面說過束昵,繪制的內容會被轉換成 GPU 的操作保存下來(承載的形式稱為 display list,對應的類也叫做 DisplayList )葛峻,再轉交給GPU锹雏。由于所有的繪制內容都沒有變成最終的像素,所以它們之間是相互獨立的术奖,那么在界面內容發(fā)生改變的時候礁遵,只要把發(fā)生了改變的 View 調用 invalidate()方法以更新它所對應的 GPU 操作就好,至于它的父 View 和兄弟 View腰耙,只需要保持原樣榛丢。那么這個工作量就很小了。
正是由于上面的原因挺庞,硬件加速不僅是由于 GPU 的引入而提高了繪制效率晰赞,還由于繪制機制的改變,而極大地提高了界面內容改變時的刷新效率选侨。
限制
如果僅僅是這樣掖鱼,硬件加速只有好處沒有缺陷,那大家都不必關心硬件加速了援制,這?篇文章也不會出現(xiàn):既然是好東西就用唄戏挡,關心那么多原理干嗎?
可事實就是晨仑,硬件加速不只是好處褐墅,也有它的限制:受到 GPU 繪制方式的限制, Canvas 的有些方法在硬件加速開啟式會失效或無法正常工作洪己。比如妥凳,在硬件加速開啟時, clipPath() 在 API 18 及以上的系統(tǒng)中才有效答捕。具體的 API 限制和API 版本的關系如下圖:
????????所以逝钥,如果你的自定義控件中有自定義繪制的內容,最好參照一下這份表格拱镐,確保你的繪制操作可以正確地在所有用戶的手機里能夠正常顯示艘款,而不是只在你的運行了最新版本 Android 系統(tǒng)的 Nexus 或 Pixel 里測試一遍沒問題就發(fā)布了持际,小心被祭天。不過有一點可以放心的是哗咆,所有的原生自帶控件蜘欲,都沒有用到 API 版本不兼容的繪制操作,可以放心使用岳枷。所以你只要檢查你寫的自定義繪制就好芒填。
View Layer
如果你的繪制操作不支持硬件加速,你需要手動關閉硬件加速來繪制界面空繁,關閉的方式是通過這行代碼:
view.setLayerType(LAYER_TYPE_SOFTWARE, null);?
有不少人都有過疑問:什么是 layer type?如果這個方法是硬件加速的開關朱庆,那么它 的參數(shù)為什么不是一個 LAYER_TYPE_SOFTWARE 來關閉硬件加速以及一個LAYER_TYPE_HARDWARE 來打開硬件加速這么兩個參數(shù)盛泡,而是三個參數(shù),在SOFTWARE 和 HARDWARE 之外還有一個 LAYER_TYPE_NONE 娱颊?難道還能既不用軟件繪制傲诵,也不用硬件繪制嗎?
事實上箱硕,這個方法的本來作用并不是用來開關硬件加速的拴竹,只是當它的參數(shù)為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 繪制,它們都不用重新計算赐写,而只要只用之前緩存的繪制結果就可以了鸟蜡。
基于這樣的原理,在進行移動挺邀、旋轉等(無需調用 invalidate() )的屬性動畫的 時候開啟 Hardware Layer 將會極大地提升動畫的效率揉忘,因為在動畫過程中 View 本身并沒有發(fā)生改變,只是它的位置或角度改變了端铛,而這種改變是可以由 GPU 通過簡單計算就完成的泣矛,并不需要重繪整個 View。所以在這種動畫的過程中開啟Hardware Layer禾蚕,可以讓本來就依靠硬件加速而變流暢了的動畫變得更加流暢您朽。實現(xiàn)方式大概是這樣:
或者如果是使用 ViewPropertyAnimator ,那么更簡單:
不過一定要注意换淆,只有你在對 translationX translationY rotation alpha 等無需調用 invalidate() 的屬性做動畫的時候哗总,這種方法才適用,因為這種方法本身利用的就是當界面不發(fā)生時倍试,緩存未更新所帶來的時間的節(jié)省讯屈。所以簡單地說——
這種方式不適用于基于自定義屬性繪制的動畫。一定記得這句話县习。一定記得這句話涮母。一定記得這句話。
總結
硬件加速指的是使用 GPU 來完成繪制的計算工作准颓,代替 CPU哈蝇。它從工作分攤和繪 制機制優(yōu)化這兩個角度提升了繪制的速度。
硬件加速可以使用 setLayerType() 來關閉硬件加速攘已,但這個方法其實是用來設置View Layer 的:
1. 參數(shù)為 LAYER_TYPE_SOFTWARE 時炮赦,使用軟件來繪制 View Layer,繪制到一個Bitmap样勃,并順便關閉硬件加速吠勘;
2. 參數(shù)為 LAYER_TYPE_HARDWARE 時,使用 GPU 來繪制 View Layer峡眶,繪制到一個OpenGL texture(如果硬件加速關閉剧防,那么行為和 VIEW_TYPE_SOFTWARE 一致);
3. 參數(shù)為 LAYER_TYPE_NONE 時辫樱,關閉 View Layer峭拘。View Layer 可以加速無 invalidate() 時的刷新效率,但對于需要調用invalidate() 的刷新無法加速。
View Layer 繪制所消耗的實際時間是比 不使用 View Layer 時要高的鸡挠,所以要慎重使用辉饱。