關(guān)于硬件加速的那么點兒東西

為什么會突然學(xué)習(xí)硬件加速呢?因為在繪圖的時候,并不是所有的函數(shù)都支持硬件加速,我就有一個疑問,硬件加速不是好東西來的嗎?干嘛不支持,百思不得解,于是遍尋資料,最后發(fā)現(xiàn)還是研究官方的API文檔更加的靠譜。

硬件加速

安卓的硬件加速是自安卓3.0(API 11)之后才有的,安卓2D繪制管線支持硬件加速,但因為啟用硬件加速需要增加資源,所以將會消耗更多的RAM。

在API14之后(包括14),硬件加速默認(rèn)是開啟的,側(cè)面也說明,從11-13默認(rèn)是關(guān)閉的,不過我想這個現(xiàn)在我們已經(jīng)不考慮了,畢竟現(xiàn)在開發(fā)都不考慮低版本的手機了。啟用硬件加速的最簡單方式就是整個application都啟用,簡稱一鍋端,如果只是使用標(biāo)準(zhǔn)的view或者drawable,那么應(yīng)該不會產(chǎn)生不好的繪制效果(這也是android官方API文檔的原話,所以請放心使用)。但是,因為硬件加速并不支持所有的2D繪制操作,如果你的應(yīng)用有自定義的view或者調(diào)用某些繪制函數(shù),啟用硬件加速可能會對你的應(yīng)用有一些影響,比如看不見一些元素或者呈現(xiàn)錯誤的像素,所以為了解決這個不兼容的問題(在API14之后繪圖時使用不支持硬件加速的方法),google大佬們的建議是,通過真機測試上啟用硬件加速,遇到問題的話對以下四個級別進行控制,我們可以選擇啟用或者不啟用硬件加速:

Application級別:

在安卓manifest文件,添加下列屬性到<application>標(biāo)簽就可以給整個應(yīng)用添加硬件加速:

Activity級別:

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

window級別:

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

PS:目前還不允許在window級別關(guān)閉硬件加速的

view級別:

你可以在運行時對指定的View關(guān)閉硬件加速

第二個參數(shù)是Paint

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

PS:目前還不允許運行時啟用硬件加速,View layers還是一些函數(shù)是不支持硬件加速的。

判斷一個View是否開啟硬件加速

有時候我們想知道應(yīng)用是否開啟了硬件加速,尤其是對于自定義View的情景,如果你的應(yīng)用有很多的自定義繪制并且不是所有的操作都支持新的繪制管線時(硬件加速)。那么這兩個不同的方法就變得特別的有用,可以檢測應(yīng)用是否是硬件加速的:

View.isHardwareAcclerated() 返回true如果View是依附到啟動了硬件加速的window

Canvas.isHardwareAccelerated 返回true如果canvas是硬件加速的

但是,強烈建議使用Canvas.isHardwareAcclerated代替View.isHardwareAcclerated,因為View的這個方法真的非常不靠譜,即使這個View是依附在一個硬件加速的Window上,但是仍然可以使用一個不啟用硬件加速的Canvas進行繪制,比如當(dāng)我們把View繪制到bitmap上的時候。而且很多時候,我們?nèi)绻麑indow不熟悉的話,view有沒有跟Window綁定在一起都不知道。

Android繪制模式

在硬件加速出現(xiàn)之前,原來的圖像處理,渲染工作是由軟件實現(xiàn)的。

當(dāng)我們啟用硬件加速的時候,android Framework會使用一個新的繪制模式,這個模式會使用display list來渲染畫面。為了搞清楚display list以及它是怎么運行的,在學(xué)習(xí)心得繪制模式之前,我們應(yīng)該學(xué)習(xí)一下在硬件加速出現(xiàn)之前,android使用的基于軟件的繪制模式。

基于軟件的繪制模式

在軟件繪制模式,view是按照下面兩個步驟進行繪制的:

1、無效化View層次結(jié)構(gòu)

2、繪制View的層次結(jié)構(gòu)

當(dāng)應(yīng)用需要更新它的一部分UI,它會調(diào)用view的invalidate方法,無效化消息就會通過各種途徑傳遞到View的層次結(jié)構(gòu),然后計算屏幕中需要重繪的區(qū)域(臟區(qū)域),android系統(tǒng)還會對View層次結(jié)構(gòu)中臟區(qū)域相交的所有view進行繪制,對于這種繪制模式,有兩個不好的地方:

第一、這個模式在每一次繪制都需要執(zhí)行大量的代碼,比如,如果你的應(yīng)用對一個button調(diào)用invalidate,而這個button坐標(biāo)在其他view的上方,那么android系統(tǒng)就會重繪這些view,即使他們沒有發(fā)生改變,僅僅因為它們處于和button相交的區(qū)域

第二、繪制模式會有一些隱藏bug,當(dāng)android系統(tǒng)重繪那些相交的views的時候,即使你沒有調(diào)用invalidate,相交區(qū)域內(nèi)的一個被改變過的view可能也會進行重繪,這個時候,你通過其他的view的繪制來給這個view進行重繪,當(dāng)你不經(jīng)意間修改你的代碼,這個時候可能你已經(jīng)忘了這一段代碼有這個隱藏的bug,修改代碼后你發(fā)現(xiàn)顯示的效果有問題,本該進行重繪的區(qū)域沒有重繪,你就會懷疑是自己的代碼邏輯出現(xiàn)問題了,所以應(yīng)該盡可能在修改view的數(shù)據(jù)或狀態(tài)的時候,對每一個你修改過的自定義View,主動調(diào)用他們的invalidate方法。

PS:當(dāng)view的屬性發(fā)生改變的時候,例如TextView上的background color或者text,這個時候android view會自動調(diào)用invalidate,進行重繪

硬件加速繪制模式

android系統(tǒng)依然使用invalidate和draw函數(shù)來請求屏幕刷新渲染界面,但實際上繪制的時候是有區(qū)別的,不同于立即執(zhí)行繪制命令,android系統(tǒng)會先把它們記錄在display list上,這個display lists包含view的層次結(jié)構(gòu)的繪制代碼。其他的優(yōu)化是android系統(tǒng)只需要記錄和更新display lists,通過調(diào)用invalidate函數(shù)來標(biāo)記那些臟view,那些沒有被標(biāo)記為invalidate的view可以簡單的進行重繪通過事先記錄在display list上的記錄。新的繪制模式包含三個步驟:

1、無效化View的層次結(jié)構(gòu)

2、記錄和更新顯示列表

3、繪制顯示列表

使用這個模式,你不能再依賴臟區(qū)域內(nèi)相交的view來繪制其他的view,為了確保android系統(tǒng)記錄這個view的display list,你必須調(diào)用invalidate,不調(diào)用的話,就會導(dǎo)致一個view看起來跟之前是一樣的,即使你已經(jīng)改變它了。所以這就一次性解決了兩個問題,對于View層次結(jié)構(gòu)中不想重繪的View,只要不調(diào)用那個View的invalidae即可。

使用display list對動畫效果也有好處,因為設(shè)置指定的屬性,例如alpha或者rotation,不需要調(diào)用目標(biāo)view的invalidate,因為它會自動完成,這個優(yōu)化也應(yīng)用到view的display list。例如,假設(shè)有一個LinearLayout在Button上面有一個ListView,那么對于LinearLayout的display list就會像這樣的:

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的繪制代碼都會再次執(zhí)行。

不支持的繪制操作

當(dāng)啟用了硬件加速后,2D渲染管線支持大部分的canvas的常用繪制操作和一些不常用的操作,分別列舉一下,其實我們也只有在自定義View的時候才擔(dān)心canvas調(diào)用的函數(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.

中間這段實在是看的夠嗆,希望有大牛看懂了能夠告訴一下小弟。

View Layers

在android的所有版本中,views可以在離屏渲染,或者使用view的繪制緩存,或者通過使用Canvas.saveLayer()。離屏緩存,或者圖層,有幾種用途。你可以使用它們獲得更好的顯示效果,當(dāng)對一些復(fù)雜的views使用動畫效果或者一些合成效果,比如,你可以實現(xiàn)漸變效果使用Canvas.saveLayer()來臨時渲染一個view到一個layer,然后把不透明的元素合成到屏幕上。

android3.0開始,使用layers的時候有更多的控制方法了,通過View.setLayerType()函數(shù),這個API有兩個參數(shù):layer的類型,和一個可選的描述layer如何合成的Paint對象,你可以使用Paint參數(shù)來給layer添加Color Filter,指定混合模式,或者不透明度,可以選擇以下三種layer type:

LAYER_TYPE_NONE:view只會普通地進行渲染,并且不會使用離屏緩存回退,這是默認(rèn)的行為。

LAYER_TYPE_HARDWARE:如果應(yīng)用啟動了硬件加速,那么這個view就會使用硬件里面的texture渲染,如果應(yīng)用不能夠硬件加速,那么它的效果就跟LAYER_TYPE_SOFTWARE一樣。

LAYER_TYPE_SOFTWARE:這個view將會使用軟件來渲染到一個bitmap里。

texture:紋理,在3D游戲開發(fā)里面叫做貼圖,存放圖片到texture里面,運行時會讀取,而在這里當(dāng)我們需要重繪的時候也是一樣的原理,從硬件中讀取紋理然后進行繪制。

這幾種類型的layer取決于你的用途:

Performance:使用一個硬件圖層類型來渲染view到硬件中的texture,自從view被渲染到一個圖層,它的繪制代碼就不再被執(zhí)行直到view調(diào)用invalidate方法,一些動畫,例如alpha動畫,使用GPU來實現(xiàn)就可以高效地直接作用于圖層上。

Visual effects:使用一個硬件或者軟件圖層類型,還有一個Paint對view進行一些特殊的處理,比如,你可以使用黑色和白色通過ColorMatrixColorFilter來繪制一個view。

Compatibility:使用一個軟件圖層類型來強迫一些view使用軟件來渲染,如果一個view是硬件加速的(比如,如果你整個應(yīng)用都是硬件加速的),那么就會出現(xiàn)渲染問題,最簡單的解決方式就是限制硬件渲染管道(關(guān)閉View的硬件加速)。

View layer和animations

當(dāng)你的應(yīng)用是硬件加速的,硬件圖層類型可以傳達更快和更加順滑的動畫,當(dāng)你在處理的是一個復(fù)雜的又很多繪制操作的view的時候,運行一個動畫不總是60幀每秒的??梢酝ㄟ^使用硬件層來渲染view到一個硬件的texture中來優(yōu)化這個問題,硬件texture可以用來對view進行動畫,排除開始動畫的時候需要重繪自己的View,view不會重新重繪除非你改變它的屬性,然后調(diào)用invalidate()。如果你運行一個動畫在你的應(yīng)用上,但是得不到一個你想要的順滑結(jié)果,考慮啟用硬件加速在你的動畫view上。當(dāng)view從硬件圖層回退的時候,它的一些屬性會通過圖層合成到屏幕上的方式進行處理,設(shè)置這些屬性將會更加高效,因為它不需要view重繪或者無效化,下面的一些屬性可以通過這種方式來合成到屏幕上,調(diào)用這些屬性的setter方法就可以在目標(biāo)view上不需要重繪。

alpha:改變圖層的不透明度

x,y,translationX,translationY:改變圖層的位置

scaleX,scaleY:改變圖層的大小

rotation,rotaionX,rotationY:改變圖層在三維空間的排列方向

pivotX,pivotY:改變圖層的轉(zhuǎn)移點

這些屬性都是同樣的用法當(dāng)動畫一個view使用ObjectAnimator,如果你想要訪問這些屬性,調(diào)用這些屬性的getter和setter,比如,為了修改alpha屬性,調(diào)用setAlpha,接下來的代碼片段展示了最有效的方式來圍繞Y軸旋轉(zhuǎn)一個view在3D:

view.setLayerType(View.LAYER_TYPE_HARDWARE, null);

ObjectAnimator.ofFloat(view, "rotationY", 180).start();

因為硬件層消耗video存儲,所以強烈建議啟用它們只有在動畫時長并且關(guān)閉它們當(dāng)動畫完成的時候,你可以完成這個通過使用動畫監(jiān)聽器,這一個比較細(xì)節(jié),但能夠?qū)iew進行優(yōu)化,畢竟手機內(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)用的時候要想讓GPU的效率更加的高,應(yīng)該注意以下幾點:

減少應(yīng)用中view的數(shù)量

要繪制的view越多,那么必然就越慢,使用軟件渲染管道也是一樣的,減少view的數(shù)量是優(yōu)化UI的最簡單的途徑。

避免透支

不要在頂部畫太多互相混合的圖層,移除那些完全被其他不透明View覆蓋的View,如果你需要在頂部畫幾個互相混合的圖層,考慮把他們放到一個單一的圖層里,一個很好的經(jīng)驗法則與當(dāng)前硬件是不繪制超過2.5倍的每幀屏幕上的像素數(shù)(在一個位圖的像素的透明像素!),簡而言之就是不要嵌套太多層。

不要老是創(chuàng)建Paint和Path對象

一個普遍的錯誤是,每次調(diào)用draw方法的時候總是new一個Paint對象,或者new一個Path對象,這樣就強迫垃圾回收器頻繁地運行,而且也失去了硬件管道中的緩存和優(yōu)化。

不要頻繁修改外形

比如復(fù)雜的外形,路徑和圓,它們都是使用texture mask進行渲染的,每次修改路徑,硬件管道就創(chuàng)建一個新的mask,這樣開銷是很大的。

不要頻繁修改bitmap

每一次修改一個bitmap中的內(nèi)容,當(dāng)你下次繪制它的時候,它就會再次上傳到GPU中的texture。

小心使用alpha

當(dāng)你使用setAlpha,或者AlphaAnimation,或者ObjectAnimator來改變一個View的透明度時,它渲染在離屏緩存中需要兩倍填充率,當(dāng)需要在在一個非常大的view上修改alpha,就要考慮設(shè)置view的layer type為LAYER_TYPE_HARDWARE

最后總結(jié)一下硬件加速我們應(yīng)該知道:

1、硬件加速是從API 11引入,API 14之后才默認(rèn)開啟。對于標(biāo)準(zhǔn)的繪制操作和控件都是支持的,但是對于自定義View的時候或者一些特殊的繪制函數(shù)就需要考慮是否需要關(guān)閉硬件加速。

2、我們面對不支持硬件加速的情況,就需要限制硬件加速,這個兼容性的問題是因為硬件加速是把View的繪制函數(shù)轉(zhuǎn)化為使用OpenGL的函數(shù)來進完成實際的繪制的,那么必然會存在OpenGL中不支持原始回執(zhí)函數(shù)的情況,對于這些繪制函數(shù),就會失效。

3、硬件加速的消耗問題,因為是使用OpenGL,所以就需要把系統(tǒng)中OpenGL加載到內(nèi)存中,所以O(shè)penGL API調(diào)用就會占用8MB,而實際上會占用更多內(nèi)存,并且使用了硬件必然增加耗電量了。

4、另一方面,硬件加速的優(yōu)勢還有display list這個設(shè)計,使用這個的話,我們就不需要每次重繪都執(zhí)行大量的代碼,因為對臟區(qū)域的,基于軟件的繪制模式會重繪臟區(qū)域內(nèi)的所有控件,而display只會更新列表,然后繪制列表內(nèi)的控件。

哎,還需要好好學(xué)習(xí)英語才行?。〗酉聛頊?zhǔn)備啃一下xfermode。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.1Controlling Hardware Acceleration 從Android3.0(API Leve...
    android之子閱讀 3,980評論 0 11
  • 在上篇文章 屬性動畫(一) 中已經(jīng)對屬性動畫有了基本的介紹,本篇文章將對屬性動畫中稍微高級點的內(nèi)容進行介紹,主要介...
    lijiankun24閱讀 458評論 0 0
  • 硬件加速的原理 將view的繪制函數(shù)轉(zhuǎn)化成OpenGL中的函數(shù)來完成繪制。 軟件繪制與硬件繪制的區(qū)別 軟件繪制在軟...
    wanderingGuy閱讀 453評論 0 1
  • 在iOS中隨處都可以看到絢麗的動畫效果,實現(xiàn)這些動畫的過程并不復(fù)雜,今天將帶大家一窺ios動畫全貌。在這里你可以看...
    每天刷兩次牙閱讀 8,688評論 6 30
  • 身處這個信息爆炸的時代,任何一個產(chǎn)品的宣傳已經(jīng)不能在用傳統(tǒng)的電視廣告,積累客戶等等。每一個銷售都希望自己的產(chǎn)...
    a小劉閱讀 462評論 0 0

友情鏈接更多精彩內(nèi)容