SurfaceView und GLSurfaceView

Die Benutzeroberfläche des Android App-Frameworks basiert auf einer Hierarchie von Objekten, die mit einer Ansicht beginnen. Alle UI-Elemente werden einer Reihe von Messungen und einem Layoutprozess unterzogen, um sie in einen rechteckigen Bereich einzupassen. Anschließend werden alle sichtbaren Ansichtsobjekte auf einer Oberfläche gerendert, die vom WindowManager eingerichtet wurde, als die App in den Vordergrund gebracht wurde. Der UI-Thread der App führt das Layout und Rendering in einem Buffer pro Frame aus.

SurfaceView

Eine SurfaceView ist eine Komponente, mit der Sie eine zusätzliche zusammengesetzte Ebene in Ihre Ansichtshierarchie einbetten können. Eine SurfaceView verwendet dieselben Layoutparameter wie andere Ansichten und kann daher wie jede andere Ansicht manipuliert werden. Der Inhalt der SurfaceView ist jedoch transparent.

Wenn Sie mit einer externen Pufferquelle wie einem GL-Kontext oder einem Mediendekoder rendern, müssen Sie Puffer aus der Pufferquelle kopieren, um sie auf dem Bildschirm anzuzeigen. Mit einer SurfaceView ist das möglich.

Wenn die Ansichtskomponente der SurfaceView sichtbar werden soll, bittet das Framework SurfaceControl, eine neue Oberfläche von SurfaceFlinger anzufordern. Wenn Sie Callbacks erhalten möchten, wenn die Oberfläche erstellt oder zerstört wird, verwenden Sie die SurfaceHolder-Schnittstelle. Standardmäßig wird die neu erstellte Oberfläche hinter der App-UI platziert. Sie können die Standard-Z-Reihenfolge überschreiben, um die neue Oberfläche nach oben zu verschieben.

Das Rendern mit SurfaceView ist in Fällen von Vorteil, in denen Sie auf einer separaten Oberfläche rendern müssen, z. B. beim Rendern mit der Camera API oder einem OpenGL ES-Kontext. Wenn Sie mit SurfaceView rendern, stellt SurfaceFlinger Buffers direkt auf dem Bildschirm zusammen. Ohne SurfaceView müssen Sie Buffers mit einer Offscreen-Oberfläche zusammenführen, die dann auf dem Bildschirm zusammengeführt wird. Das Rendering mit SurfaceView vermeidet zusätzlichen Aufwand. Nach dem Rendern mit SurfaceView verwenden Sie den UI-Thread, um die Aktivität mit dem Lebenszyklus zu koordinieren und bei Bedarf die Größe oder Position der Ansicht anzupassen. Anschließend kombiniert der Hardware Composer die App-Benutzeroberfläche und die anderen Ebenen.

Die neue Oberfläche ist die Produzentenseite einer BufferQueue, deren Verbraucher eine SurfaceFlinger-Ebene ist. Sie können die Oberfläche mit jedem Mechanismus aktualisieren, der eine BufferQueue versorgen kann, z. B. mit Canvas-Funktionen, die von der Oberfläche bereitgestellt werden, mit dem Anhängen einer EGLSurface und dem Zeichnen auf der Oberfläche mit GLES oder mit dem Konfigurieren eines Mediendekoders zum Schreiben der Oberfläche.

SurfaceView und der Aktivitätslebenszyklus

Wenn Sie eine SurfaceView verwenden, rendern Sie die Oberfläche in einem anderen Thread als dem Haupt-UI-Thread.

Für eine Aktivität mit einer SurfaceView gibt es zwei separate, aber voneinander abhängige Zustandsmaschinen:

  • App onCreate/onResume/onPause
  • Oberfläche erstellt/geändert/zerstört

Wenn die Aktivität gestartet wird, erhalten Sie Callbacks in dieser Reihenfolge:

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

Wenn Sie auf „Zurück“ klicken, erhalten Sie Folgendes:

  1. onPause()
  2. surfaceDestroyed() (wird kurz vor dem Verlassen der Oberfläche aufgerufen)

Wenn Sie das Display drehen, wird die Aktivität entfernt und neu erstellt. Sie sehen dann den vollständigen Zyklus. Ob es sich um einen Schnellstart handelt, erkennen Sie an der Markierung bei isFinishing(). Es ist möglich, eine Aktivität so schnell zu starten/stoppen, dass surfaceCreated() nach onPause() eintritt.

Wenn Sie auf die Ein-/Aus-Taste tippen, um das Display auszuschalten, wird nur onPause() ohne surfaceDestroyed() angezeigt. Die Oberfläche bleibt aktiv und das Rendering kann fortgesetzt werden. Du kannst weiterhin Choreographer-Ereignisse erhalten, wenn du sie weiterhin anforderst. Wenn Sie einen Sperrbildschirm haben, der eine andere Ausrichtung erzwingt, werden Ihre Aktivitäten möglicherweise fortgesetzt, wenn das Display des Geräts aktiviert wird. Andernfalls können Sie den Bildschirmschoner mit derselben Oberfläche beenden wie zuvor.

Die Lebensdauer des Threads kann an die Oberfläche oder an die Aktivität gebunden werden, je nachdem, was passieren soll, wenn das Display dunkel wird. Der Thread kann entweder beim Starten/Beenden der Aktivität oder beim Erstellen/Zerstören der Oberfläche gestartet/angehalten werden.

Wenn der Thread beim Starten/Anhalten der Aktivität gestartet und angehalten wird, passt das gut zum App-Lebenszyklus. Sie starten den Renderer-Thread in onResume() und beenden ihn in onStop(). Beim Erstellen und Konfigurieren des Threads ist die Oberfläche manchmal bereits vorhanden, manchmal nicht (z. B. ist sie noch aktiv, nachdem das Display mit der Ein-/Aus-Taste ausgeschaltet wurde). Sie müssen warten, bis die Oberfläche erstellt wurde, bevor Sie sie im Thread initialisieren. Sie können die Initialisierung nicht im surfaceCreate()-Callback vornehmen, da er nicht noch einmal ausgelöst wird, wenn die Oberfläche nicht neu erstellt wurde. Stattdessen sollten Sie den Oberflächenstatus abfragen oder zwischenspeichern und an den Renderer-Thread weiterleiten.

Das Starten und Beenden des Threads beim Erstellen/Zerstören der Oberfläche funktioniert gut, da die Oberfläche und der Renderer logisch miteinander verbunden sind. Sie starten den Thread erst nach dem Erstellen der Oberfläche, wodurch einige Probleme bei der Kommunikation zwischen Threads vermieden werden. Nachrichten zum Erstellen oder Ändern der Oberfläche werden einfach weitergeleitet. Damit das Rendering gestoppt wird, wenn das Display leer ist, und fortgesetzt wird, wenn es wieder angezeigt wird, müssen Sie Choreographer anweisen, den Frame-Draw-Callback nicht mehr aufzurufen. onResume() setzt die Callbacks fort, wenn der Renderer-Thread ausgeführt wird. Wenn Sie die Animation jedoch basierend auf der verstrichenen Zeit zwischen den Frames animieren, kann es eine große Lücke geben, bevor das nächste Ereignis eintritt. Mit einer expliziten Nachricht zum Pausieren/Fortsetzen lässt sich dieses Problem lösen.

Bei beiden Optionen, unabhängig davon, ob die Lebensdauer des Threads an die Aktivität oder die Oberfläche gebunden ist, geht es darum, wie der Renderer-Thread konfiguriert ist und ob er ausgeführt wird. Ein ähnliches Problem ist das Extrahieren des Status aus dem Thread, wenn die Aktivität beendet wird (in onStop() oder onSaveInstanceState()). In solchen Fällen ist es am besten, die Lebensdauer des Threads an die Aktivität zu binden, da nach dem Zusammenführen des Renderer-Threads auf den Status des gerenderten Threads ohne Synchronisierungsprimitiven zugegriffen werden kann.

GLSurfaceView

Die Klasse GLSurfaceView bietet Hilfsklassen zum Verwalten von EGL-Kontexten, zur Kommunikation zwischen Threads und zur Interaktion mit dem Aktivitätszyklus. Sie müssen keine GLSurfaceView verwenden, um GLES zu verwenden.

GLSurfaceView erstellt beispielsweise einen Thread für das Rendering und konfiguriert dort einen EGL-Kontext. Der Status wird automatisch bereinigt, wenn die Aktivität pausiert wird. Die meisten Apps müssen nichts über EGL wissen, um GLES mit GLSurfaceView zu verwenden.

In den meisten Fällen kann GLSurfaceView die Arbeit mit GLES erleichtern. In manchen Situationen kann es störend sein.