Android筆記-hardwareAccelerated 硬件加速詳解


從Android3.0(API Level 11)開始,Android 2D渲染管道能夠更好的支持硬件加速。硬件加速執行的所有的繪圖操作都是使用GPU在View對象的畫布上來進行的。因爲啓用硬件加速會增加資源的需求,因此這樣的應用會佔用更多的內存。 

啓用硬件加速的最容易的方法是給整個應用程序都打開全局硬件加速功能。

如果應用程序只使用標準的View和Drawable,那麼打開全局硬件加速不會導致任何的不良的繪製效果。但是,因爲硬件加速並不支持所有的2D圖形繪製操作,所以對於那些使用定製的View和繪製調用的應用程序來說,打開全局硬件加速,可以會影響繪製效果。問題通常會出現在對那些不可見的元素進行了異常或錯誤的像素渲染。爲了避免這種問題,Android提供以下級別,以便可選擇性的啓用或禁止硬件加速

 1. Application

 2. Activity 

3. Window 

4. View 

如果應用程序執行了定製化的繪圖,就要在實際的帶有硬件加速的硬件設備上測試,以便發現問題。  
控制硬件加速 
能夠用以下級別來控制硬件加速 Application級別 
在應用的Android清單文件中,把下列屬性添加到<application>元素中,來開啓整個應用程序的硬件加速。 

<application android:hardwareAccelerated="true" ...>

Activity級別 

如果應用程序不能夠正確的使用被打開的全局硬件加速,那麼也可以對Activity分別進行控制。在<activity>元素中使用android:hardwareAccelerated屬性,能夠啓用或禁止Activity級別的硬件加速。以下示例啓用全局的硬件加速,但卻禁止了一個Activity的硬件加速: <application android:hardwareAccelerated="true">     <activity ... /> 

    <activity android:hardwareAccelerated="false" /> </application>

Window級別 

如果需要更細粒度的控制,就可以使用下列代碼來針對給定的窗口來啓用硬件加速:

 getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,   WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED); 

注意:當前不能在Window級別禁止硬件加速。

View級別 

能夠使用下列代碼在運行時針對一個獨立的View對象來禁止硬件加速: 

myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); 

注意:當前不能在View級別開啓硬件加速。View層除了禁止硬件加速以外,還有其他的功能,更多的相關信息請看本文的“View層”。 判斷一個View對象是否被硬件加速 
有些時候,尤其是對於那些定製的View對象,應用程序知道當前的View對象是否被硬件加速是十分有益的。如果應用程序做了很多定製的繪圖操作,並且不是所有的操作都會被新

的渲染管道所支持,那麼這種判斷就特別有用。 


有兩種不同的方法來檢查應用程序是否被硬件加速了: 
1. 如果一個View對象跟一個被硬件加速的窗口綁定,那麼View.isHardwareAccelerated()  方法就會返回true; 

2. 如果一個Canvas對象被硬件加速了,那麼Canvas.isHardwareAccelerated()方法就會返回  true。 

如果必須要在繪圖代碼中做這種檢查,那麼在可能的情況下,要使用Canvas.isHardwareAccelerated()方法來代替View.isHardwareAccelerated()方法。當一個View對象跟一個被硬件加速的窗口綁定的時候,它依然能夠使用使用一個非硬件加速的Canvas對象。例如,把一個View對象繪製到緩存中的一個位圖時就會發生這種情況。 


HardAccelerated的作用:

當硬件加速被啓用時,Android框架會採用一個新的繪圖模式,這種模式利用顯示列表把應用程序呈現在屏幕上,這樣的速度更快。要充分理解顯示列表,以及它們是如何影響應用程序的,對於理解Android是如何繪製沒有硬件加速的View對象是有益的。下面分別介紹基於軟件的和硬件加速的繪圖模式。 基於軟件的繪圖模式 
在軟件的繪圖模式中,View對象是通過以下兩個步驟來繪製的: 1. 讓View層次結構失效; 2. 繪製View層次結構。 
無論何時,應用程序需要更新它的UI部分時,都回調用發生內容改變的View對象的invalidate()方法。無效的消息請求會在View對象層次結構中傳遞,以便計算出需要重繪的屏幕區域(髒區)。然後,Android系統會在View層次結構中繪製所有的跟髒區相交的區域。不幸的是,這種方法有兩個缺點: 
1. 這種模式在每個繪圖傳遞中需要很多的代碼執行。例如,如果應用程序調用了一個按鈕
的invalidate()方法,並且該按鈕位於另一個View對象的上方,那麼即使該View對象沒有變化,那麼Android系統也要重新繪製它。 
2. 第二個問題是這個種繪圖模式能夠隱藏應用程序中的bug。因爲當View對象跟髒區相
交時,Android系統就會重新繪製它,所以即使沒有調用View對象上的invalidate()方法,那麼View對象內容的改變也可能會導致它被重繪。當發生這種情況時,就要依賴另一個被失效的View對象來獲取正確的行爲。這種行爲能夠改變每次你對應用程序的修改。因爲這個原因,在修改影響View對象的繪圖代碼的數據和狀態是,應該始終調用該定製View對象的invalidate()方法。 
注意:在View對象的屬性發生變化時,如背景色或TextView對象中的文本等,Android系統會自動的調用該View對象的invalidate()方法。 硬件加速繪圖模式 
這種模式下,Android系統依然會使用invalidate()方法和draw()方法來請求屏幕更新和展現View對象,但是實際的繪圖處理是不同的,它會立即執行繪圖命令,Android系統把這些命令記錄在內部的顯示列表中,列表中包含了View對象層次結構的繪圖代碼的輸出。另一種優化是:Android系統只需要針對由invalidate()方法調用所標記的View對象的髒區進行記錄和更新顯示列表。沒有失效的View對象能夠通過重新發布先前被記錄的顯示列表來進行簡單的重繪工作。這種繪圖模式包含三個階段: 1. 讓View的層次結構失效; 2. 記錄和更新顯示列表; 3. 繪製顯示列表。 












使用這種模式,不能夠依賴相交的髒區View的draw()方法來執行繪圖工作。要確保Android系統記錄一個View對象的顯示列表,就必須調用invalidate()方法,如果忘記調用該方法,那麼在變化發生後,View對象看上去會跟變化之前相同,這是一個比較容易發現的Bug。 使用顯示列表對提升動畫的性能也是有好處的,因爲設置諸如透明度、旋轉等屬性時,不需要讓目標View對象失效(系統會自動做這件事)。這種優化還適用於帶有顯示列表的View對象(應用程序被硬件加速時的任意View對象)。例如,假設有一個包含了一個Button對象的ListView對象的LinearLayout佈局,那麼LinearLayout佈局的顯示列表如下: 1. DrawDisplayList(ListView); 2. DrawDisplayList(Button)。 
假設現在要改變ListView對象的不透明度,那麼在調用ListView對象的setAlpha(0.5f)方法時,顯示列表就包含了以下處理: 1. SaveLayerAlpha(0.5); 
2. DrawDisplayList(ListView); 3. Restore; 
4. DrawDisplayList(Button). 
這裏沒有執行復雜的ListView對象的繪圖代碼。相反,系統只是比較簡單的更新了LinearLayout對象的顯示列表。在一個沒有啓用硬件加速的應用程序中,該列表(ListView)和它的父對象都會再次執行繪圖代碼。 不被硬件加速所支持的繪圖操作 
在硬件加速的時候,2D渲染管道支持大多數的通常用於Canvas的繪圖操作,以及一些很少使用的操作。被用於渲染應用程序的所有的繪圖操作都有發送給Android系統,默認的Widget和佈局,以及一些常用的可視效果,如反射和瓷磚的紋理效果都是被支持的。以下列出了已知的不支持硬件加速的操作: 1. Canvas 
chipPath() chipRegion() drawPicture() drawPosText() drawTextOnPath() drawVertives() 2. Paint 
setLinearText() setMaskFilter() setRasterizer() 
另外,還有一些操作行爲會因啓用了硬件加速而不同: 1. Canvas 
clipRect():硬件加速會忽略XOR、Difference和ReverseDifference三種剪輯模式,3D變換不適用於剪輯矩形。 
drawBitmapMesh():硬件加速會忽略顏色數組。 drawLines():硬件加速不支持抗鋸齒處理。 
setDrawFilter():硬件加速能夠被設置,但是會被忽略。 2. Paint 
setDither():硬件加速會忽略其設置。 
setFilterBitmap():位圖過濾是始終打開的。 












setShadowLayer():該項設置只對文本有效。 3. ComposeShader 
ComposeShader對象只能包含不同類型的着色器(例如,BitmapShader和LinearGradient,但是不能夠包含兩個BitmapShader對象的實例)。 
ComposeShader對象不能夠包含一個ComposeShader對象。 
如果應用程序受到這些錯誤的功能或限制的影響,那麼能夠通過調用setLayerType(View.LAYER_TYPE_SOFTWARE, null)方法針對應用程序受到影響的部分來關閉硬件加速。這種方法,依然還能夠在其他的地方利用硬件加速。  
View層 
在Android的所有版本中,通過使用View對象的繪圖緩衝,或使用Canvas.saveLayer()方法,View對象都具有在屏幕外緩衝區呈現的能力。屏幕外緩衝區或層由多種用途。在呈現複雜的動畫或使用組合效果時,它們能夠獲得更好的性能。例如,使用Canvas.saveLayer()方法暫時把一個View對象呈現在一個層中,然後使用不透明因子把該View對象合成到屏幕上,從而實現淡入淡出的效果。  
從Android3.0(API Level 11)開始,用View.setLayerType()方法使用層的方式和時機會更多的控制。這個API需要兩個參數:一個是層的類型,另一個是可選的,用於描述層應該如何被合成的Paint對象。能夠使用這個Paint對象來進行顏色過濾、特殊的混合模式、或者層的透明度。View對象能夠使用以下三種層類型: 
LAYER_TYPE_NONE:View對象用普通的方式來呈現,並且不是由屏幕外緩存來返回的。這種類型是默認的行爲; 
LAYER_TYPE_HARDWARE:如果應用程序是硬件加速的,那麼該View對象被呈現在硬件的一個硬件紋理中。如果沒有被硬件加速,那麼這種層類型的行爲與LAYER_TYPE_SOFTWARE相同。 
LAYER_TYPE_SOFTWARE:View對象會被呈現在軟件的一個位圖中。 使用哪種層的類型,依賴以下目標: 
1. 性能:使用硬件層類型,把View呈現到一個硬件紋理中。一旦該View對象被呈現到一
個層中,那麼它的繪圖代碼直到調用該View對象的invalidate()方法時纔會被執行。對於某些動畫,如alpha動畫,就能夠直接使用該層,這麼做對於GPU來說是非常高效的。 2. 視覺效果:使用硬件或軟件層類型和一個Paint對象,能夠把一些特殊的視覺處理應用
給一個View對象。例如,使用ColorMatrixColorFilter對象繪製一個黑白相間的View對象。 
3. 兼容性:使用軟件層類型會強制把一個View對象呈現在軟件中。如果View對象被硬件
加速(例如,如果整個應用程序都被硬件加速)發生呈現問題,那麼使用軟件層類型來解決硬件呈現管道的限制是一個容易的方法。 View層和動畫 
當應用程序被硬件加速的時候,硬件層能夠傳遞更快、更平滑的動畫。當播放具有複雜的繪圖操作的動畫時,以每秒60幀的速度播放不總是可能。這樣能夠通過使用硬件層把View對象呈現在硬件紋理中,可以緩解這種情況。硬件紋理能夠被用於動畫視圖,這樣在該View對象呈現動畫時,就可以消除View對象所需要的重繪操作。直到View對象的屬性發生變化時(invalidate()方法被調用),該View對象纔會被重繪。如果在應用程序運行一個動畫,並且沒有獲得想要的平滑結果,就要考慮在動畫View上啓用硬件層。 
當一個View被硬件層返回時,通過層方法處理的某些屬性會被合成到屏幕上。因爲它們不












需要讓View對象失效和重繪,所以設置這些屬性是非常高效的。下面列出了影響層被合成的方式。調用這些屬性設置器,會導致失效處理的優化,並且不會對目標View對象進行重繪: 
1. alpha:改變層的透明度; 
2. x,y,translation,translation:改變層的位置; 3. scaleX,scaleY:改變成的尺寸; 
4. rotation,rotation,rotationY:改變3D空間中層的方向; 5. pivotX,pivotY:改變層的變換起源。 
這些屬性是在用ObjectAnimator對象給View對象設置動畫時所使用的名稱。如果想要訪問這些屬性,就要調用相應的set或get方法。例如,要修改alpha屬性,就要調用setAlpha()方法。下面的代碼展示了在3D空間中圍繞Y軸旋轉View對象的最有效的方法: view.setLayerType(View.LAYER_TYPE_HARDWARE, null); ObjectAnimator.ofFloat(view, "rotationY", 180).start(); 因爲硬件層會消耗顯示內存,因此強烈推薦只在動畫播放期間啓用硬件層,並且在動畫播放結束後就禁用該硬件層。能夠使用動畫監聽器來完成這種操作: 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(); 
更多的屬性動畫的信息,請看Property Animation(http://developer.android.com/guide/topics/graphics/prop-animation.html)  
提示和技巧 
切換到硬件加速的2D圖形能夠有效改善性能,但是依然要通過以下推薦的方式來設計應用程序,以便有效的使用GPU: 
1. 減少應用程序中View對象的數量 
系統繪製越多的View對象,就會越慢。這種情況也適用於軟件呈現管道。減少View對象的有效方法之一就是優化UI。 2. 避免過度繪圖 
在彼此的頂部不要繪製太多的層。因爲刪除View時要同時刪除遮蓋在該View對象之上所有其他的不透明的View對象。如果需要繪製幾個圖層,要儘量在上面採用合成模式,考慮把它們合併成一個層。一個好的規則是:每幀的像素數不要大於屏幕上像素數的2.5倍(以位圖的透明點陣數來計算)。 3. 不要在繪圖方法中創建呈現對象 
一個常見的錯誤時每次調用呈現方法時創建一個新的Paint對象或Path對象。這樣就會強制頻繁的運行垃圾回收,並且會繞過硬件管道中的緩存和優化。 4. 不要經常的編輯形狀 
對於複雜的形狀,如路徑和圓,是使用紋理掩碼來呈現的。每次創建或修改路徑,硬件通道都要創建一個新的掩碼,這樣會消耗大量的資源。 











發佈了44 篇原創文章 · 獲贊 14 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章