為什么會(huì)突然學(xué)習(xí)硬件加速呢?因?yàn)樵诶L圖的時(shí)候,并不是所有的函數(shù)都支持硬件加速,我就有一個(gè)疑問,硬件加速不是好東西來的嗎?干嘛不支持,百思不得解,于是遍尋資料,最后發(fā)現(xiàn)還是研究官方的API文檔更加的靠譜。
硬件加速
安卓的硬件加速是自安卓3.0(API 11)之后才有的,安卓2D繪制管線支持硬件加速,但因?yàn)閱⒂糜布铀傩枰黾淤Y源,所以將會(huì)消耗更多的RAM。
在API14之后(包括14),硬件加速默認(rèn)是開啟的,側(cè)面也說明,從11-13默認(rèn)是關(guān)閉的,不過我想這個(gè)現(xiàn)在我們已經(jīng)不考慮了,畢竟現(xiàn)在開發(fā)都不考慮低版本的手機(jī)了。啟用硬件加速的最簡(jiǎn)單方式就是整個(gè)application都啟用,簡(jiǎn)稱一鍋端,如果只是使用標(biāo)準(zhǔn)的view或者drawable,那么應(yīng)該不會(huì)產(chǎn)生不好的繪制效果(這也是android官方API文檔的原話,所以請(qǐng)放心使用)。但是,因?yàn)橛布铀俨⒉恢С炙械?D繪制操作,如果你的應(yīng)用有自定義的view或者調(diào)用某些繪制函數(shù),啟用硬件加速可能會(huì)對(duì)你的應(yīng)用有一些影響,比如看不見一些元素或者呈現(xiàn)錯(cuò)誤的像素,所以為了解決這個(gè)不兼容的問題(在API14之后繪圖時(shí)使用不支持硬件加速的方法),google大佬們的建議是,通過真機(jī)測(cè)試上啟用硬件加速,遇到問題的話對(duì)以下四個(gè)級(jí)別進(jìn)行控制,我們可以選擇啟用或者不啟用硬件加速:
Application級(jí)別:
在安卓manifest文件,添加下列屬性到<application>標(biāo)簽就可以給整個(gè)應(yīng)用添加硬件加速:

Activity級(jí)別:
如果不想全局都啟用硬件加速,那么個(gè)別Activity可以不指定硬件加速,在Activity級(jí)別啟動(dòng)或者不啟用硬件加速,你可以<activity>標(biāo)簽指定android:hardwareAccelerated屬性

window級(jí)別:
如果需要更細(xì)粒度的控制,可以用以下代碼為一個(gè)window啟用硬件加速

PS:目前還不允許在window級(jí)別關(guān)閉硬件加速的
view級(jí)別:
你可以在運(yùn)行時(shí)對(duì)指定的View關(guān)閉硬件加速

或者在xml中的控件屬性中,使用android:layerType=”software”來關(guān)閉硬件加速:比如

PS:目前還不允許運(yùn)行時(shí)啟用硬件加速,View layers還是一些函數(shù)是不支持硬件加速的。
判斷一個(gè)View是否開啟硬件加速
有時(shí)候我們想知道應(yīng)用是否開啟了硬件加速,尤其是對(duì)于自定義View的情景,如果你的應(yīng)用有很多的自定義繪制并且不是所有的操作都支持新的繪制管線時(shí)(硬件加速)。那么這兩個(gè)不同的方法就變得特別的有用,可以檢測(cè)應(yīng)用是否是硬件加速的:
View.isHardwareAcclerated() 返回true如果View是依附到啟動(dòng)了硬件加速的window
Canvas.isHardwareAccelerated 返回true如果canvas是硬件加速的
但是,強(qiáng)烈建議使用Canvas.isHardwareAcclerated代替View.isHardwareAcclerated,因?yàn)閂iew的這個(gè)方法真的非常不靠譜,即使這個(gè)View是依附在一個(gè)硬件加速的Window上,但是仍然可以使用一個(gè)不啟用硬件加速的Canvas進(jìn)行繪制,比如當(dāng)我們把View繪制到bitmap上的時(shí)候。而且很多時(shí)候,我們?nèi)绻麑?duì)window不熟悉的話,view有沒有跟Window綁定在一起都不知道。
Android繪制模式
在硬件加速出現(xiàn)之前,原來的圖像處理,渲染工作是由軟件實(shí)現(xiàn)的。
當(dāng)我們啟用硬件加速的時(shí)候,android Framework會(huì)使用一個(gè)新的繪制模式,這個(gè)模式會(huì)使用display list來渲染畫面。為了搞清楚display list以及它是怎么運(yùn)行的,在學(xué)習(xí)心得繪制模式之前,我們應(yīng)該學(xué)習(xí)一下在硬件加速出現(xiàn)之前,android使用的基于軟件的繪制模式。
基于軟件的繪制模式
在軟件繪制模式,view是按照下面兩個(gè)步驟進(jìn)行繪制的:
1、無效化View層次結(jié)構(gòu)
2、繪制View的層次結(jié)構(gòu)
當(dāng)應(yīng)用需要更新它的一部分UI,它會(huì)調(diào)用view的invalidate方法,無效化消息就會(huì)通過各種途徑傳遞到View的層次結(jié)構(gòu),然后計(jì)算屏幕中需要重繪的區(qū)域(臟區(qū)域),android系統(tǒng)還會(huì)對(duì)View層次結(jié)構(gòu)中臟區(qū)域相交的所有view進(jìn)行繪制,對(duì)于這種繪制模式,有兩個(gè)不好的地方:
第一、這個(gè)模式在每一次繪制都需要執(zhí)行大量的代碼,比如,如果你的應(yīng)用對(duì)一個(gè)button調(diào)用invalidate,而這個(gè)button坐標(biāo)在其他view的上方,那么android系統(tǒng)就會(huì)重繪這些view,即使他們沒有發(fā)生改變,僅僅因?yàn)樗鼈兲幱诤蚥utton相交的區(qū)域
第二、繪制模式會(huì)有一些隱藏bug,當(dāng)android系統(tǒng)重繪那些相交的views的時(shí)候,即使你沒有調(diào)用invalidate,相交區(qū)域內(nèi)的一個(gè)被改變過的view可能也會(huì)進(jìn)行重繪,這個(gè)時(shí)候,你通過其他的view的繪制來給這個(gè)view進(jìn)行重繪,當(dāng)你不經(jīng)意間修改你的代碼,這個(gè)時(shí)候可能你已經(jīng)忘了這一段代碼有這個(gè)隱藏的bug,修改代碼后你發(fā)現(xiàn)顯示的效果有問題,本該進(jìn)行重繪的區(qū)域沒有重繪,你就會(huì)懷疑是自己的代碼邏輯出現(xiàn)問題了,所以應(yīng)該盡可能在修改view的數(shù)據(jù)或狀態(tài)的時(shí)候,對(duì)每一個(gè)你修改過的自定義View,主動(dòng)調(diào)用他們的invalidate方法。
PS:當(dāng)view的屬性發(fā)生改變的時(shí)候,例如TextView上的background color或者text,這個(gè)時(shí)候android view會(huì)自動(dòng)調(diào)用invalidate,進(jìn)行重繪
硬件加速繪制模式
android系統(tǒng)依然使用invalidate和draw函數(shù)來請(qǐng)求屏幕刷新渲染界面,但實(shí)際上繪制的時(shí)候是有區(qū)別的,不同于立即執(zhí)行繪制命令,android系統(tǒng)會(huì)先把它們記錄在display list上,這個(gè)display lists包含view的層次結(jié)構(gòu)的繪制代碼。其他的優(yōu)化是android系統(tǒng)只需要記錄和更新display lists,通過調(diào)用invalidate函數(shù)來標(biāo)記那些臟view,那些沒有被標(biāo)記為invalidate的view可以簡(jiǎn)單的進(jìn)行重繪通過事先記錄在display list上的記錄。新的繪制模式包含三個(gè)步驟:
1、無效化View的層次結(jié)構(gòu)
2、記錄和更新顯示列表
3、繪制顯示列表
使用這個(gè)模式,你不能再依賴臟區(qū)域內(nèi)相交的view來繪制其他的view,為了確保android系統(tǒng)記錄這個(gè)view的display list,你必須調(diào)用invalidate,不調(diào)用的話,就會(huì)導(dǎo)致一個(gè)view看起來跟之前是一樣的,即使你已經(jīng)改變它了。所以這就一次性解決了兩個(gè)問題,對(duì)于View層次結(jié)構(gòu)中不想重繪的View,只要不調(diào)用那個(gè)View的invalidae即可。
使用display list對(duì)動(dòng)畫效果也有好處,因?yàn)樵O(shè)置指定的屬性,例如alpha或者rotation,不需要調(diào)用目標(biāo)view的invalidate,因?yàn)樗鼤?huì)自動(dòng)完成,這個(gè)優(yōu)化也應(yīng)用到view的display list。例如,假設(shè)有一個(gè)LinearLayout在Button上面有一個(gè)ListView,那么對(duì)于LinearLayout的display list就會(huì)像這樣的:
DrawDisplayList(ListView)
DrawDisplayList(Button)
假設(shè)現(xiàn)在你通過調(diào)用setAlpha(0.5)來修改ListView的透明度,那么display list就變成這樣了:
SaveLayerAlpha(0.5)
DrawDisplayList(ListView)
Restore
DrawDisplayList(Button)
關(guān)于ListView的復(fù)雜的繪制代碼并沒有被執(zhí)行,系統(tǒng)只是更新了LinearLayout的display list,如果應(yīng)用沒有啟用硬件加速,那么listview以及它的父容器LinearLayout的繪制代碼都會(huì)再次執(zhí)行。
不支持的繪制操作
當(dāng)啟用了硬件加速后,2D渲染管線支持大部分的canvas的常用繪制操作和一些不常用的操作,分別列舉一下,其實(shí)我們也只有在自定義View的時(shí)候才擔(dān)心canvas調(diào)用的函數(shù)會(huì)不會(huì)不支持硬件加速,所以我們只需要真正用到的時(shí)候查閱一下即可。




Canvas Scaling
The hardware accelerated 2D rendering pipeline was built first to support unscaled drawing, with some drawing operations degrading quality significantly at higher scale values. These operations are implemented as textures drawn at scale 1.0, transformed by the GPU. In API level <17, using these operations will result in scaling artifacts increasing with scale.
The following table shows when implementation was changed to correctly handle large scales:

Note: 'Simple' shapes aredrawRect(),drawCircle(),drawOval(),drawRoundRect(), anddrawArc()(with useCenter=false) commands issued with a Paint that doesn't have a PathEffect, and doesn't contain non-default joins (viasetStrokeJoin()/setStrokeMiter()). Other instances of those draw commands fall under 'Complex,' in the above chart.
If your application is affected by any of these missing features or limitations, you can turn off hardware acceleration for just the affected portion of your application by callingsetLayerType(View.LAYER_TYPE_SOFTWARE, null). This way, you can still take advantage of hardware acceleration everywhere else. SeeControlling Hardware Accelerationfor more information on how to enable and disable hardware acceleration at different levels in your application.
中間這段實(shí)在是看的夠嗆,希望有大??炊四軌蚋嬖V一下小弟。
View Layers
在android的所有版本中,views可以在離屏渲染,或者使用view的繪制緩存,或者通過使用Canvas.saveLayer()。離屏緩存,或者圖層,有幾種用途。你可以使用它們獲得更好的顯示效果,當(dāng)對(duì)一些復(fù)雜的views使用動(dòng)畫效果或者一些合成效果,比如,你可以實(shí)現(xiàn)漸變效果使用Canvas.saveLayer()來臨時(shí)渲染一個(gè)view到一個(gè)layer,然后把不透明的元素合成到屏幕上。
android3.0開始,使用layers的時(shí)候有更多的控制方法了,通過View.setLayerType()函數(shù),這個(gè)API有兩個(gè)參數(shù):layer的類型,和一個(gè)可選的描述layer如何合成的Paint對(duì)象,你可以使用Paint參數(shù)來給layer添加Color Filter,指定混合模式,或者不透明度,可以選擇以下三種layer type:
LAYER_TYPE_NONE:view只會(huì)普通地進(jìn)行渲染,并且不會(huì)使用離屏緩存回退,這是默認(rèn)的行為。
LAYER_TYPE_HARDWARE:如果應(yīng)用啟動(dòng)了硬件加速,那么這個(gè)view就會(huì)使用硬件里面的texture渲染,如果應(yīng)用不能夠硬件加速,那么它的效果就跟LAYER_TYPE_SOFTWARE一樣。
LAYER_TYPE_SOFTWARE:這個(gè)view將會(huì)使用軟件來渲染到一個(gè)bitmap里。
texture:紋理,在3D游戲開發(fā)里面叫做貼圖,存放圖片到texture里面,運(yùn)行時(shí)會(huì)讀取,而在這里當(dāng)我們需要重繪的時(shí)候也是一樣的原理,從硬件中讀取紋理然后進(jìn)行繪制。
這幾種類型的layer取決于你的用途:
Performance:使用一個(gè)硬件圖層類型來渲染view到硬件中的texture,自從view被渲染到一個(gè)圖層,它的繪制代碼就不再被執(zhí)行直到view調(diào)用invalidate方法,一些動(dòng)畫,例如alpha動(dòng)畫,使用GPU來實(shí)現(xiàn)就可以高效地直接作用于圖層上。
Visual effects:使用一個(gè)硬件或者軟件圖層類型,還有一個(gè)Paint對(duì)view進(jìn)行一些特殊的處理,比如,你可以使用黑色和白色通過ColorMatrixColorFilter來繪制一個(gè)view。
Compatibility:使用一個(gè)軟件圖層類型來強(qiáng)迫一些view使用軟件來渲染,如果一個(gè)view是硬件加速的(比如,如果你整個(gè)應(yīng)用都是硬件加速的),那么就會(huì)出現(xiàn)渲染問題,最簡(jiǎn)單的解決方式就是限制硬件渲染管道(關(guān)閉View的硬件加速)。
View layer和animations
當(dāng)你的應(yīng)用是硬件加速的,硬件圖層類型可以傳達(dá)更快和更加順滑的動(dòng)畫,當(dāng)你在處理的是一個(gè)復(fù)雜的又很多繪制操作的view的時(shí)候,運(yùn)行一個(gè)動(dòng)畫不總是60幀每秒的??梢酝ㄟ^使用硬件層來渲染view到一個(gè)硬件的texture中來優(yōu)化這個(gè)問題,硬件texture可以用來對(duì)view進(jìn)行動(dòng)畫,排除開始動(dòng)畫的時(shí)候需要重繪自己的View,view不會(huì)重新重繪除非你改變它的屬性,然后調(diào)用invalidate()。如果你運(yùn)行一個(gè)動(dòng)畫在你的應(yīng)用上,但是得不到一個(gè)你想要的順滑結(jié)果,考慮啟用硬件加速在你的動(dòng)畫view上。當(dāng)view從硬件圖層回退的時(shí)候,它的一些屬性會(huì)通過圖層合成到屏幕上的方式進(jìn)行處理,設(shè)置這些屬性將會(huì)更加高效,因?yàn)樗恍枰獀iew重繪或者無效化,下面的一些屬性可以通過這種方式來合成到屏幕上,調(diào)用這些屬性的setter方法就可以在目標(biāo)view上不需要重繪。
alpha:改變圖層的不透明度
x,y,translationX,translationY:改變圖層的位置
scaleX,scaleY:改變圖層的大小
rotation,rotaionX,rotationY:改變圖層在三維空間的排列方向
pivotX,pivotY:改變圖層的轉(zhuǎn)移點(diǎn)
這些屬性都是同樣的用法當(dāng)動(dòng)畫一個(gè)view使用ObjectAnimator,如果你想要訪問這些屬性,調(diào)用這些屬性的getter和setter,比如,為了修改alpha屬性,調(diào)用setAlpha,接下來的代碼片段展示了最有效的方式來圍繞Y軸旋轉(zhuǎn)一個(gè)view在3D:
view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator.ofFloat(view, "rotationY", 180).start();
因?yàn)橛布酉膙ideo存儲(chǔ),所以強(qiáng)烈建議啟用它們只有在動(dòng)畫時(shí)長(zhǎng)并且關(guān)閉它們當(dāng)動(dòng)畫完成的時(shí)候,你可以完成這個(gè)通過使用動(dòng)畫監(jiān)聽器,這一個(gè)比較細(xì)節(jié),但能夠?qū)iew進(jìn)行優(yōu)化,畢竟手機(jī)內(nèi)存這么少
View.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotationY", 180);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
view.setLayerType(View.LAYER_TYPE_NONE, null);
}
});
animator.start();
最后是一些API提到的小tips和技巧
切換到硬件加速,界面固然是更加流暢了,但是我們開發(fā)應(yīng)用的時(shí)候要想讓GPU的效率更加的高,應(yīng)該注意以下幾點(diǎn):
減少應(yīng)用中view的數(shù)量
要繪制的view越多,那么必然就越慢,使用軟件渲染管道也是一樣的,減少view的數(shù)量是優(yōu)化UI的最簡(jiǎn)單的途徑。
避免透支
不要在頂部畫太多互相混合的圖層,移除那些完全被其他不透明View覆蓋的View,如果你需要在頂部畫幾個(gè)互相混合的圖層,考慮把他們放到一個(gè)單一的圖層里,一個(gè)很好的經(jīng)驗(yàn)法則與當(dāng)前硬件是不繪制超過2.5倍的每幀屏幕上的像素?cái)?shù)(在一個(gè)位圖的像素的透明像素?。?jiǎn)而言之就是不要嵌套太多層。
不要老是創(chuàng)建Paint和Path對(duì)象
一個(gè)普遍的錯(cuò)誤是,每次調(diào)用draw方法的時(shí)候總是new一個(gè)Paint對(duì)象,或者new一個(gè)Path對(duì)象,這樣就強(qiáng)迫垃圾回收器頻繁地運(yùn)行,而且也失去了硬件管道中的緩存和優(yōu)化。
不要頻繁修改外形
比如復(fù)雜的外形,路徑和圓,它們都是使用texture mask進(jìn)行渲染的,每次修改路徑,硬件管道就創(chuàng)建一個(gè)新的mask,這樣開銷是很大的。
不要頻繁修改bitmap
每一次修改一個(gè)bitmap中的內(nèi)容,當(dāng)你下次繪制它的時(shí)候,它就會(huì)再次上傳到GPU中的texture。
小心使用alpha
當(dāng)你使用setAlpha,或者AlphaAnimation,或者ObjectAnimator來改變一個(gè)View的透明度時(shí),它渲染在離屏緩存中需要兩倍填充率,當(dāng)需要在在一個(gè)非常大的view上修改alpha,就要考慮設(shè)置view的layer type為L(zhǎng)AYER_TYPE_HARDWARE
最后總結(jié)一下硬件加速我們應(yīng)該知道:
1、硬件加速是從API 11引入,API 14之后才默認(rèn)開啟。對(duì)于標(biāo)準(zhǔn)的繪制操作和控件都是支持的,但是對(duì)于自定義View的時(shí)候或者一些特殊的繪制函數(shù)就需要考慮是否需要關(guān)閉硬件加速。
2、我們面對(duì)不支持硬件加速的情況,就需要限制硬件加速,這個(gè)兼容性的問題是因?yàn)橛布铀偈前裋iew的繪制函數(shù)轉(zhuǎn)化為使用OpenGL的函數(shù)來進(jìn)完成實(shí)際的繪制的,那么必然會(huì)存在OpenGL中不支持原始回執(zhí)函數(shù)的情況,對(duì)于這些繪制函數(shù),就會(huì)失效。
3、硬件加速的消耗問題,因?yàn)槭鞘褂肙penGL,所以就需要把系統(tǒng)中OpenGL加載到內(nèi)存中,所以O(shè)penGL API調(diào)用就會(huì)占用8MB,而實(shí)際上會(huì)占用更多內(nèi)存,并且使用了硬件必然增加耗電量了。
4、另一方面,硬件加速的優(yōu)勢(shì)還有display list這個(gè)設(shè)計(jì),使用這個(gè)的話,我們就不需要每次重繪都執(zhí)行大量的代碼,因?yàn)閷?duì)臟區(qū)域的,基于軟件的繪制模式會(huì)重繪臟區(qū)域內(nèi)的所有控件,而display只會(huì)更新列表,然后繪制列表內(nèi)的控件。
哎,還需要好好學(xué)習(xí)英語才行?。〗酉聛頊?zhǔn)備啃一下xfermode。