Putting It All Together

So how do we integrate all this with a separate rendering thread, as well as with the activity life cycle? The best way to figure this out is to look at some actual code. Listing 4-16 shows you a complete example that performs the rendering in a separate thread on a SurfaceView.

Listing 4-16. The SurfaceViewTest Activity package com.badlogic.androidgames; import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.os.Bundle; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.Window; import android.view.WindowManager;

public class SurfaceViewTest extends Activity { FastRenderView renderView;

public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(windowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); renderView = new FastRenderView(this); setContentView(renderView);

protected void onResume() { super.onResume(); renderView.resume();

protected void onPause() { super.onPause(); renderView.pause();

class FastRenderView extends SurfaceView implements Runnable { Thread renderThread = null; SurfaceHolder holder; volatile boolean running = false;

public FastRenderView(Context context) { super(context); holder = getHolder();

public void resume() { running = true;

renderThread = new Thread(this); renderThread.start();

if(!holder.getSurface().isValid()) continue;

Canvas canvas = holder.lockCanvas(); canvas.drawRGB(255, 0, 0); holder.unlockCanvasAndPost(canvas);

public void pause() { running = false; while(true) { try {

renderThread.join(); } catch (InterruptedException e) { // retry

This doesn't look all that intimidating, does it? Our activity holds a FastRenderView instance as a member. This is a custom SurfaceView subclass that will handle all the thread business and surface locking for us. To the activity, it looks like a plain-old View.

In the onCreate() method we enable full-screen mode, create the FastRenderView instance, and set it as the content view of the activity.

We also override the onResume() method this time. In this method we will start our rendering thread indirectly by calling the FastRenderView.resume() method, which does all the magic internally. This means that the thread will get started when the activity is initially created (because onCreate() is always followed by a call to onResume()). It will also get restarted when the activity is resumed from a paused state.

This of course implies that we have to stop the thread somewhere; otherwise we'd create a new thread every time onResume() was called. That's where onPause() comes in. It calls the FastRenderView.pause() method, which will completely stop the thread. The method will not return before the thread is completely stopped.

So let's look at the core class of this example: FastRenderView. It's similar to the RenderView classes we implemented in the last couple of examples in that it derives from another View class. In this case we directly derive it from the SurfaceView class. It also implements the Runnable interface so that we can pass it to the rendering thread in order for it to run the render thread logic.

The FastRenderView class has three members. The renderThread member is simply a reference to the Thread instance that will be responsible for executing our rendering thread logic. The holder member is a reference to the SurfaceHolder instance we get from the SurfaceView superclass we derive from. Finally, the running member is a simple boolean flag we will use to signal the rendering thread that it should stop execution. The volatile modifier has a special meaning we'll get to in a minute.

All we do in the constructor is call the superclass constructor and store the reference to the SurfaceHolder in the holder member.

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. If that happens, the thread will also be stopped and die. Inside the while loop, we first check whether the Surface is valid, and 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 completely die 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 heisenbug, a bug that comes and goes without the ability to be consistently reproduced.

There's one more thing you might think will make this code 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. So 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 with the point in time the Surface gets destroyed. The Surface is always destroyed after we return from the activity's onPause() method. And since we wait for the thread to die in that method via the call to FastRenderView.pause(), the rendering thread will not be alive anymore when the Surface is actually destroyed. Sexy, isn't it? But it's also confusing.

We now perform our continuous rendering the right way. We do not hog the UI thread anymore, but use a separate rendering thread instead. We made it respect the activity life cycle as well, so that it does not run in the background, eating the battery while the activity is paused. The whole world is a happy place again. Of course, we'll need to synchronize the processing of input events in the UI thread with our rendering thread. But that will turn out to be really easy, which you'll see in the next chapter when we implement our game framework based on all the information you digested in this chapter.

0 0

Post a comment