我在 Android 中为我的“康威生命游戏”实现了一个基本的游戏循环。它工作得很好,但偶尔会崩溃。在我看来,当 View 不再有效时(有时按下 Home 或有时按下 Options),有时会调用 Draw()。
所以我做了一些研究,发现我可能没有正确实现 onPause()/onResume()。我试图纠正这个问题,但它仍然间歇性地崩溃。不是所有的时间,但足够了。
我在这方面工作的时间比我现在愿意承认的要长,我希望比我了解更多的人可以看看它并告诉我我是否做任何明显错误的事情,也许是生命周期问题或某物。
这是我的代码(请注意,为简洁起见,我删除了一些不相关的方法):
// Here is the main Android activity
public class MainActivity extends Activity implements OnSharedPreferenceChangeListener
{
Game gameView;
String mSpeed, mAliveColor, mDeadColor, mBoardSize;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
String deviceId = Secure.getString(getContentResolver(), Secure.ANDROID_ID);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
mSpeed = prefs.getString("sim_speed", "Fast");
int iSpeed = 0;
if(mSpeed.equals("Warp speed"))
iSpeed = 50;
if(mSpeed.equals("Fast"))
iSpeed = 250;
if(mSpeed.equals("Medium"))
iSpeed = 500;
if(mSpeed.equals("Slow"))
iSpeed = 1000;
if(mSpeed.equals("Really slow"))
iSpeed = 2500;
// Create the Game object
gameView = new Game(this, iSpeed);
// register preference change listener
prefs.registerOnSharedPreferenceChangeListener(this);
// and set remembered preferences
String bs = prefs.getString("sim_board_size", "Large");
gameView.setBoardSize(bs);
mAliveColor = prefs.getString("alive_color", "Yellow");
gameView.setColor(mAliveColor, "alive");
mDeadColor = prefs.getString("dead_color", "Blue");
gameView.setColor(mDeadColor, "dead");
setContentView(gameView);
}
@Override
protected void onPause()
{
//gameView.isSimRunning = false;
gameView.thread.onPause();
super.onPause();
}
@Override
protected void onResume()
{
//gameView.isSimRunning = true;
gameView.thread.onResume();
//gameView.initView();
super.onResume();
}
// handle updates to preferences
public void onSharedPreferenceChanged(SharedPreferences prefs, String key)
{
if(key.equals("sim_speed"))
{
mSpeed = prefs.getString("sim_speed", "Fast");
if(mSpeed.equals("Warp speed"))
gameView.setSpeed(50);
if(mSpeed.equals("Fast"))
gameView.setSpeed(250);
if(mSpeed.equals("Medium"))
gameView.setSpeed(500);
if(mSpeed.equals("Slow"))
gameView.setSpeed(1000);
if(mSpeed.equals("Really slow"))
gameView.setSpeed(5000);
}
if(key.equals("sim_board_size"))
{
mBoardSize = prefs.getString("sim_board_size", "Large");
gameView.setBoardSize(mBoardSize);
}
if(key.equals("alive_color"))
{
mAliveColor = prefs.getString("alive_color", "Yellow");
gameView.setColor(mAliveColor, "alive");
}
if(key.equals("dead_color"))
{
mDeadColor = prefs.getString("dead_color", "Blue");
gameView.setColor(mDeadColor, "dead");
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
// Create a menu inflater
MenuInflater inflater = getMenuInflater();
// Generate a Menu from the XML menu resource file
inflater.inflate(R.menu.main_menu, menu);
return true;
}
}
// Here is the SurfaceView class
public class Game extends SurfaceView implements SurfaceHolder.Callback
{
long lastUpdate = 0;
long sleepTime=0;
public int num_cols = 51;
public int max_cols = 51;
public int num_rows = 81;
public int max_rows = 81;
public long gmDelay = 0;
private int grid_cell_size = 9;
public boolean [][] current_life = new boolean [max_cols][max_rows];
private boolean [][] successor_gen = new boolean [max_cols][max_rows];
public boolean isSimRunning = false;
public boolean isThreadStarted = false;
Paint dead_paint = new Paint();
Paint alive_paint = new Paint();
Paint background = new Paint();
private GameThread thread;
SurfaceHolder surfaceHolder;
Context context;
public Game(Context context, int dly)
{
super(context);
dead_paint.setStrokeWidth(0);
dead_paint.setColor(Color.BLUE);
alive_paint.setStrokeWidth(0);
alive_paint.setColor(Color.YELLOW);
background.setStrokeWidth(0);
background.setColor(Color.BLACK);
gmDelay = dly;
initView();
initLifeArray();
}
public void setSpeed(long s)
{
gmDelay = s;
initView();
thread.delay = s;
}
public void setBoardSize(String s)
{
thread.state = 2;
isSimRunning = false;
if(s.equals("Small"))
num_cols = 10;
else if(s.equals("Medium"))
num_cols = 25;
else if(s.equals("Large"))
num_cols = 51;
thread.state = 1;
isSimRunning = true;
initView();
}
public void setColor(String color, String type)
{
int c = 0;
// violet, white and orange
if(color.equals("Black"))
c = Color.BLACK;
else if(color.equals("Blue"))
c = Color.BLUE;
else if(color.equals("Green"))
c = Color.GREEN;
else if(color.equals("Purple"))
c = Color.rgb(109, 6, 108);
else if(color.equals("Orange"))
c = Color.rgb(255, 157, 30);
else if(color.equals("Red"))
c = Color.RED;
else if(color.equals("Yellow"))
c = Color.YELLOW;
else if(color.equals("White"))
c = Color.WHITE;
else
c = Color.YELLOW;
if(type.equals("alive"))
alive_paint.setColor(c);
else
dead_paint.setColor(c);
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
if(event != null)
{
int x = (int) event.getX()/grid_cell_size;
int y = (int) event.getY()/grid_cell_size;
int max_x = current_life.length;
if(x < max_x)
{
int max_y = current_life[x].length;
if(y < max_y)
current_life[x][y] = true;
}
return true;
}
return super.onTouchEvent(event);
}
void initView()
{
// Initialize our screen holder
SurfaceHolder holder = getHolder();
holder.addCallback(this);
// Initialize our Thread class. A call will be made to start it later
thread = new GameThread(holder, context, new Handler(), this);
setFocusable(true);
}
public void Draw(Canvas canvas)
{
int x = canvas.getWidth();
int y = canvas.getHeight();
canvas.drawRect(0, 0, x, y, background);
grid_cell_size = (int) Math.ceil((double) (x / num_cols) * 1.0);
int gap = grid_cell_size - 1;
for(int col = 0; col < num_cols; col++)
{
for(int row = 0; row < num_rows; row++)
{
if(current_life[col][row])
canvas.drawRect(col*grid_cell_size, row*grid_cell_size, col*grid_cell_size+gap, row*grid_cell_size+gap,
alive_paint);
else
canvas.drawRect(col*grid_cell_size, row*grid_cell_size, col*grid_cell_size+gap, row*grid_cell_size+gap,
dead_paint);
}
}
}
// These methods are overridden from the SurfaceView super class. They are automatically called
// when a SurfaceView is created, resumed or suspended.
@Override
public void surfaceChanged(SurfaceHolder arg0, int format, int width, int height)
{
}
@Override
public void surfaceDestroyed(SurfaceHolder arg0)
{
}
@Override
public void surfaceCreated(SurfaceHolder arg0)
{
if (!isThreadStarted) {
thread.start();
isThreadStarted = true;
}
thread.onResume();
}
}
// Finally, we have the thread class
public class GameThread extends Thread
{
// flag to hold game state
// private static final String TAG = GameThread.class.getSimpleName();
private Game game;
private SurfaceHolder mSurfaceHolder;
//for consistent rendering
private long sleepTime;
public long delay=250;
//state of game (Running or Paused).
int state = 1;
public final static int RUNNING = 1;
public final static int PAUSED = 2;
private Object mPauseLock = new Object();
private boolean mPaused = false;
public GameThread(SurfaceHolder surfaceHolder, Context context, Handler handler, Game g)
{
super();
//data about the screen
mSurfaceHolder = surfaceHolder;
delay = g.gmDelay;
this.game = g;
}
public void onPause()
{
state = 2;
synchronized (mPauseLock) {
mPaused = true;
}
}
public void onResume()
{
state = 1;
synchronized (mPauseLock) {
mPaused = false;
mPauseLock.notifyAll();
}
}
@Override
public void run()
{
while (state == RUNNING && ! mPaused)
{
delay = this.game.gmDelay;
//time before update
long beforeTime = System.nanoTime();
// Update the simulation one generation
game.createNextGeneration();
Canvas c = null;
try
{
//lock canvas so nothing else can use it
c = mSurfaceHolder.lockCanvas(null);
synchronized(mSurfaceHolder)
{
//if(game.isSimRunning)
game.Draw(c);
}
}
finally
{
// do this in a finally so that if an exception is thrown
// during the above, we don't leave the Surface in an inconsistent state
if (c != null)
{
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
synchronized (mPauseLock) {
while (mPaused) {
try {
mPauseLock.wait();
} catch (InterruptedException e) {
}
}
}
// Sleep time. Time required to sleep to keep game consistent
// This starts with the specified delay time (in milliseconds) then subtracts from that the actual
// time it took to update and render the game. This allows the simulation to render smoothly.
this.sleepTime = delay-((System.nanoTime()-beforeTime)/1000000L);
try
{
//actual sleep code
if(sleepTime>0)
{
Thread.sleep(sleepTime);
}
}
catch (InterruptedException ex)
{
Logger.getLogger(GameThread.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}