旋转SurfaceView动画时MainThread挂起

时间:2014-02-02 20:01:26

标签: android multithreading locking surfaceview android-lifecycle

我的应用程序中的另一个线程将一个背景动画绘制到SurfaceView上。除了旋转屏幕外,动画似乎运行良好。然后主线程有时会挂起几秒钟。使用DDMS我看到主线程正在调用Object.wait(),我不明白它在哪里或为什么这样做。

下面是一些缩写代码,如果需要,可以在https://github.com/GavinDBrown/Amazing的github上找到完整的源代码。

主要活动:

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();
    }

}

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();
    }
}

最后是线程:

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

1 个答案:

答案 0 :(得分:0)

问题是GOLThread正在调用SurfaceHolder.lockCanvas()并且过于频繁地获取null。

来自SurfaceHolder.lockCanvas()

上的文档
  

如果在Surface未准备好时(之前)重复调用此方法   Callback.surfaceCreated或Callback.surfaceDestroyed之后),你的   呼叫将被限制为慢速以避免消耗   CPU。

因此操作系统通过让我的线程进入睡眠来限制调用。

我通过更新GOLThread.run()中的代码来修复它

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