Die Benutzeroberfläche des Android-App-Frameworks basiert auf einer Hierarchie von Objekten, die mit einer View beginnt. Alle UI-Elemente durchlaufen eine Reihe von Messungen und einen Layoutprozess, bei dem sie in einen rechteckigen Bereich passen. Anschließend rendert das Framework alle sichtbaren Ansichtsobjekte auf einer Oberfläche, die vom WindowManager eingerichtet wurde, als die App in den Vordergrund gebracht wurde. Der UI-Thread der App führt das Layout und das Rendern in einen Puffer 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 bearbeitet werden. Der Inhalt der SurfaceView ist jedoch transparent.
Wenn Sie mit einer externen Pufferquelle rendern, z. B. einem GL-Kontext oder einem Media-Decoder, müssen Sie Puffer aus der Pufferquelle kopieren, um sie auf dem Bildschirm anzuzeigen. Mit einer SurfaceView ist das möglich.
Wenn die Ansichtskomponente von SurfaceView sichtbar wird, fordert das Framework SurfaceControl auf, 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 Ebene hinter der App-UI-Ebene platziert. Sie können die Standard-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 Camera API oder einem OpenGL ES-Kontext rendern. Wenn Sie mit SurfaceView rendern, stellt SurfaceFlinger Puffer direkt auf dem Bildschirm dar. Ohne SurfaceView müssen Sie Puffer auf einer Offscreen-Oberfläche zusammenfügen, die dann auf dem Bildschirm zusammengefügt wird. Das Rendern mit SurfaceView spart also zusätzliche Arbeit. Nach dem Rendern mit SurfaceView müssen Sie den UI-Thread verwenden, um den Aktivitätslebenszyklus zu koordinieren und bei Bedarf die Größe oder Position der Ansicht anzupassen. Anschließend führt der Hardware Composer die App-Benutzeroberfläche und die anderen Ebenen zusammen.
Die neue Oberfläche ist die Erstellerseite einer BufferQueue, deren Consumer eine SurfaceFlinger-Ebene ist. Sie können die Oberfläche mit jedem Mechanismus aktualisieren, der eine BufferQueue speisen kann, z. B. mit von der Oberfläche bereitgestellten Canvas-Funktionen, durch Anhängen einer EGLSurface und Zeichnen auf der Oberfläche mit GLES oder durch Konfigurieren eines Media-Decoders zum Schreiben auf die Oberfläche.
SurfaceView und der Aktivitätslebenszyklus
Wenn Sie eine SurfaceView verwenden, rendern Sie die Oberfläche über einen anderen Thread als den Haupt-UI-Thread.
Für eine Aktivität mit einer SurfaceView gibt es zwei separate, aber voneinander abhängige Zustandsautomaten:
- App
onCreate
/onResume
/onPause
- Oberfläche erstellt/geändert/gelöscht
Wenn die Aktivität gestartet wird, erhalten Sie Callbacks in dieser Reihenfolge:
onCreate()
onResume()
surfaceCreated()
surfaceChanged()
Wenn Sie auf „Zurück“ klicken, erhalten Sie Folgendes:
onPause()
surfaceDestroyed()
(wird kurz vor dem Verschwinden der Oberfläche aufgerufen)
Wenn Sie das Display drehen, wird die Aktivität beendet und neu erstellt. Ob es sich um einen Schnellstart handelt, erkennen Sie an isFinishing()
. Es ist möglich, eine Aktivität so schnell zu starten/beenden, dass surfaceCreated()
nach onPause()
erfolgt.
Wenn Sie auf die Ein/Aus-Taste tippen, um das Display zu deaktivieren, wird nur onPause()
ohne surfaceDestroyed()
angezeigt. 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 entsperrt wird. Andernfalls können Sie den Bildschirm mit derselben Oberfläche wie zuvor wieder aktivieren.
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/Beenden einer Aktivität oder beim Erstellen/Zerstören einer Oberfläche gestartet/beendet werden.
Wenn der Thread mit dem Starten/Beenden der Aktivität gestartet/beendet 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 nach dem Ein- und Ausschalten des Displays mit der Ein/Aus-Taste noch aktiv). 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 ausführen, da dieser nicht noch einmal ausgelöst wird, wenn die Oberfläche nicht neu erstellt wurde. Fragen Sie stattdessen den Oberflächenstatus ab oder speichern Sie ihn im Cache und leiten Sie ihn an den Renderer-Thread weiter.
Der Thread wird beim Erstellen/Zerstören der Oberfläche gestartet/beendet, da die Oberfläche und der Renderer logisch miteinander verbunden sind. Sie starten den Thread, nachdem die Oberfläche erstellt wurde. Dadurch werden einige Probleme mit der Thread-übergreifenden Kommunikation vermieden. Außerdem werden Nachrichten, die sich auf die Erstellung oder Änderung der Oberfläche beziehen, weitergeleitet. Um zu prüfen, ob das Rendern beendet wird, wenn der Bildschirm leer wird, und wieder aufgenommen wird, wenn er wieder angezeigt wird, weisen Sie Choreographer an, den Frame-Draw-Callback nicht mehr aufzurufen. onResume()
setzt die Callbacks fort, wenn der Renderer-Thread ausgeführt wird. Wenn Sie die Animation jedoch auf der Grundlage der verstrichenen Zeit zwischen Frames animieren, kann es zu einer großen Lücke kommen, bevor das nächste Ereignis eintrifft. Mit einer expliziten Pause-/Fortsetzen-Nachricht lässt sich dieses Problem beheben.
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 Synchronisierungspunkte zugegriffen werden kann.
GLSurfaceView
Die Klasse GLSurfaceView bietet Hilfsklassen für die Verwaltung von EGL-Kontexten, die Interthread-Kommunikation und die Interaktion mit dem Aktivitätslebenszyklus. Sie müssen keine GLSurfaceView verwenden, um GLES zu nutzen.
GLSurfaceView erstellt beispielsweise einen Thread für das Rendern 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 das störend sein.