硬體加速

從 Android 3.0 (API 級別 11) 開始,Android 2D 算繪管道支援硬體加速功能,也就是說,對 View 畫布執行的所有繪圖作業都使用 GPU。由於啟用硬體加速需要較多資源,應用程式會耗用更多 RAM。

如果您的目標 API 級別 >=14,系統預設會啟用硬體加速,不過這個功能也能自行指定啟用。如果您的應用程式僅使用標準檢視和 Drawable,全域開啟此功能應不會造成不良繪圖效果。不過,由於並非所有 2D 繪圖作業都支援硬體加速,開啟這項設定可能會影響部分自訂檢視畫面或繪製呼叫。通常會出現的問題包括隱形元素、例外狀況或錯誤轉譯像素。為解決上述現象,Android 會讓您在多個層級選擇啟用或停用硬體加速。請參閱控制硬體加速

如果您的應用程式會自訂繪圖,請在開啟硬體加速的實際硬體裝置上測試應用程式,以找出硬體問題。「繪圖作業支援」一節說明硬體加速的已知問題及解決方法。

另請參閱 OpenGL with Framework APIRenderscript

控制硬體加速

您可以在下列層級中控制硬體加速:

  • 應用程式
  • 活動
  • 視窗
  • 查看

應用程式層級

在 Android 資訊清單檔案中,將下列屬性新增至 <application> 標記,以便為整個應用程式啟用硬體加速:

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

活動層級

如果全域開啟硬體加速時,您的應用程式無法正常運作,也可以個別控制各項活動。如要在活動層級啟用或停用硬體加速,您可以針對 <activity> 元素使用 android:hardwareAccelerated 屬性。以下示範為整個應用程式啟用硬體加速,但針對一項活動停用此功能:

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

視窗層級

如果您需要更精細的控制,可以使用下列程式碼啟用特定視窗的硬體加速功能:

Kotlin

window.setFlags(
        WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
        WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
)

Java

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

注意事項:目前無法在視窗層級停用硬體加速。

資料檢視層級

您可以使用下列程式碼,在執行階段停用個別檢視畫面的硬體加速:

Kotlin

myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null)

Java

myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

注意事項:您目前無法在檢視畫面層級啟用硬體加速。除了停用硬體加速外,檢視畫面層級還提供其他功能。如要進一步瞭解其用途,請參閱檢視畫面層級

判斷檢視畫面是否採用硬體加速

有時應用程式需要確認是否正在使用硬體加速,尤其是針對自訂檢視這類項目。如果您的應用程式會執行大量自訂繪圖,且新的算繪管線無法正常支援所有作業,此方法就能派上用場。

有兩種方式可以檢查應用程式是否啟用硬體加速:

如果必須在繪圖程式碼中進行這項檢查,請盡可能使用 Canvas.isHardwareAccelerated(),而非 View.isHardwareAccelerated()。當檢視畫面附加至硬體加速視窗時,仍可使用非硬體加速畫布進行繪製。舉例來說,為執行快取而將檢視區塊繪製為點陣圖,就屬於這種情況。

Android 繪圖模型

啟用硬體加速後,Android 架構會使用新的繪圖模型,透過「顯示清單」在螢幕上算繪應用程式。如要全面瞭解顯示清單及其對應用程式的影響,建議您也一併研究 Android 如何在沒有硬體加速的情況下繪製檢視畫面。以下各節說明以軟體為基礎的硬體加速繪圖模型。

基於軟體的繪圖模型

在軟體繪圖模型中,系統會使用下列兩個步驟繪製檢視畫面:

  1. 撤銷層級
  2. 繪製層級

每當應用程式需要更新 UI 的一部分時,如果任何檢視畫面的內容有變更,就會叫用 invalidate() (或其中一個變化版本)。無效訊息會向上傳遞至檢視區塊階層,以計算需要重新繪製的畫面區域 (無效區域)。接著,Android 系統會在與無效區域交叉的任何檢視畫面上繪製階層。很抱歉,此繪圖形式有以下兩個缺點:

  • 首先,此模型需要在每次繪圖傳遞時執行大量程式碼。舉例來說,如果應用程式在某個按鈕上呼叫 invalidate(),且該按鈕位於其他檢視畫面上方,Android 系統就會重新繪製檢視畫面,即使尚未變更。
  • 第二個問題是繪圖模型可隱藏應用程式中的錯誤。由於 Android 系統會與無效區域相交時重新繪製檢視畫面,因此即使未對 invalidate() 呼叫其內容,您變更內容的檢視畫面仍可能會重新繪製。在這種情況下,您必須依賴其他無效檢視畫面才能取得適當的行為。每次修改應用程式時,行為都可能改變。因此,每當您修改資料或狀態,且會影響到檢視畫面繪圖程式碼時,請務必對自訂檢視區塊呼叫 invalidate()

注意:Android 檢視區塊會在自身屬性 (例如背景顏色或 TextView 中的文字) 改變時自動呼叫 invalidate()

硬體加速繪圖模型

Android 系統會使用 invalidate()draw() 要求畫面更新及算繪檢視畫面,但實際繪圖的方式不同。Android 系統不會立即執行繪圖指令,而是在顯示清單中 (當中含有檢視區塊階層繪圖程式碼的輸出內容) 記錄這些指令。另一項最佳化是,Android 系統只需要記錄及更新顯示清單,以用於 invalidate() 呼叫標記為有瑕疵的檢視畫面。系統會重新發出先前記錄的顯示清單,以重新繪製未失效的檢視畫面。新的繪圖模型包含三個階段:

  1. 撤銷層級
  2. 記錄及更新顯示清單
  3. 繪製顯示清單

使用這個模型時,您無法仰賴與無效區域相交的檢視畫面執行其 draw() 方法。為確保 Android 系統記錄檢視畫面的顯示清單,您必須呼叫 invalidate()。如果不這樣做,即使檢視畫面變更,仍會以相同方式呈現。

使用顯示清單功能也能提升動畫效能,因為設定特定屬性(例如 Alpha 或旋轉)不需要使目標檢視區塊失效(系統會自動執行此動作)。這項最佳化功能也適用於含有顯示清單的檢視畫面(應用程式執行硬體加速時可使用的任何檢視畫面)。舉例來說,假設 LinearLayout 包含 Button 上方的 ListViewLinearLayout 的顯示清單如下所示:

  • DrawDisplayList(ListView)
  • DrawDisplayList(Button)

假設您想變更 ListView 的不透明度。在 ListView 上叫用 setAlpha(0.5f) 後,顯示清單現在會包含:

  • SaveLayerAlpha(0.5)
  • DrawDisplayList(ListView)
  • 還原
  • DrawDisplayList(Button)

未執行 ListView 的複雜繪圖程式碼。系統只更新更簡易的 LinearLayout 顯示清單。在未啟用硬體加速功能的應用程式中,會再次執行清單及其父項的繪圖程式碼。

支援繪圖作業

硬體加速時,2D 算繪管道可支援最常用的 Canvas 繪圖作業,以及許多不常用的作業。系統支援所有用於算繪 Android 應用程式的繪圖作業,預設小工具和版面配置,以及反射和圖塊紋理等常見的進階視覺效果。

下表說明各種 API 級別的各種作業支援等級:

第一支援的 API 級別
畫布
drawBitmapMesh() (色彩陣列) 18
drawPicture() 23
drawPosText() 16
drawTextOnPath() 16
drawVertices() 29
setDrawFilter() 16
clipPath() 18
clipRegion() 18
clipRect(Region.Op.XOR) 18
clipRect(Region.Op.Difference) 18
clipRect(Region.Op.ReverseDifference) 18
clipRect() 旋轉/視角 18
繪製
setAntiAlias() (適用於文字) 18
setAntiAlias() (行) 16
setFilterBitmap() 17
setLinearText()
setMaskFilter()
setPathEffect() (行) 28
setShadowLayer() (文字除外) 28
setStrokeCap() (行) 18
setStrokeCap() (點) 19
setSubpixelText() 28
Xfermode
PorterDuff.Mode.DARKEN (framebuffer) 28
PorterDuff.Mode.LIGHTEN (framebuffer) 28
PorterDuff.Mode.OVERLAY (framebuffer) 28
著色器
ComposeShader 中的 ComposeShader 28
ComposeShader 中的相同類型著色器 28
ComposeShader 上的本機矩陣 18

畫布縮放

最初的硬體加速 2D 算繪管道最初是支援未縮放的繪圖,有些繪圖作業在採用較大的縮放值時會大幅降低品質。這些作業會實作為縮放比例為 1.0 的紋理,並由 GPU 轉換。從 API 級別 28 開始,所有繪圖作業都可以順利縮放。

下表顯示了變更實作以正確處理大比值縮放:
要縮放的繪圖作業 第一支援的 API 級別
drawText() 18
drawPosText() 28
drawTextOnPath() 28
簡單形狀* 17
複雜形狀* 28
drawPath() 28
陰影圖層 28

注意:「簡單」形狀指的是使用不含 PathEffect,也不含非預設彙整 (透過 setStrokeJoin()/setStrokeMiter()) 的 Paint,發出的 drawRect()drawCircle()drawOval()drawRoundRect()drawArc() (其中 useCenter=false) 指令。這些繪製指令的其他例項都屬於上表中的「複雜」形狀。

如果您的應用程式受到任何缺少的功能或限制影響,您可以呼叫 setLayerType(View.LAYER_TYPE_SOFTWARE, null) 來只為應用程式受影響的部分關閉硬體加速。這樣一來,您仍可以運用任意地方的硬體加速功能。如要進一步瞭解如何在應用程式的不同層級啟用和停用硬體加速功能,請參閱「控制硬體加速」一文。

檢視畫面圖層

在所有 Android 版本中,檢視畫面都能使用檢視畫面的繪圖快取或 Canvas.saveLayer() 算繪到螢幕外的緩衝區。螢幕外緩衝區或圖層有多種用途。他們可用於在呈現複雜的檢視畫面動畫時,或套用組合效果時獲得更好的效果。例如,您可以使用 Canvas.saveLayer() 來實作淡出效果,暫時將檢視畫面算繪到圖層,然後以不透明度的因數在螢幕上重新合併。

從 Android 3.0 (API 級別 11) 開始,您可以透過 View.setLayerType() 方法進一步控制如何使用圖層。這個 API 採用兩個參數:您要使用的圖層類型和選用的 Paint 物件,說明圖層的複合式結構。您可以使用 Paint 參數為圖層套用色彩濾鏡、特殊混合模式或透明度。檢視畫面可使用以下三種圖層類型之一:

  • LAYER_TYPE_NONE:檢視畫面正常算繪,而且不受螢幕外緩衝區支援。此為預設行為。
  • LAYER_TYPE_HARDWARE:如果應用程式使用硬體加速功能,系統會將硬體中的檢視畫面算繪為硬體紋理。如果應用程式未使用硬體加速,此圖層類型的行為與 LAYER_TYPE_SOFTWARE 相同。
  • LAYER_TYPE_SOFTWARE:檢視畫面在軟體中算繪為點陣圖。

您使用的圖層類型取決於您的目標:

  • 效能:使用硬體圖層類型將檢視畫面算繪為硬體紋理。將檢視畫面算繪成圖層後,直到檢視畫面呼叫 invalidate() 時,系統才會執行其繪圖程式碼。接著,部分動畫 (例如 Alpha 動畫) 可以直接套用至圖層,這對於 GPU 執行而言非常高效。
  • 視覺效果:使用硬體或軟體圖層類型和 Paint,將特殊視覺處理方式套用至檢視畫面。舉例來說,您可以使用 ColorMatrixColorFilter 以黑色和白色繪製檢視畫面。
  • 相容性:使用軟體圖層類型來強制在軟體中算繪檢視畫面。如果檢視畫面採用硬體加速 (例如整個應用程式都採用硬體加速) 後產生了算繪問題,這種方法可輕鬆解決硬體算繪管道的限制問題。

檢視畫面圖層和動畫

如果應用程式採用硬體加速,硬體圖層可以提供更快速、更流暢的動畫效果。對於核發許多繪圖作業的複雜檢視畫面,在進行動畫時,無法以每秒 60 個影格為單位執行動畫。使用硬體層將檢視畫面算繪為硬體紋理可解決這個問題。可以使用硬體紋理為檢視畫面建立動畫,因此不需要在檢視畫面動畫化時,重新繪製檢視畫面。除非您變更檢視畫面的屬性、呼叫 invalidate() 或手動呼叫 invalidate(),否則不會重新繪製檢視畫面。如果您在應用程式中執行動畫,卻未獲得所需的流暢結果,請考慮在動畫檢視畫面中啟用硬體圖層。

當檢視畫面由硬體圖層支援時,其部分屬性會以圖層在畫面中的複合方式處理。設定這些屬性非常有效率,因為這類屬性不需要使用無效且重新繪製的檢視畫面。下列屬性會影響圖層的組成方式。針對下列任一屬性呼叫 setter 將導致最佳無效結果,而且無需重新繪製目標檢視畫面:

  • alpha:變更圖層的不透明度
  • xytranslationXtranslationY:變更圖層的位置
  • scaleXscaleY:變更圖層的大小
  • rotationrotationXrotationY:變更圖層在 3D 空間中的方向
  • pivotXpivotY:變更圖層的轉換來源

這些屬性是使用 ObjectAnimator 為檢視畫面建立動畫時使用的名稱。如要存取這些屬性,需叫用適當的 setter 或 getter。舉例來說,如要修改 Alpha 屬性,請叫用 setAlpha()。下列程式碼片段顯示圍繞 Y 軸時,以 3D 方式旋轉檢視畫面的最有效方法:

Kotlin

view.setLayerType(View.LAYER_TYPE_HARDWARE, null)
ObjectAnimator.ofFloat(view, "rotationY", 180f).start()

Java

view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ObjectAnimator.ofFloat(view, "rotationY", 180).start();

由於硬體圖層會耗用影片記憶體,因此強烈建議您只在動畫期間啟用這類記憶體,在動畫結束後停用它們。您可以利用動畫監聽器完成這項操作:

Kotlin

view.setLayerType(View.LAYER_TYPE_HARDWARE, null)
ObjectAnimator.ofFloat(view, "rotationY", 180f).apply {
    addListener(object : AnimatorListenerAdapter() {
        override fun onAnimationEnd(animation: Animator) {
            view.setLayerType(View.LAYER_TYPE_NONE, null)
        }
    })
    start()
}

Java

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();

如要進一步瞭解屬性動畫,請參閱「屬性動畫」。

提示與秘訣

切換為硬體加速 2D 圖形可以立即提升效能,但如果要在設計應用程式時有效使用 GPU,您仍應採用下列建議:

減少應用程式中的檢視畫面數量
系統需要繪製的檢視畫面越多,處理速度就越慢。這也適用於軟體算繪管道。如要最佳化使用者介面,最簡單的方法之一就是減少檢視畫面。
避免過度繪製
請勿互相繪製過多圖層。移除檢視畫面上被其他不透明檢視畫面完全遮擋的檢視畫面。如果您需要繪製多個彼此重疊的圖層,請考慮將這些圖層合併為單一圖層。使用目前的硬體繪製時,原則上每個畫面每個像素的像素數都不會超過 2.5 倍 (點陣圖數量中的透明像素!)
不要在繪圖方法中建立算繪物件
常見的錯誤是每次叫用算繪方法時,建立新的 Paint 或新的 Path。這會強制更頻繁地執行垃圾收集器,並在硬體管道中略過快取和最佳化。
不要經常修改形狀
舉例來說,複雜的形狀、路徑和圓形都是使用紋理遮罩算繪的。每次建立或修改路徑時,硬體管道都會建立新的遮罩,費用高昂。
不要經常修改點陣圖
每次變更點陣圖的內容時,系統都會在下次繪圖時,重新上傳為 GPU 紋理。
請謹慎使用 Alpha 版
使用 setAlpha()AlphaAnimationObjectAnimator 建立半透明的檢視畫面時,該畫面會呈現在螢幕外緩衝區中,所需供應率會加倍。針對非常大的檢視畫面套用 Alpha 版時,建議您將檢視畫面的圖層類型設為 LAYER_TYPE_HARDWARE