從Android3.0(API Level 11)開始,Android 2D渲染管道能夠更好的支援硬體加速。硬體加速執行的所有的繪圖操作都是使用GPU在View對象的畫布上來進行的。因為啟用硬體加速會增加資源的需求,因此這樣的應用會佔用更多的記憶體。
硬體加速在target api大於等於14的情況下,是預設開啟的,但是我們也可以顯示的開啟硬體加速。如果應用程式只使用標準的View和Drawable,那麼開啟全域硬體加速不會導致任何不良的繪製影響。然而,由於硬體加速並不支援所有的2D圖形繪製操作,因此對於那些使用定製的View和繪製調用來說,開啟全域硬體加速,會造成影響。對於這個問題,通常是對那些不可見的元素進行了異常或錯誤的像素渲染。為了避免這種問題,Android提供了多個層級的硬體加速操作(開啟或者關閉),具體可見控制硬體加速。
如果應用程式執行了定製化的繪圖,並且開啟了硬體加速,那麼就要在帶有硬體加速的真機上測試,以便發現問題。 不支援的繪製操作描述了硬體加速的問題,以及如何解決它們。
控制硬體加速(Controlling Hardware Acceleration)android提供了以下四個個層級來控制硬體加速:
ApplicationActivityWindowViewApplication層級
在應用的Android資訊清單檔中,把下列屬性添加到元素中,能夠開啟整個應用程式的硬體加速:
Activity層級
如果應用程式不能正確的使用被開啟的全域硬體加速,那麼也可以在Activity層級上進行控制。在元素中使用android:hardwareAccelerated屬性,能夠啟用或禁止Activity層級的硬體加速。以下樣本啟用全域的硬體加速,但卻禁止了一個Activity的硬體加速:
Window層級
如果需要更精細的控制,就可以使用下列代碼來針對給定的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 Layer除了禁止硬體加速以外,還有其他的功能,更多的相關資訊請看本文的View layer。
判斷一個View對象是否被硬體加速
有些時候,知道當前的View對象(尤其是那些定製的View對象)是否被硬體加速對應用程式來說是非常有用的。如果應用程式做了很多定製的繪圖操作,並且不是所有的操作都會被新的渲染管道所支援,那麼這種判斷就特別有用。
有兩種不同的方法來檢查應用程式是否被硬體加速了:
View.isHardwareAccelerated():返回值為true,View對象跟一個被硬體加速的視窗綁定;反之,未加速Canvas.isHardwareAccelerated():返回值為true,Canvas對象被硬體加速;反之,未加速
Android的繪圖模式(Android Drawing Models)
當硬體加速啟用時,Android架構會採用一個新的繪圖模式,這種模式利用顯示列表把應用程式渲染在螢幕上。充分理解顯示列表,以及它們是如何影響應用程式的,對於理解Android是如何繪製沒有硬體加速的View對象也是有益的。下面分別介紹基於軟體和硬體加速的繪圖模式。
基於軟體的繪圖模式(Software-based drawing model)
在軟體的繪圖模式中,View對象是通過以下兩個步驟來繪製的:
讓View階層失效繪製View階層
無論何時,當應用程式需要更新它的UI部分時,它都會調用View#invalidate()(或者invalidate方法的相關變體)使UI內容改變。失效的訊息請求會在View對象階層上進行一路傳遞,以便計算出需要重繪的螢幕地區(髒區)。然後,Android系統就會在View階層中繪製所有跟髒區相交的地區。不幸的是,這種繪圖模式有兩個缺點:
第一個問題,在每個繪圖傳遞中,這種繪圖模式都需要很多的代碼執行。例如,如果應用程式調用了一個按鈕的invalidate()方法,並且該按鈕位於另一個View對象的上方,那麼即使該View對象沒有變化,那麼Android系統也要重新繪製這個View對象。第二個問題,這個種繪圖模式能夠隱藏應用程式中的bug。由於Android系統會重新繪製其它跟髒區相交的View對象,所以即使沒有調用View對象上的invalidate()方法,那麼View對象內容的改變也可能會導致其它View被重繪。當發生這種情況時,就要依賴另一個被失效的View對象來擷取適當的行為。這種行為能夠改變每次你對應用程式的修改。因為這個原因,所以為了影響繪圖代碼,在修改定製View對象的資料和狀態時,應該始終調用該定製View對象的invalidate()方法。
注意:在View對象的屬性發生變化時,如背景色或TextView對象中的文本等,Android會自動的調用該View對象的invalidate()方法。
硬體加速繪圖模式(Hardware accelerated drawing model)
這種模式下,Android系統依然會使用invalidate()和draw()來請求螢幕更新並且渲染View,但是實際的繪圖操作與基於軟體的繪圖模式是不同的。它會立即執行繪圖命令,Android系統把這些命令記錄在內部的顯示列表中,這個列表包含了View對象階層的繪圖代碼的輸出。另一個最佳化是:Android系統只需要針對由invalidate()方法調用所標記的View對象的髒區進行記錄和更新顯示列表。沒有失效的View對象能夠通過重新發布先前被記錄的顯示列表來進行簡單的重繪工作。這種新的繪圖模式包含三個階段:
讓View的階層失效記錄和更新顯示列表繪製顯示列表
使用這種模式,不能夠依賴相交的髒區的View#draw()執行。要確保Android系統記錄一個View對象的顯示列表,就必須調用invalidate()方法,如果忘記調用該方法,那麼在變化發生後,View對象看上去會跟變化之前相同。
使用顯示列表對提升動畫的效能也是有好處的,因為設定特殊屬性,諸如透明度、旋轉等屬性時,不需要請求目標View對象失效(系統會自動做這件事)。這種最佳化還適用於帶有顯示列表的View對象(應用程式被硬體加速時的任意View對象)。例如,假設有一個包含了一個Button對象的ListView對象的LinearLayout布局,那麼LinearLayout布局的顯示列表如下:
DrawDisplayList(ListView)DrawDisplayList(Button)
假設現在要改變ListView對象的透明度,那麼在調用ListView對象的setAlpha(0.5f)方法時,顯示列表就包含了以下處理:
SaveLayerAlpha(0.5);DrawDisplayList(ListView);Restore;DrawDisplayList(Button).
這裡沒有執行複雜的ListView對象的繪圖代碼。相反,系統只是比較簡單的更新了LinearLayout對象的顯示列表。在一個沒有啟用硬體加速的應用程式中,該列表(ListView)和它的父物件都會再次執行繪圖代碼。
不支援硬體加速的操作
在硬體加速的時候,2D渲染管道支援大多數的通常用於Canvas的繪圖操作,以及一些很少使用的操作。被用於渲染應用程式的所有的繪圖操作都有發送給Android系統,預設的Widget和布局,以及一些常用的可視效果,如反射和瓷磚的紋理效果都是被支援的。
下面給出一個連結——不同API層級上不支援的硬體加速的操作。
View Layers
在Android的所有版本中,通過使用View對象的繪圖緩衝,或使用Canvas.saveLayer()方法,View都具有渲染到離屏(off-screen)緩衝區的能力。離屏緩衝區或層有多種用途,在呈現複雜的動畫或使用組合效果時,能夠獲得更好的效能。例如,使用Canvas.saveLayer()可以實現淡入淡出的效果,先暫時把一個View對象渲染在一個層中,然後把它和不透明因子合成到螢幕上。
從Android3.0(API Level 11)開始,在如何和什麼時候使用View.setLayerType()問題上,Android提供了更多的控制。這個API攜帶兩個參數:一個是層的類型,另一個是可選的Paint對象,這個對象描述層應該如何被合成的。使用這個Paint對象能夠進行顏色過濾、特殊的混合模式、或者層的透明度。View對象能夠使用以下三種層類型:
LAYER_TYPE_NONE:View對象用普通的方式來渲染,並且不是由螢幕外緩衝來返回的。這種類型是預設的行為。LAYER_TYPE_HARDWARE:如果應用程式是硬體加速的,那麼該View對象被渲染在硬體的一個硬體紋理(texture)中。如果沒有開啟硬體加速,那麼這種層類型的行為與LAYER_TYPE_SOFTWARE相同。LAYER_TYPE_SOFTWARE:View對象會被呈現在軟體的一個位元影像中。
根據以下目的,選擇層類型:
效能:使用硬體層類型,把View渲染到一個硬體紋理中。一旦該View對象被渲染到一個層中,那麼它的繪圖代碼直到調用該View對象的invalidate()方法時才會被執行。對於某些動畫,如alpha動畫,就能夠直接使用該層,這麼做對於GPU來說是非常高效的。視覺效果:使用層類型(硬體或軟體)和一個Paint對象,能夠把一些特殊的視覺處理應用給一個View對象。例如,使用ColorMatrixColorFilter對象繪製一個黑白相間的View對象。相容性:使用軟體層類型會強制把一個View對象渲染在軟體中。如果被硬體加速的View對象(例如,如果整個應用程式都被硬體加速)發生渲染問題,那麼使用軟體層類型來解決硬體渲染管道的限制是一個簡單的方法。
View layers and animations
當應用程式被硬體加速的時候,硬體層能夠傳遞更快、更平滑的動畫。當播放具有複雜的繪圖操作的動畫時,以每秒60幀的速度播放不總是可能的。但是,可以通過使用硬體層把View對象渲染在硬體紋理中,緩解這種情況。硬體紋理能夠被用於動畫視圖,這樣在該View對象呈現動畫時,就可以消除View對象所需要的重繪操作。除非該View對象的屬性發生變化時(invalidate()方法被調用),它才會被重繪。如果在應用程式運行一個動畫,並且沒有獲得想要的平滑結果,就要考慮在動畫View上啟用硬體層。
當一個View從硬體層被返回時,通過層方法處理的某些屬性會被合成到螢幕上。因為它們不需要讓View對象失效和重繪,所以設定這些屬性是非常高效的。下面列出了影響層被合成的方式。調用這些屬性設定器,會導致失效處理的最佳化,並且不會對目標View對象進行重繪:
alpha:改變層(layer)的透明度;x,y,translation,translation:改變層的位置;scaleX,scaleY:改變層的尺寸;rotation,rotation,rotationY:改變3D空間中層的方向;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.
Tips and Tricks
選擇硬體加速的2D圖形能夠有效改善效能,但是為了有效使用GPU,應該按照以下建議設計應用程式:
減少應用程式中View對象的數量
系統繪製越多的View對象,就會越慢。這種情況也適用於軟體渲染管道。減少View對象的有效方法之一就是最佳化UI。
避免過度繪圖
在彼此的頂部不要繪製太多的層。移除那些完全被別的不透明View遮蓋的View。對於當前硬體的一個好的原則是,每幀的像素數不要大於螢幕上像素數的2.5倍(以位元影像的透明點陣數來計算)。
不要在繪圖方法中建立渲染對象
一個常見的錯誤是每次調用渲染方法時建立一個新的Paint對象或Path對象。這樣就會強制頻繁的運行記憶體回收,導致繞過硬體管道中的緩衝和最佳化。
不要經常的編輯形狀
對於複雜的形狀,如路徑和圓,是使用紋理掩碼來呈現的。每次建立或修改路徑,硬體通道都要建立一個新的紋理遮罩,這樣會消耗大量的資源。
不要經常的編輯位元影像
每次改變位元影像內容,它都會被再次上傳到GPU的紋理,以供下次繪製。
要小心的使用alpha相關的方法
當使用setAlpha()、 AlphaAnimation或ObjectAnimator,讓一個View對象半透明時,需要雙倍填充率來渲染到離屏緩衝。當在一個大的View對象上應用透明效果時,要考慮把View對象的層類型設定為LAYER_TYPE_HARDWARE。