Game Development Reference
In-Depth Information
Next comes the FastRenderView.resume() method. It is responsible for starting up the rendering
thread. Notice that we create a new Thread each time this method is called. This is in line with
what we discussed when we talked about the activity's onResume() and onPause() methods. We
also set the running flag to true . You'll see how that's used in the rendering thread in a bit. The
final piece to take away is that we set the FastRenderView instance itself as the Runnable of the
thread. This will execute the next method of the FastRenderView in that new thread.
The FastRenderView.run() method is the workhorse of our custom View class. Its body is
executed in the rendering thread. As you can see, it's merely composed of a loop that will stop
executing as soon as the running flag is set to false . When that happens, the thread will also be
stopped and will die. Inside the while loop, we first check to ensure that the Surface is valid. If it
is, we lock it, render to it, and unlock it again, as discussed earlier. In this example, we simply fill
the Surface with the color red.
The FastRenderView.pause() method looks a little strange. First we set the running flag to false .
If you look up a little, you will see that the while loop in the FastRenderView.run() method will
eventually terminate due to this, and hence stop the rendering thread. In the next couple of lines,
we simply wait for the thread to die completely, by invoking Thread.join() . This method will
wait for the thread to die, but might throw an InterruptedException before the thread actually
dies. Since we have to make absolutely sure that the thread is dead before we return from that
method, we perform the join in an endless loop until it is successful.
Let's come back to the volatile modifier of the running flag. Why do we need it? The reason
is delicate: the compiler might decide to reorder the statements in the FastRenderView.pause()
method if it recognizes that there are no dependencies between the first line in that method and
the while block. It is allowed to do this if it thinks it will make the code execute faster. However,
we depend on the order of execution that we specified in that method. Imagine if the running
flag were set after we tried to join the thread. We'd go into an endless loop, as the thread would
never terminate.
The volatile modifier prevents this from happening. Any statements where this member is
referenced will be executed in order. This saves us from a nasty Heisenberg—a bug that comes
and goes without the ability to be reproduced consistently.
There's one more thing that you might think will cause this code to explode. What if the surface
is destroyed between the calls to SurfaceHolder.getSurface().isValid() and SurfaceHolder.
lock() ? Well, we are lucky—this can never happen. To understand why, we have to take a step
back and see how the life cycle of the Surface works.
We know that the Surface is created asynchronously. It is likely that our rendering thread will
execute before the Surface is valid. We safeguard against this by not locking the Surface unless
it is valid. That covers the surface creation case.
The reason the rendering thread code does not explode from the Surface being destroyed,
between the validity check and the locking, has to do with the point in time at which the Surface
gets destroyed. The Surface is always destroyed after we return from the activity's onPause()
method. Since we wait for the thread to die in that method via the call to FastRenderView.pause() ,
the rendering thread will no longer be alive when the Surface is actually destroyed. Sexy, isn't it?
But it's also confusing.
We now perform our continuous rendering the correct way. We no longer hog the UI thread,
but instead use a separate rendering thread. We made it respect the activity life cycle as well,
Search WWH ::




Custom Search