11.2: The SurfaceView class
Contents:
When you create a custom view and override its onDraw()
method, all drawing happens on the UI thread. Drawing on the UI thread puts an upper limit on how long or complex your drawing operations can be, because your app has to complete all its work for every screen refresh. Each screen refresh usually happens in 16 milliseconds or less, for a typical 60 frames-per-second mobile display. (See the lesson on Performance.)
One solution is to move some of the drawing work to a different thread. One way of doing that is with a SurfaceView
.
A SurfaceView
is a view in your app's view hierarchy that has its own separate Surface
, as shown in the diagram below. This makes it possible to draw to the Surface
from a separate thread.
In the context of the Android framework, Surface
refers to a lower-level drawing surface whose contents are eventually displayed on the user's screen. To create what the user sees on the screen, the Android system processes your app's layouts and drawing instructions to compose one or more Surfaces
, and renders them to the screen. All the views in your view hierarchy are rendered onto one Surface
in the UI thread.
To draw onto a Surface
:
- Start a thread.
- Lock the
SurfaceView
's canvas. - Do your drawing.
- Post it to the
Surface
. You learn more about this below.
The Android system eventually combines all the Surfaces
and renders them to the screen, with the SurfaceView's
Surface
behind the app's Surface
.
To fully understand how SurfaceView
works requires learning Android Graphics Architecture, which is beyond the scope of this course. The Android developer documentation provides an excellent and much more detailed technical overview in the Graphics Architecture documentation.
SurfaceView
is currently not hardware-accelerated. Depending on your app, you may need to consider performance trade-offs. Starting with Android O, you can hardware-accelerate your SurfaceView, which eliminates this specific performance concern.
Working with the SurfaceView class
To create a SurfaceView
and draw to it in a separate thread, do the following. See the Creating a SurfaceView object practical for a detailed example.
- Create a custom view that extends
SurfaceView
and implementsRunnable
. TheRunnable
class includes arun()
method.Implementing therun()
method allows a class to execute on a separate thread. Define you class as follows:public class GameView extends SurfaceView implements Runnable{}
- Use the custom view's constructor to initialize your member variables and obtain a reference to the
SurfaceView
'sSurfaceHolder
. Through the holder you can control theSurface
size and format, edit the pixels in theSurface
, and monitor changes to theSurface
. Most importantly, the holder is persistent even when aSurface
is not available (see next step).mSurfaceHolder = getHolder();
- A
Surface
is only available while theSurfaceView
's window is visible. If yourSurfaceView
is not always visible, implement thesurfaceCreated(SurfaceHolder)
andsurfaceDestroyed(SurfaceHolder)
callback methods. Use these methods to discover when theSurface
is created and destroyed, as the window is shown and hidden.
Now implement the run()
method to do the following:
- Always check whether a valid
Surface
is available.if (mSurfaceHolder.getSurface().isValid()) {
Lock the canvas. If there is more than one thread drawing on this
Surface
, you must put this into atry/catch
block to handle anexception
when you can't acquire the lock. See the SurfaceView documentation for details and limitations.mCanvas = mSurfaceHolder.lockCanvas(); // OR for Android O and later, you can also use lockHardwareCanvas() mCanvas = mSurfaceHolder.lockHardwareCanvas();
- Draw on the canvas.
- Unlock the canvas and post its contents to the
Surface
. Minimize the amount of time the canvas is locked, as there is a performance penalty associated with this.mSurfaceHolder.unlockCanvasAndPost(mCanvas);
Implement pause()
and resume()
to stop and start a new thread. For example:
public void pause() {
mRunning = false;
try {
// Stop the thread (rejoin the main thread)
mGameThread.join();
} catch (InterruptedException e) {
}
}
public void resume() {
mRunning = true;
mGameThread = new Thread(this);
mGameThread.start();
}
Handle user-touches or any other data input that affects drawing.
Be aware of these threading semantics:
- Ensure that the drawing thread only touches the underlying
Surface
while it is valid; that is, betweenSurfaceHolder.Callback.surfaceCreated()
andSurfaceHolder.Callback.surfaceDestroyed()
. - All
SurfaceView
andSurfaceHolder.Callback
methods will be called from the thread running theSurfaceView
's window (typically the main thread of the application). These methods need to correctly synchronize with any state that is also touched by the drawing thread.
SurfaceView
. See the documentation and many publicly available examples if you are interested in learning more about lower-level drawing, game loops, and game development.
Related practical
The related practical documentation is in Creating a SurfaceView object.
Learn more
Android developer docs
SurfaceView
classSurfaceHolder
classClippingBasic
code sample- Graphics architecture article
- Sleeping your app is a bad idea
Choreographer
class- Hardware acceleration
- Grafika collection of Android graphics tips