如果没有触摸事件发生,Android SurfaceView 会变慢。

3
我正在制作一款游戏,但游戏循环的实现存在问题。我使用了SurfaceView并绘制2D精灵(位图)。目前,游戏中的玩家是一艘飞船,在陨石田中移动。飞船保持在屏幕中央,手机通过倾斜来移动陨石(陨石改变位置而不是玩家)。新的陨石会在旧的陨石从屏幕上消失后生成,营造出无尽的陨石场景感觉。
我在Nexus 5上运行游戏时发现,大约三秒后,沿屏幕下方移动的陨石变得不流畅,尽管我的游戏循环设置为以60fps运行。以下是代码:
@Override
public void run() {
    Canvas canvas;
    long startTime;
    long timeTaken;
    long sleepTime;

    while (running) {
        canvas = null;

        //Update and Draw
        try {
            startTime = System.nanoTime();
            canvas = gameView.getHolder().lockCanvas();

            //Update
            gameModel.update(gameController.getPlayerShots(), gameController.getCurrentTilt());

            //Game Over?
            if (gameModel.isGameOver()) { running = false; }

            //Draw
            synchronized (gameView.getHolder()) { gameModel.drawToCanvas(canvas); }
        }
        finally {
            if (canvas != null) {
                gameView.getHolder().unlockCanvasAndPost(canvas);
            }
        }

        //Sleep if needed
        timeTaken = System.nanoTime() - startTime;
        sleepTime = FRAME_TIME - timeTaken;
        try {
            if (sleepTime > 0) {
                sleep(Math.abs((int)sleepTime/1000000), Math.abs((int)sleepTime % 1000000));
            }

        }
        catch (InterruptedException e) {}
    }

    //Load menu after game over
    if (gameModel.isGameOver()) {
        gameController.launchContinueMenu();
    }
}

我认为问题可能是我的模型在绘制到画布上,而我没有使用SurfaceView的onDraw()方法。重构后使用onDraw(),但结果与之前相同。我打印出每帧的休眠时间,我的线程始终睡眠5-10毫秒(一帧16.667毫秒),所以我的Nexus性能不足。
我在stackoverflow上读到,不应该在视图持有器上使用synchronize()。仍然没有运气。
然后,我再次在stackoverflow上阅读到,使用Thread.sleep()可能会引起问题,因为它并不总是准确的。我进行了如下重大改进。
@SuppressLint("WrongCall")
@Override
public void run() {
    Canvas canvas;

    while (running) {
        canvas = null;

        //Update and Draw
        try {
            canvas = gameView.getHolder().lockCanvas();

            //Calc and smooth time
            long currentTimeMS = SystemClock.uptimeMillis();
            double currentDeltaTimeMS;
            if (lastTimeMS > 0) {
                currentDeltaTimeMS = (currentTimeMS - lastTimeMS);
            }
            else {
                currentDeltaTimeMS = smoothedDeltaTimeMS; // just the first time
            }
            avgDeltaTimeMS = (currentDeltaTimeMS + avgDeltaTimeMS * (avgPeriod - 1)) / avgPeriod;

            // Calc a better aproximation for smooth stepTime
            smoothedDeltaTimeMS = smoothedDeltaTimeMS + (avgDeltaTimeMS - smoothedDeltaTimeMS) * smoothFactor;

            lastTimeMS = currentTimeMS;

            //Update
            gameModel.update(smoothedDeltaTimeMS / 1000.0d, gameController.getCurrentTilt());

            //Game Over?
            if (gameModel.isGameOver()) { running = false; }

            //Draw
//                synchronized (gameView.getHolder()) {
//                   gameModel.drawToCanvas(canvas);
                gameView.onDraw(canvas);
//                }
        }
        //Release canvas
        finally {
            if (canvas != null) {
                gameView.getHolder().unlockCanvasAndPost(canvas);
            }
        }
    }

    //Load menu after game over
    if (gameModel.isGameOver()) {
        gameController.launchContinueMenu();
    }
}

这种新的方法应该尽可能快地使用增量时间来绘制下一帧,而不是像以前那样使用设置的增量。但仍然没有成功。
我将位图位置从矩形改为一个新的位置类,该类使用双精度浮点数代替整数模拟子像素移动。但仍然没有成功。
我尝试过以下几种方法:
  1. 使用onDraw()而不是gameModel drawing到canvas
  2. 不要在view的holder上使用synchronize()
  3. 使用自上次更新以来的时间来更新下一帧,而不是每个update()调用都推进一帧。
  4. 去掉Thread.sleep()
  5. 子像素移动
经过所有这些尝试后,我发现当手机正在充电时游戏运行良好。当我拔下手机后3秒钟,游戏就会变得卡顿。然后我注意到,如果在游戏变得卡顿时点击屏幕,一切都会再次平滑3秒钟。我猜测有一种节省电池的功能影响了我的SurfaceView性能?这是怎么回事,我该如何禁用它?这真是令人发疯...
出于好奇,我再次测试了我的第一个游戏循环(第一个代码块),并在玩游戏时点击屏幕,一切都运行得很顺畅。天哪...为了无用的复杂性增加了这么多工作量。我想这是一个很好的学习经验。有什么办法可以让我的SurfaceView保持全速运行吗?非常感谢你的帮助。 :)

这里没有缺乏研究努力 :p +1 - keyser
2个回答

0

你的观察是正确的。简而言之,N5中的电源管理会积极地降低功率。如果你的手指与屏幕接触,时钟速度会增加以确保交互顺畅。(更多信息请参见我对this question的回答。)

处理这个问题的最佳方法是认识到有时需要丢帧。Grafika中的“Record GL app”活动使用Choreographer的一个简单技巧来实现这一点。此外,this article还提供了一些关于游戏循环的思考。


-1

我认为你应该尝试让你的游戏更省电,可以通过使用背景布局#000来使游戏变暗,从而节省更多的电量。此外,你还可以使用Intent标志,如clear top来杀死前一个活动以节省电池电量,清除RAM(避免内存不足)。

但对于你的情况,你可以使用wakelock


结束之前的活动对此没有影响,并且(除非确定前一个应用程序在后台运行)不会对电池寿命产生影响。唤醒锁定可以防止设备完全进入睡眠状态,但不能防止其降低时钟频率。 - fadden

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接