「Conway's Game of Life」の実装のために、Android に基本的なゲーム ループを実装しました。それはかなりうまく機能しますが、時々クラッシュします。ビューが無効になったとき (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);
}
}
}
}