I am using a SurfaceView and a rendering thread to develop a game based on structure like LunarLander.
However, I faced many problems, and here I want to point them all out. I wish anyone who want to develop a game won't need to struggle with them anymore. And anyone who have a better idea of the structure can share their experiences, because I am still new to android and is eager to learn :)
[1] The function thread.start()
should not be called more than once.
Many articles mentioned to create a thread when surface is created, in order to render again after activity paused using the method :
public void surfaceCreated(SurfaceHolder holder) {
// start the thread here so that we don't busy-wait in run()
// waiting for the surface to be created
if (thread.getState() == Thread.State.TERMINATED)
{
thread = new LunarThread(getHolder(), getContext(), getHandler());
}
thread.setRunning(true);
thread.start();
}
You can see that if the thread isn't terminated and the function is called, the activity crashes.
[2] If you pressed the "power" or the "red phone" button, or the phone is idle for a few minutes, the activity would be onPause()
state, but the thread is still running. That is a really bad practice, so I need to find out a method to let the thread stop, and start again when onResume()
.
[3] If the screen lock is portrait / landscape, and your game is sticked to the other orientation, the screen lock forced you to "orient" once. That means to start the activity once more. I still can not find a solution to it. (as I mentioned in Android screen orientation bug)
Here are my code to fix those problems:
UIThread
public class UIThread extends Activity
{
private gameView gameview;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
gameview = (gameView) findViewById(R.id.gameview);
}
protected void onPause()
{
super.onPause();
Log.v("MY_ACTIVITY", "onPause");
gameview.terminateThread();
System.gc();
}
protected void onResume()
{
super.onResume();
Log.v("MY_ACTIVITY", "onResume");
if (gameview.surfaceCreated == true)
{
gameview.createThread(gameview.getHolder());
}
}
}
GameView
public class gameView extends SurfaceView implements SurfaceHolder.Callback
{
boolean surfaceCreated;
class gameThread extends Thread{}
public gameView(Context context, AttributeSet attrs)
{
super(context, attrs);context.getResources();
Log.v("MY_ACTIVITY", "gameView");
surfaceCreated = false;
}
public void createThread (SurfaceHolder holder)
{
thread = new gameThread(holder);
thread.run = true;
thread.start();
}
public void terminateThread ()
{
thread.run = false;
try
{
thread.join();
}
catch (InterruptedException e)
{
Log.e("FUNCTION", "terminateThread corrupts");
}
}
public void surfaceCreated(SurfaceHolder holder)
{
Log.v("MY_ACTIVITY", "surfaceCreated");
if (surfaceCreated == false)
{
createThread(holder);
surfaceCreated = true;
}
}
public void surfaceDestroyed(SurfaceHolder holder)
{
Log.v("MY_ACTIVITY", "surfaceDestroyed");
surfaceCreated = false;
}
}
Manifest
<activity android:name=".UIThread"
android:screenOrientation="landscape"
android:configChanges="orientation">
I use onResume()
instead of surfaceCreated()
to new thread. And I set a boolean surfaceCreated
to know that onResume()
comes from the first time application is created, or comes from the "screen off" situation.
So the thread die every time onPause()
is called. Seems to be a good practice. Another way to let the thread stop then resume again is to synchronize an object and call wait / notify. But I don't know it is better or not.
Is there any better way to control the rendering thread?
the solution is here
public void surfaceCreated(SurfaceHolder holder){
if (plot_thread.getState() == Thread.State.TERMINATED) {
plot_thread = new WaveformPlotThread(getHolder(), this);
plot_thread.setRunning(true);
plot_thread.start();
}
else {
plot_thread.setRunning(true);
plot_thread.start();
}
}