SurfaceView と GLSurfaceView

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

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 の使用が容易になりますが、状況によっては困難になることもあります。