0

「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);
            }
        }
    }
}
4

1 に答える 1

0

操作を実行する前に、キャンバスが Draw() で null かどうかを確認することで問題を解決しました。

すべての提案とコメントをありがとう!!

于 2013-08-01T15:23:25.567 に答える