SurfaceView und GLSurfaceView

Mit Sammlungen den Überblick behalten Sie können Inhalte basierend auf Ihren Einstellungen speichern und kategorisieren.

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. Dann 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

Eine SurfaceView ist eine Komponente, die Sie verwenden können, um eine zusätzliche zusammengesetzte Ebene in Ihre Ansichtshierarchie einzubetten. Eine SurfaceView verwendet die gleichen Layoutparameter wie andere Ansichten, sodass sie wie jede andere Ansicht bearbeitet werden kann, aber der Inhalt der SurfaceView ist transparent.

Wenn Sie mit einer externen Pufferquelle wie GL-Kontext oder einem Mediendecoder rendern, 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. Verwenden Sie die SurfaceHolder- Schnittstelle, um Rückrufe zu erhalten, wenn die Oberfläche erstellt oder zerstört wird. 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, z. B. wenn Sie mit der Kamera-API oder einem OpenGL ES-Kontext rendern. Wenn Sie mit SurfaceView rendern, setzt SurfaceFlinger Puffer direkt auf dem Bildschirm zusammen. Ohne SurfaceView müssen Sie Puffer auf einer Offscreen-Oberfläche zusammensetzen, die dann auf dem Bildschirm zusammengesetzt wird, sodass beim Rendern mit SurfaceView zusätzliche Arbeit entfällt. Verwenden Sie nach dem Rendern mit SurfaceView den UI-Thread, um sich mit dem Aktivitätslebenszyklus zu koordinieren und bei Bedarf Anpassungen an der Größe oder Position der Ansicht vorzunehmen. Anschließend fügt der Hardware Composer die App-Benutzeroberfläche und die anderen Ebenen zusammen.

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 füttern kann, wie z. B. von der Oberfläche bereitgestellte Canvas-Funktionen, das Anfügen 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

Rendern Sie bei Verwendung einer SurfaceView 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ückklicken, erhalten Sie:

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

Wenn Sie den Bildschirm drehen, wird die Aktivität abgerissen und neu erstellt und Sie erhalten den vollständigen Zyklus. Sie können feststellen, dass es sich um einen schnellen Neustart handelt, indem 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 zu leeren, erhalten Sie nur onPause() ohne surfaceDestroyed() . Die Oberfläche bleibt aktiv und das Rendern kann fortgesetzt werden. Sie können weiterhin Choreographer-Ereignisse erhalten, wenn Sie sie 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 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 werden, 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 starten/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. nach dem Umschalten des Bildschirms mit dem Netzschalter noch aktiv). Sie müssen warten, bis die Oberfläche erstellt wurde, bevor Sie im Thread initialisieren. Sie können im surfaceCreate() Callback 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 cachen 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 verflochten sind. Sie starten den Thread, nachdem die Oberfläche erstellt wurde, wodurch einige Probleme bei 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 fortgesetzt wird, wenn er wieder leer ist, weisen Sie Choreographer an, den Aufruf des Frame-Draw-Callbacks zu beenden. onResume() setzt die Rückrufe fort, wenn der Renderer-Thread läuft. Wenn Sie jedoch basierend auf der verstrichenen Zeit zwischen Frames animieren, kann es eine große Lücke geben, bevor das nächste Ereignis eintrifft. Die Verwendung einer expliziten Pause/Fortsetzen-Nachricht kann dieses Problem lösen.

Beide Optionen, unabhängig davon, ob die Lebensdauer des Threads an die Aktivität oder die Oberfläche gebunden ist, konzentrieren sich darauf, wie der Renderer-Thread konfiguriert ist und ob er ausgeführt wird. Ein verwandtes Problem ist das Extrahieren des Status aus dem Thread, 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 Zustand des gerenderten Threads ohne Synchronisierungsprimitive zugegriffen werden kann.

GLSurfaceView

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

Beispielsweise erstellt GLSurfaceView einen Thread zum Rendern und konfiguriert dort einen EGL-Kontext. Der Status wird automatisch bereinigt, wenn die Aktivität angehalten 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 im Weg stehen.