0

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

1 回答 1

0

我通过在执行任何操作之前检查画布在 Draw() 中是否为空来解决了这个问题。

谢谢大家的建议和意见!!

于 2013-08-01T15:23:25.567 回答