The Android app framework UI is based on a hierarchy of objects that start with a View. All UI elements go through a series of measurements and a layout process that fits them into a rectangular area. Then, all visible view objects are rendered to a surface that was set up by the WindowManager when the app was brought to the foreground. The app's UI thread performs layout and rendering to a buffer per frame.
SurfaceView
A SurfaceView is a component that you can use to embed an additional composite layer within your view hierarchy. A SurfaceView takes the same layout parameters as other views, so it can be manipulated like any other view, but the SurfaceView's contents are transparent.
When you render with an external buffer source, such as GL context or a media decoder, you need to copy buffers from the buffer source to display the buffers on the screen. Using a SurfaceView enables you to do that.
When the SurfaceView's view component is about to become visible, the framework asks SurfaceControl to request a new surface from SurfaceFlinger. To receive callbacks when the surface is created or destroyed, use the SurfaceHolder interface. By default, the newly created surface is placed behind the app UI surface. You can override the default Z-ordering to put the new surface on top.
Rendering with SurfaceView is beneficial in cases where you need to render to a separate surface, such as when you render with the Camera API or an OpenGL ES context. When you render with SurfaceView, SurfaceFlinger directly composes buffers to the screen. Without a SurfaceView, you need to composite buffers to an offscreen surface, which then gets composited to the screen, so rendering with SurfaceView eliminates extra work. After rendering with SurfaceView, use the UI thread to coordinate with the activity lifecycle and make adjustments to the size or position of the view if needed. Then, the Hardware Composer blends the app UI and the other layers.
The new surface is the producer side of a BufferQueue, whose consumer is a SurfaceFlinger layer. You can update the surface with any mechanism that can feed a BufferQueue, such as surface-supplied Canvas functions, attaching an EGLSurface and drawing on the surface with GLES, or configuring a media decoder to write the surface.
SurfaceView and the activity lifecycle
When using a SurfaceView, render the surface from a thread other than the main UI thread.
For an activity with a SurfaceView, there are two separate but interdependent state machines:
- App
onCreate
/onResume
/onPause
- Surface created/changed/destroyed
When the activity starts, you get callbacks in this order:
onCreate()
onResume()
surfaceCreated()
surfaceChanged()
If you click back, you get:
onPause()
surfaceDestroyed()
(called just before the surface goes away)
If you rotate the screen, the activity is torn down and recreated and you
get the full cycle. You can tell it's a quick restart by checking
isFinishing()
. It's possible to start/stop an activity so
quickly that surfaceCreated()
happens after
onPause()
.
If you tap the power button to blank the screen, you get only
onPause()
without surfaceDestroyed()
. The surface
remains active, and rendering can continue. You can keep getting
Choreographer events if you continue to request them. If you have a lock
screen that forces a different orientation, your activity may be restarted when
the device is unblanked. Otherwise, you can come out of screen-blank with the
same surface as before.
The lifespan of the thread can be tied to the surface or to the activity, depending on what you want to happen when the screen goes blank. The thread can start/stop either on Activity start/stop or on surface create/destroy.
Having the thread start/stop on Activity start/stop works well with the app
lifecycle. You start the renderer thread
in onResume()
and stop it in onStop()
.
When creating and configuring the thread, sometimes the surface
already exists, othertimes it doesn't (for example, it's still active after toggling
the screen with the power button). You have to wait for the surface to be created
before initializing in the thread. You can't initialize in the
surfaceCreate()
callback because it won't fire again if the surface
wasn't recreated. Instead, query or cache the surface
state, and forward it to the renderer thread.
Having the thread start/stop on surface create/destroy works well because
the surface and the renderer are logically
intertwined. You start the thread after the surface is created, which
avoids some interthread communication concerns; and surface created/changed
messages are simply forwarded. To ensure that rendering stops when the screen
goes blank and resumes when it un-blanks, tell Choreographer to stop invoking
the frame draw callback. onResume()
resumes the callbacks if the
renderer thread is running. However, if you animate
based on elapsed time between frames, there could be a large gap before the
next event arrives; using an explicit pause/resume message can solve this issue.
Both options, whether the lifespan of the thread is tied to the Activity
or the surface, focus on how the renderer thread is
configured and whether it's executing. A related concern is extracting state
from the thread when the activity is killed (in onStop()
or
onSaveInstanceState()
); in such cases, tying the lifespan of the
thread to the activity works best because
after the renderer thread has been joined, the rendered thread's state can be
accessed without synchronization primitives.
GLSurfaceView
The GLSurfaceView class provides helper classes for managing EGL contexts, interthread communication, and interaction with the activity lifecycle. You don't need to use a GLSurfaceView to use GLES.
For example, GLSurfaceView creates a thread for rendering and configures an EGL context there. The state is cleaned up automatically when the activity pauses. Most apps don't need to know anything about EGL to use GLES with GLSurfaceView.
In most cases, GLSurfaceView can make working with GLES easier. In some situations, it can get in the way.