SurfaceView と GLSurfaceView

Android アプリのフレームワーク UI は、View で始まるオブジェクトの階層に基づいています。すべての UI 要素は、一連の測定とレイアウト プロセスを経て、矩形領域に収まります。次に、表示状態のすべてのビュー オブジェクトは、アプリが前面に表示されたときに WindowManager によって設定されたサーフェスにレンダリングされます。アプリの UI スレッドは、フレームごとにバッファへのレイアウトとレンダリングを実行します。

SurfaceView

SurfaceView は、ビューの階層内に追加の複合レイヤを埋め込むために使用できるコンポーネントです。SurfaceView は、他のビューと同じレイアウト パラメータを取得するため、他のビューと同じように操作できますが、SurfaceView の内容は透明になります。

GL コンテキストやメディア デコーダなどの外部バッファソースでレンダリングする場合、バッファソースからバッファをコピーして画面上に表示する必要があります。これは、SurfaceView を使用することで実行できます。

SurfaceView のビュー コンポーネントが表示されると、フレームワークは SurfaceFlinger から新しいサーフェスをリクエストするように SurfaceControl に要求します。サーフェスが作成または破棄されたときにコールバックを受け取るには、SurfaceHolder インターフェースを使用します。デフォルトでは、新しく作成されたサーフェスはアプリの UI サーフェスの背後に配置されます。デフォルトの Z オーダーをオーバーライドして、新しいサーフェスを上に配置できます。

SurfaceView でのレンダリングは、Camera API や OpenGL ES コンテキストでレンダリングするときなど、別のサーフェスにレンダリングする必要がある場合に便利です。SurfaceView でレンダリングすると、SurfaceFlinger は、バッファを画面に直接合成します。SurfaceView を使用しない場合は、バッファを画面上のサーフェスに合成する必要があります(その後、画面に合成されます)。つまり、SurfaceView でレンダリングすると、余分な作業が排除されることになります。SurfaceView でレンダリングしたら、必要に応じて UI スレッドを使用してアクティビティのライフサイクルとの調整、またはビューのサイズや位置の調整を行います。次に、Hardware Composer がアプリの UI と他のレイヤを合成します。

新しいサーフェスは BufferQueue のプロデューサー側で、その消費者は SurfaceFlinger レイヤです。サーフェスが提供するキャンバス関数など、BufferQueue にフィードを提供できる任意のメカニズムでサーフェスを更新できるので、EGLSurface をアタッチして GLES でサーフェスに描画、またはサーフェスに対して書き込みを行うメディア デコーダを設定できます。

SurfaceView とアクティビティのライフサイクル

SurfaceView を使用する場合、メインの UI スレッド以外のスレッドからサーフェスをレンダリングします。

SurfaceView のあるアクティビティには、独立しているものの相互に依存する 2 つのマシンがあります。

  • アプリ onCreate / onResume / onPause
  • サーフェスの作成 / 変更 / 破棄

アクティビティが開始すると、次の順番でコールバックを受けます。

  1. onCreate()
  2. onResume()
  3. surfaceCreated()
  4. surfaceChanged()

[戻る] をクリックすると、次を受け取ります。

  1. onPause()
  2. surfaceDestroyed()(サーフェスがなくなる直前に呼び出されます)

画面を回転させると、アクティビティが破棄されて再作成され、フルサイクルを取得します。isFinishing() を確認することで、クイック再起動であることがわかります。アクティビティをすぐに開始または停止できるので、surfaceCreated()onPause() の後に発生します。

電源ボタンをタップして画面を空白にすると、surfaceDestroyed() なしの onPause() のみを取得します。サーフェスはアクティブのままで、レンダリングを続行できます。Choreographer のイベントは、リクエストを続けることで引き続き取得できます。別の向きを強制するロック画面がある場合は、デバイスの画面が空白ではないときに、アクティビティが再起動する可能性があります。それ以外の場合は、以前と同じサーフェスで画面の空白から出すことができます。

スレッドの存続期間は、画面に何も表示されないときに何を実行したいかに応じて、サーフェスまたはアクティビティに関連付けられます。スレッドは、アクティビティの開始 / 停止、またはサーフェスの作成 / 廃棄のいずれかで開始 / 停止できます。

アクティビティの開始 / 停止で開始 / 停止のスレッドがある場合は、アプリのライフサイクルと適切に連携します。レンダラのスレッドを onResume() で開始して、onStop() で停止します。スレッドを作成して設定するときに、サーフェスがすでに存在する場合と存在しない場合があります(たとえば、電源ボタンで画面を切り替えた後も、サーフェスは引き続きアクティブです)。スレッドで初期化する前に、サーフェスが作成されるのを待つ必要があります。サーフェスが再作成されていない場合は再び配信しないため、surfaceCreate() コールバックでは初期化できません。代わりに、サーフェスの状態のクエリまたはキャッシュを実行し、レンダラ スレッドに転送します。

サーフェスとレンダラは論理的に絡み合っているため、サーフェスの作成 / 破棄で開始 / 停止のスレッドがある場合は適切に機能します。サーフェスの作成後にスレッドを開始することで、スレッド間の通信に関する問題が一部回避され、サーフェスが作成 / 変更したメッセージは単純に転送されます。画面が空白になったときにレンダリングが停止し、空白ではなくなったときに再開されるようにするには、フレーム描画のコールバックの呼び出しを停止するように、Choreographer に指示します。onResume() は、レンダラ スレッドが実行中の場合にコールバックを再開します。ただし、フレーム間の経過時間に基づいてアニメーション化する場合は、次のイベントに到達する前に大きなギャップが生じることがあります。明示的な一時停止 / 再開のメッセージを使用することで、この問題を解決できます。

どちらのオプションも、スレッドの存続期間がアクティビティやサーフェスに関連付けられるかどうかにかかわらず、レンダラ スレッドの設定方法と実行の有無を重視します。関連する問題は、onStop()onSaveInstanceState() でアクティビティが強制終了されたときに、状態をスレッドから抽出することです。このような場合、レンダラ スレッドが結合した後、レンダリングされたスレッドの状態には同期プリミティブなしでアクセスできるため、スレッドの存続期間をアクティビティに結び付けるのが最適です。

GLSurfaceView

GLSurfaceView クラスは、EGL コンテキスト、スレッド間通信、アクティビティのライフサイクルとの相互作用を管理するためのヘルパークラスを提供します。GLSurfaceView を使用して GLES を使用する必要はありません。

たとえば、GLSurfaceView はレンダリング用のスレッドを作成し、そこで EGL コンテキストを構成します。状態は、アクティビティが一時停止すると自動的にクリーンアップされます。ほとんどのアプリは GLSurfaceView で GLES を使用するために EGL について認識する必要はありません。

ほとんどの場合、GLSurfaceView は GLES での作業を容易にします。状況によっては、邪魔になることもあります。