ハードウェア アクセラレーション

Android 3.0(API レベル 11)から、Android 2D レンダリング パイプラインはハードウェア アクセラレーションをサポートしています。つまり、View のキャンバス上で行われる描画オペレーションはすべて GPU を使用します。ハードウェア アクセラレーションを有効にするために必要なリソースが増えるため、アプリの RAM 使用量が増えます。

対象 API レベルが 14 以上の場合、ハードウェア アクセラレーションはデフォルトで有効になりますが、明示的に有効にすることもできます。アプリケーションで標準のビューと Drawable のみを使用している場合は、グローバルにオンにしても描画に悪影響はありません。ただし、ハードウェア アクセラレーションはすべての 2D 描画オペレーションでサポートされているわけではないため、オンにすると一部のカスタムビューまたは描画呼び出しに影響する場合があります。問題は通常、要素が表示されない、例外、またはピクセルが誤ってレンダリングされることとして現れます。この問題を解決するために、Android にはハードウェア アクセラレーションを複数のレベルで有効または無効にするオプションが用意されています。ハードウェア アクセラレーションを制御するをご覧ください。

アプリケーションでカスタム描画を行う場合は、ハードウェア アクセラレーションをオンにして実際のハードウェア デバイスでアプリケーションをテストし、問題がないか確認します。描画オペレーションのサポートのセクションでは、ハードウェア アクセラレーションの既知の問題と、その回避方法について説明します。

Framework API を使用した OpenGLRenderscript もご覧ください。

ハードウェア アクセラレーションを制御する

ハードウェア アクセラレーションは、次のレベルで制御できます。

  • アプリ
  • アクティビティ
  • ウィンドウ
  • 表示

アプリレベル

Android マニフェスト ファイルで、次の属性を <application> タグに追加してアプリ全体のハードウェア アクセラレーションを有効にします。

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

アクティビティ レベル

ハードウェア アクセラレーションをグローバルにオンにしてもアプリが正しく動作しない場合は、個々のアクティビティに対して制御することもできます。ハードウェア アクセラレーションをアクティビティ レベルで有効または無効にするには、<activity> 要素の android:hardwareAccelerated 属性を使用します。次の例では、アプリ全体に対してハードウェア アクセラレーションを有効にしていますが、1 つのアクティビティに対しては無効にしています。

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

: 現在、ビューレベルではハードウェア アクセラレーションを有効にできません。ビューレイヤには、ハードウェア アクセラレーションを無効にする以外の機能もあります。使用方法の詳細については、ビューレイヤをご覧ください。

ビューがハードウェア アクセラレーションされているかどうかを判断する

アプリがハードウェア アクセラレーションされているかどうかを確認することは、特にカスタムビューなどの場合に役立つことがあります。これは、アプリがカスタム描画を多く行い、すべてのオペレーションが新しいレンダリング パイプラインで適切にサポートされていない場合、特に便利です。

アプリがハードウェア アクセラレーションされているかどうかを確認する方法は 2 つあります。

このチェックを描画コードで行う必要がある場合は、可能であれば View.isHardwareAccelerated() ではなく Canvas.isHardwareAccelerated() を使用します。ビューがハードウェア アクセラレーションされているウィンドウにアタッチされていても、ハードウェア アクセラレーションされていないキャンバスを使用して描画できます。これは、たとえばキャッシュのためにビューをビットマップに描画するときに発生します。

Android 描画モデル

ハードウェア アクセラレーションが有効になっている場合、Android フレームワークは、ディスプレイ リストを利用してアプリを画面にレンダリングする新しい描画モデルを利用します。ディスプレイ リストと、それがアプリに及ぼす影響を完全に理解するには、ハードウェア アクセラレーションなしで Android がビューを描画する方法について理解しておくことが役に立ちます。次のセクションでは、ソフトウェアベースの描画モデルとハードウェア アクセラレーションされている描画モデルについて説明します。

ソフトウェアベースの描画モデル

ソフトウェア描画モデルでは、次の 2 つのステップでビューが描画されます。

  1. 階層を無効にする
  2. 階層を描画する

アプリで UI の一部の更新が必要になると、コンテンツを変更したすべてのビューについて invalidate()(またはそのバリアントの 1 つ)が呼び出されます。無効化メッセージはビュー階層の全体に反映され、再描画が必要な画面の領域(ダーティな領域)が計算されます。Android システムは、ダーティな領域と交差する階層内のビューを描画します。残念ながら、この描画モデルには欠点が 2 つあります。

  • まず、このモデルでは描画パスごとに大量のコードを実行する必要があります。たとえば、アプリがボタンに invalidate() を呼び出し、そのボタンが別のビューの上にある場合、Android システムはビューが変更されていなくても再描画します。
  • 2 つめの問題は、描画モデルがアプリのバグを隠す場合があるということです。Android システムは、ダーティな領域と交差するときにビューを再描画するため、コンテンツを変更したビューは invalidate() が呼び出されていなくても再描画される可能性があります。これが発生した場合は、正常な動作を得るには別のビューが無効化されることが必要になります。この動作は、アプリを変更するたびに変わる可能性があります。そのため、ビューの描画コードに影響するデータまたはステートを変更するときは、常にカスタムビューで invalidate() を呼び出す必要があります。

: Android のビューでは、背景色や TextView のテキストなどのプロパティが変更されると、自動的に invalidate() が呼び出されます。

ハードウェア アクセラレーションされている描画モデル

Android システムは invalidate()draw() を使用して画面の更新をリクエストし、ビューのレンダリングを行いますが、実際の描画の処理は異なります。Android システムでは描画コマンドがすぐに実行されるのではなく、ビュー階層の描画コードの出力を含む表示リストに記録されます。もう 1 つの最適化は、Android システムが invalidate() 呼び出しによってダーティとマークされたビューのディスプレイ リストを記録し更新するだけでよいということです。無効化されていないビューは、以前に記録されたディスプレイ リストを再発行するだけで再描画できます。新しい描画モデルには次の 3 つの段階があります。

  1. 階層を無効にする
  2. ディスプレイ リストを記録し、更新する
  3. ディスプレイ リストを描画する

このモデルでは、ダーティな領域と交差するビューに依存して draw() メソッドを実行することはできません。Android システムでビューのディスプレイ リストを確実に記録するには、invalidate() を呼び出す必要があります。これを怠ると、ビューは変更された後でも同じように表示されます。

ディスプレイ リストを使用すると、アルファや回転などの特定のプロパティを設定しても対象のビューを無効にする必要がないため(自動的に行われます)、アニメーションのパフォーマンスも向上します。この最適化は、ディスプレイ リストのあるビュー(アプリケーションがハードウェア アクセラレーションされている場合のビュー)にも適用されます。たとえば、Button の上に ListView を含む LinearLayout があるとします。LinearLayout のディスプレイ リストは次のようになります。

  • DrawDisplayList(ListView)
  • DrawDisplayList(Button)

ここで、ListView の不透明度を変更するとします。ListViewsetAlpha(0.5f) を呼び出すと、ディスプレイ リストは次のようになります。

  • SaveLayerAlpha(0.5)
  • DrawDisplayList(ListView)
  • Restore
  • DrawDisplayList(Button)

ListView の複雑な描画コードは実行されませんでした。代わりに、システムはずっと単純な LinearLayout のディスプレイ リストのみを更新しました。ハードウェア アクセラレーションが有効になっていないアプリでは、リストとその親の描画コードが両方とも再度実行されます。

描画オペレーションのサポート

ハードウェア アクセラレーションを行うと、2D レンダリング パイプラインは、よく使用される Canvas 描画オペレーションと、あまり使用されない多くのオペレーションをサポートします。Android に付属するアプリのレンダリングに使用される描画オペレーション、デフォルトのウィジェットとレイアウト、また反射やタイル テクスチャなどの、よく使われる高度な視覚効果がすべてサポートされています。

次の表に、API レベルにわたってさまざまなオペレーションのサポートレベルを示します。

サポートされている最初の API レベル
Canvas
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()(rotation / perspective を指定) 18
Paint
setAntiAlias()(テキストの場合) 18
setAntiAlias()(線の場合) 16
setFilterBitmap() 17
setLinearText()
setMaskFilter()
setPathEffect()(線の場合) 28
setShadowLayer()(テキスト以外) 28
setStrokeCap()(線の場合) 18
setStrokeCap()(点の場合) 19
setSubpixelText() 28
Xfermode
PorterDuff.Mode.DARKEN(フレームバッファ) 28
PorterDuff.Mode.LIGHTEN(フレームバッファ) 28
PorterDuff.Mode.OVERLAY(フレームバッファ) 28
Shader
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 オブジェクトという 2 つのパラメータを取ります。Paint パラメータを使用して、カラーフィルタ、特別なブレンドモード、または不透明度をレイヤに適用できます。ビューでは、次の 3 つのレイヤタイプのうちいずれかを使用できます。

  • LAYER_TYPE_NONE: ビューは通常どおりレンダリングされます。オフスクリーン バッファによって支えられてはいません。これがデフォルト設定です。
  • LAYER_TYPE_HARDWARE: アプリケーションがハードウェア アクセラレーションされている場合、ビューはハードウェアでハードウェア テクスチャにレンダリングされます。アプリがハードウェア アクセラレーションされていない場合、このレイヤタイプは LAYER_TYPE_SOFTWARE と同じように動作します。
  • LAYER_TYPE_SOFTWARE: ビューはソフトウェアでビットマップにレンダリングされます。

使用するレイヤのタイプは、目的によって異なります。

  • パフォーマンス: ハードウェア レイヤタイプを使用してビューをハードウェア テクスチャにレンダリングします。ビューがレイヤにレンダリングされたら、ビューが invalidate() を呼び出すまで、描画コードを実行する必要はありません。アルファ アニメーションなどの一部のアニメーションは、レイヤに直接適用できます。これは GPU で非常に効率的です。
  • 視覚効果: ハードウェアまたはソフトウェアのレイヤタイプと Paint を使用して、ビューに特殊な視覚処理を適用します。たとえば ColorMatrixColorFilter を使用して、白黒でビューを描画できます。
  • 互換性: ソフトウェア レイヤタイプを使用して、ビューを強制的にソフトウェアでレンダリングします。ハードウェア アクセラレーションされたビュー(たとえば、アプリケーション全体がハードウェア アクセラレーションされている場合)でレンダリングの問題がある場合、ハードウェア レンダリング パイプラインの制限を簡単に回避できます。

ビューレイヤとアニメーション

アプリケーションがハードウェア アクセラレーションされている場合、ハードウェア レイヤを使用すると高速で滑らかなアニメーションを実現できます。多くの描画オペレーションを生む複雑なビューをアニメーション化する場合は、必ずしもアニメーションを 60 フレーム/秒で実行できるとは限りません。これは、ハードウェア レイヤを使用してビューをハードウェア テクスチャにレンダリングすることで緩和できます。その場合、ハードウェア テクスチャを使用してビューをアニメーション化できるため、アニメーション化するときにビューを常に再描画する必要がなくなります。invalidate() を呼び出すビューのプロパティを変更するか、invalidate() を手動で呼び出しない限り、ビューは再描画されません。アプリでアニメーションを実行していて、求める滑らかな結果が得られない場合は、アニメーション ビューでハードウェア レイヤを有効にすることを検討してください。

ビューがハードウェア レイヤで支えられている場合、そのプロパティの一部は、レイヤを画面上で合成する方法で処理されます。そのようなプロパティを設定すると、ビューを無効にして再描画する必要がないため、効率的です。次のプロパティのリストは、レイヤの合成方法に影響します。こうしたプロパティのいずれかに対してセッターを呼び出すと、最適な無効化が行われ、対象のビューは再描画されません。

  • alpha: レイヤの不透明度を変更します
  • xytranslationXtranslationY: レイヤの位置を変更します
  • scaleXscaleY: レイヤのサイズを変更します
  • rotationrotationXrotationY: 3D 空間でのレイヤの向きを変更します
  • pivotXpivotY: レイヤの変換元を変更します

これらのプロパティは、ObjectAnimator でビューをアニメーション化するときに使用される名前です。これらのプロパティにアクセスする場合は、該当するセッターまたはゲッターを呼び出します。たとえば 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 を効果的に使用するようにアプリケーションを設計する必要があります。

アプリのビューの数を減らす
システムが描画するビューの数が多いほど、速度は遅くなります。これはソフトウェア レンダリング パイプラインにも当てはまります。ビューを減らすことは、UI を最適化する非常に簡単な方法です。
オーバードローを回避する
あまり多くのレイヤを重ね合わせて描画しないでください。上にある他の不透明なビューで完全に隠されているビューは削除します。複数のレイヤを重ね合わせて描画する必要がある場合は、1 つのレイヤに統合することを検討してください。現在のハードウェアでの経験則として、フレームごとに画面上のピクセル数(ビットマップ内の透明ピクセルのカウント)の 2.5 倍を超えて描画しないことをおすすめします。
描画メソッドでレンダリング オブジェクトを作成しない
よくある間違いは、レンダリング メソッドを呼び出すたびに新しい Paint または新しい Path を作成することです。そうすると、ガベージ コレクタの実行頻度が高くなり、ハードウェア パイプラインのキャッシュと最適化もバイパスされます。
図形を頻繁に変更しない
複雑な図形、パス、円などは、テクスチャ マスクを使用してレンダリングされます。パスを作成または変更するたびに、ハードウェア パイプラインによって新しいマスクが作成されます。これはコストが高くつく可能性があります。
ビットマップを頻繁に変更しない
ビットマップのコンテンツを変更するたびに、次回描画するときに GPU テクスチャとして再度アップロードされます。
アルファは慎重に使用する
setAlpha()AlphaAnimation、または ObjectAnimator を使用してビューを半透明にすると、必要なフィルレートが倍になるオフスクリーン バッファにレンダリングされます。非常に大きなビューにアルファを適用する場合は、ビューのレイヤタイプを LAYER_TYPE_HARDWARE に設定することを検討してください。