SurfaceView und GLSurfaceView

Die Benutzeroberfläche des Android-App-Frameworks basiert auf einer Hierarchie von Objekten, die mit einer View beginnen. Alle UI-Elemente durchlaufen eine Reihe von Messungen und einen Layoutprozess, der sie in einen rechteckigen Bereich einpasst. 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 Layout und Rendering in einem Puffer pro Frame durch.

SurfaceView

Ein 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, sodass sie wie jede andere Ansicht manipuliert werden kann, der Inhalt der SurfaceView ist jedoch transparent.

Wenn Sie mit einer externen Pufferquelle rendern, z. B. einem GL-Kontext oder einem Mediendecoder, müssen Sie Puffer aus der Pufferquelle kopieren, um die Puffer auf dem Bildschirm anzuzeigen. Mit einem SurfaceView können Sie dies tun.

Wenn die Ansichtskomponente von SurfaceView sichtbar wird, fordert das Framework SurfaceControl auf, eine neue Oberfläche von SurfaceFlinger anzufordern. Um Rückrufe zu erhalten, 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-Oberfläche platziert. Sie können die standardmäßige Z-Reihenfolge überschreiben, um die neue Oberfläche oben zu platzieren.

Das Rendern mit SurfaceView ist in Fällen von Vorteil, in denen Sie auf einer separaten Oberfläche rendern müssen, beispielsweise wenn Sie mit der Kamera-API oder einem OpenGL ES-Kontext rendern. Wenn Sie mit SurfaceView rendern, stellt SurfaceFlinger Puffer direkt auf dem Bildschirm zusammen. Ohne SurfaceView müssen Sie Puffer zu einer Offscreen-Oberfläche zusammenfügen, die dann mit dem Bildschirm zusammengesetzt wird, sodass beim Rendern mit SurfaceView kein zusätzlicher Aufwand erforderlich ist. Verwenden Sie nach dem Rendern mit SurfaceView den UI-Thread zur Koordinierung mit dem Aktivitätslebenszyklus und nehmen Sie bei Bedarf Anpassungen an der Größe oder Position der Ansicht vor. Anschließend verschmilzt der Hardware Composer die App-Benutzeroberfläche mit den anderen Ebenen.

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

SurfaceView und der Aktivitätslebenszyklus

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

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

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

Wenn die Aktivität beginnt, erhalten Sie Rückrufe in dieser Reihenfolge:

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

Wenn Sie zurück klicken, erhalten Sie:

  1. onPause()
  2. surfaceDestroyed() (wird aufgerufen, kurz bevor die Oberfläche verschwindet)

Wenn Sie den Bildschirm drehen, wird die Aktivität abgebaut und neu erstellt und Sie erhalten den vollständigen Zyklus. Sie können erkennen, dass es sich um einen schnellen Neustart handelt, indem Sie isFinishing() überprüfen. Es ist möglich, eine Aktivität so schnell zu starten/stoppen, dass surfaceCreated() nach onPause() erfolgt.

Wenn Sie auf den Netzschalter tippen, um den Bildschirm auszublenden, erhalten Sie nur onPause() ohne surfaceDestroyed() . Die Oberfläche bleibt aktiv und das Rendern kann fortgesetzt werden. Sie können weiterhin Choreographen-Events erhalten, wenn Sie diese weiterhin anfordern. Wenn Sie einen Sperrbildschirm haben, der eine andere Ausrichtung erzwingt, wird Ihre Aktivität möglicherweise neu gestartet, wenn das Gerät nicht mehr leer ist. Andernfalls können Sie mit der gleichen Oberfläche wie zuvor aus dem Bildschirm herauskommen.

Die Lebensdauer des Threads kann an die Oberfläche oder an die Aktivität gebunden sein, je nachdem, was passieren soll, wenn der Bildschirm leer wird. Der Thread kann entweder beim Starten/Stoppen der Aktivität oder beim Erstellen/Zerstören der Oberfläche gestartet/stoppen.

Das Starten/Stoppen des Threads beim Starten/Stoppen der Aktivität funktioniert gut mit dem App-Lebenszyklus. Sie starten den Renderer-Thread in onResume() und stoppen ihn in onStop() . Beim Erstellen und Konfigurieren des Threads ist die Oberfläche manchmal bereits vorhanden, manchmal nicht (z. B. ist sie immer noch aktiv, nachdem der Bildschirm mit der Power-Taste umgeschaltet wurde). Sie müssen warten, bis die Oberfläche erstellt wurde, bevor Sie sie im Thread initialisieren. Sie können den surfaceCreate() -Rückruf nicht initialisieren, da er nicht erneut ausgelöst wird, wenn die Oberfläche nicht neu erstellt wurde. Fragen Sie stattdessen den Oberflächenzustand ab oder zwischenspeichern Sie ihn und leiten Sie ihn an den Renderer-Thread weiter.

Das Starten/Stoppen des Threads beim Erstellen/Zerstören der Oberfläche funktioniert gut, da die Oberfläche und der Renderer logisch miteinander verknüpft sind. Sie starten den Thread, nachdem die Oberfläche erstellt wurde, wodurch einige Bedenken hinsichtlich der Kommunikation zwischen Threads vermieden werden. und oberflächlich erstellte/geänderte Nachrichten werden einfach weitergeleitet. Um sicherzustellen, dass das Rendern stoppt, wenn der Bildschirm leer wird, und wieder aufgenommen wird, wenn er wieder leer ist, weisen Sie Choreographer an, den Aufruf des Frame-Draw-Callbacks nicht mehr aufzurufen. onResume() setzt die Rückrufe fort, wenn der Renderer-Thread ausgeführt wird. Wenn Sie die Animation jedoch auf der Grundlage der zwischen den Frames verstrichenen Zeit durchführen, kann es zu einer großen Lücke kommen, bevor das nächste Ereignis eintrifft. Die Verwendung einer expliziten Pause-/Fortsetzungsnachricht kann 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, liegt der Schwerpunkt darauf, wie der Renderer-Thread konfiguriert ist und ob er ausgeführt wird. Ein damit verbundenes Problem besteht darin, den Status aus dem Thread zu extrahieren, wenn die Aktivität beendet wird (in onStop() oder onSaveInstanceState() ); In solchen Fällen funktioniert es am besten, die Lebensdauer des Threads an die Aktivität zu binden, da nach dem Beitritt zum Renderer-Thread auf den Status des gerenderten Threads ohne Synchronisierungsprimitive zugegriffen werden kann.

GLSurfaceView

Die GLSurfaceView- Klasse stellt Hilfsklassen für die Verwaltung von EGL-Kontexten, die Kommunikation zwischen Threads und die Interaktion mit dem Aktivitätslebenszyklus bereit. Sie müssen kein GLSurfaceView verwenden, um GLES zu verwenden.

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

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