문제

I have a background animation drawn onto a SurfaceView by another thread in my app. The animation seems to work well except when the screen is rotated. Then the main thread will sometimes hang for a couple of seconds. Using DDMS I see that the main thread is calling Object.wait(), I don't understand where or why it's doing that though.

Below is some abbreviated code, if needed the full source can be found on github at https://github.com/GavinDBrown/Amazing.

Main Activity:

public class StartMenu extends Activity {

    /** A handle to the thread that's running the Game Of Life animation. */
    private GOLThread mGOLThread;

    /** A handle to the View in which the background is running. */
    private GOLView mGOLView;

public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
                setContentView(R.layout.game_of_life_background);
        startGOLBackground();
    }
    private void startGOLBackground() {
        // get handles to the GOLView and it's GOLThread
        mGOLView = (GOLView) findViewById(R.id.game_of_life_background);
        mGOLThread = new GOLThread(mGOLView.getHolder());
        mGOLView.setThread(mGOLThread);
        mGOLThread.start();
    }

    private void stopGOLBackground() {
        if (mGOLThread != null) {
            mGOLThread.halt(); // stop the animation if it's valid
            boolean retry = true;
            while (retry) {
                try {
                    mGOLThread.join();
                    retry = false;
                } catch (InterruptedException e) {
                }
            }
            mGOLThread = null;
            mGOLView = null;
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        mGOLThread = mGOLView.getThread();
            mGOLThread.unpause();
    }

    @Override
    public void onPause() {
        super.onPause();
        mGOLThread.pause();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        stopGOLBackground();
    }

}

The SurfaceView:

public class GOLView extends SurfaceView implements SurfaceHolder.Callback {
    /** The thread that actually draws the animation */
    private GOLThread thread;
    SurfaceHolder surfaceHolder;

    public GOLView(Context context, AttributeSet attrs) {
        super(context, attrs);
        SurfaceHolder holder = getHolder();
        holder.addCallback(this);

    }
    @Override
    public void onWindowFocusChanged(boolean hasWindowFocus) {
        if (hasWindowFocus){
            thread.unpause();
        } else {
            thread.pause();
        }

    }
        public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {
        thread.setSurfaceSize(width, height);
    }
        @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        thread.pause();
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        thread.unpause();
    }
}

And finally the Thread:

public class GOLThread extends Thread {

    private GameOfLife gameOfLife;
    private final Object GOLLock = new Object();
        private int mCanvasHeight;
        private int mCanvasWidth;
        private SurfaceHolder mSurfaceHolder;

        public GOLThread(SurfaceHolder surfaceHolder) {
        mSurfaceHolder = surfaceHolder;
    }

        @Override
    public void start() {
        synchronized (mSurfaceHolder) {
            stopped = false;
            mSurfaceHolder.notify();
        }
        super.start();
    }

        public void halt() {
        synchronized (mSurfaceHolder) {
            paused = true;
            stopped = true;
            mSurfaceHolder.notify();
        }
    }

        public void pause() {
        synchronized (mSurfaceHolder) {
            paused = true;
        }
    }

        public void unpause() {
        synchronized (mSurfaceHolder) {
            paused = false;
            mSurfaceHolder.notify();
        }
    }

    public Bundle saveState(Bundle outState) {
        synchronized (GOLLock) {
            if (outState != null) {
                outState.putParcelable(GAME_OF_LIFE_ID, gameOfLife);
            }
        }
        return outState;
    }

    public synchronized void restoreState(Bundle savedState) {
        synchronized (GOLLock) {
            gameOfLife = (GameOfLife) savedState.getParcelable(GAME_OF_LIFE_ID);
        }
    }
    @Override
    public void run() {
        while (!stopped) {
            while (paused && !stopped) {
                try {
                    synchronized (mSurfaceHolder) {
                        mSurfaceHolder.wait(100L);
                    }

                } catch (InterruptedException ignore) {
                }
            }
            // Check if thread was stopped while it was paused.
            if (stopped)
                break;

            beforeTime = System.nanoTime();
            Canvas c = null;
            try {
                c = mSurfaceHolder.lockCanvas();
                synchronized (GOLLock) {
                    if (gameOfLife != null) {
                        gameOfLife.drawAndUpdate(c);
                    } else {
                        pause();
                    }

                }
            } finally {
                if (c != null) {
                    mSurfaceHolder.unlockCanvasAndPost(c);
                }
            }

            sleepTime = FRAME_DELAY
                    - ((System.nanoTime() - beforeTime) / 1000000L);

            try {
                // actual sleep code
                if (sleepTime > 0 && !stopped && !paused) {
                    synchronized (mSurfaceHolder) {
                        mSurfaceHolder.wait(sleepTime);
                    }
                }
            } catch (InterruptedException ex) {

            }
        }
    }

    public void setSurfaceSize(int width, int height) {
        synchronized (GOLLock) {
            if (mCanvasWidth != width || mCanvasHeight != height) {
                mCanvasWidth = width;
                mCanvasHeight = height;
                // reset the GOL
                if (mCanvasWidth > 0 && mCanvasHeight > 0) {
                    gameOfLife = new GameOfLife();
                    gameOfLife.init(mCanvasWidth, mCanvasHeight);
                }
            }
        }
    }
}
도움이 되었습니까?

해결책

The problem is GOLThread is calling SurfaceHolder.lockCanvas() and getting null as a result too often.

From the docs on SurfaceHolder.lockCanvas()

If you call this repeatedly when the Surface is not ready (before Callback.surfaceCreated or after Callback.surfaceDestroyed), your calls will be throttled to a slow rate in order to avoid consuming CPU.

So the OS was throttling calls by putting my threads to sleep.

I fixed it by updating the code in GOLThread.run() to have

    Canvas c = null;
    try {
        c = mSurfaceHolder.lockCanvas();
        if (c == null) {
            // Pause here so that our calls do not get throttled by the
            // OS for calling lockCanvas too often.
            pause();
        } else {
            synchronized (GOLLock) {
                if (gameOfLife != null) {
                    gameOfLife.drawAndUpdate(c);
                } else {
                    pause();
                }
            }
        }
    } 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);
        }
    }
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top