【问题标题】:Why onTouchEvent of SurfaceView called a few seconds delayed?为什么SurfaceView的onTouchEvent调用延迟几秒?
【发布时间】:2014-12-11 01:47:15
【问题描述】:

我有一个非常简单的 SurfaceView 游戏,有时游戏会在几秒钟内不响应触摸事件,然后它会立即响应所有这些触摸事件。我已经在 Galaxy S3 和 Nexus 4 上测试了我的游戏,它运行良好,似乎这个问题只发生在 Galaxy S5 上。

  1. 主要活动:

    public class DroidzActivity extends Activity {
    /** Called when the activity is first created. */
    
    private static final String TAG = DroidzActivity.class.getSimpleName();
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // requesting to turn the title OFF
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        // making it full screen
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        // set our MainGamePanel as the View
        setContentView(new MainGamePanel(this));
        Log.d(TAG, "View added");
    }
    
    @Override
    protected void onDestroy() {
        Log.d(TAG, "Destroying...");
        super.onDestroy();
    }
    
    @Override
    protected void onStop() {
        Log.d(TAG, "Stopping...");
        super.onStop();
    }     
    
    }
    
    1. 主游戏面板

公共类 MainGamePanel 扩展 SurfaceView 实现 SurfaceHolder.Callback {

private static final String TAG = MainGamePanel.class.getSimpleName();

private MainThread thread;

public MainGamePanel(Context context) {
    super(context);
    // adding the callback (this) to the surface holder to intercept events
    getHolder().addCallback(this);

    // create the game loop thread
    thread = new MainThread(getHolder(), this);

    // make the GamePanel focusable so it can handle events
    setFocusable(true);
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
        int height) {
}

@Override
public void surfaceCreated(SurfaceHolder holder) {
    // at this point the surface is created and
    // we can safely start the game loop
    thread.setRunning(true);
    thread.start();
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    Log.d(TAG, "Surface is being destroyed");
    // tell the thread to shut down and wait for it to finish
    // this is a clean shutdown
    boolean retry = true;
    while (retry) {
        try {
            thread.setRunning(false);
            thread.join();
            retry = false;
        } catch (InterruptedException e) {
            // try again shutting down the thread
        }
    }
    Log.d(TAG, "Thread was shut down cleanly");
}

public void render(Canvas canvas){
    if(canvas!=null)
        canvas.drawColor(colorList[colorIndex]);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        colorIndex++;
        colorIndex = colorIndex % colorList.length;
    }

    return super.onTouchEvent(event);
}
int [] colorList = {Color.RED, Color.GREEN, Color.BLUE, Color.GRAY};
    int colorIndex = 0;

}
  1. 主线程

    公共类 MainThread 扩展线程 {

    private static final String TAG = MainThread.class.getSimpleName();
    
    // Surface holder that can access the physical surface
    private SurfaceHolder surfaceHolder;
    // The actual view that handles inputs
    // and draws to the surface
    private MainGamePanel gamePanel;
    
    // flag to hold game state 
    private boolean running;
    public void setRunning(boolean running) {
        this.running = running;
    }
    
    public MainThread(SurfaceHolder surfaceHolder, MainGamePanel gamePanel) {
        super();
        this.surfaceHolder = surfaceHolder;
        this.gamePanel = gamePanel;
    }
    
    
    // desired fps
    private final static int    MAX_FPS = 50;   
    // maximum number of frames to be skipped
    private final static int    MAX_FRAME_SKIPS = 5;    
    // the frame period
    private final static int    FRAME_PERIOD = 1000 / MAX_FPS;  
    
    
    @Override
    public void run() {
        Canvas canvas;
        Log.d(TAG, "Starting game loop");
    
        long beginTime;     // the time when the cycle begun
        long timeDiff;      // the time it took for the cycle to execute
        int sleepTime;      // ms to sleep (<0 if we're behind)
        int framesSkipped;  // number of frames being skipped 
    
        sleepTime = 0;
    
        while (running) {
            canvas = null;
            // try locking the canvas for exclusive pixel editing
            // in the surface
            try {
                canvas = this.surfaceHolder.lockCanvas();
                synchronized (surfaceHolder) {
                    beginTime = System.currentTimeMillis();
                    framesSkipped = 0;  // resetting the frames skipped
                    // update game state 
                //  this.gamePanel.update();
                    // render state to the screen
                    // draws the canvas on the panel
                    this.gamePanel.render(canvas);              
                    // calculate how long did the cycle take
                    timeDiff = System.currentTimeMillis() - beginTime;
                    // calculate sleep time
                    sleepTime = (int)(FRAME_PERIOD - timeDiff);
    
                    if (sleepTime > 0) {
                        // if sleepTime > 0 we're OK
                        try {
                            // send the thread to sleep for a short period
                            // very useful for battery saving
                            Thread.sleep(sleepTime);    
                        } catch (InterruptedException e) {}
                    }
    
                    while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) {
                        // we need to catch up
                        // update without rendering
                    //  this.gamePanel.update(); 
                        // add frame period to check if in next frame
                        sleepTime += FRAME_PERIOD;  
                        framesSkipped++;
                    }
                }
            } finally {
                // in case of an exception the surface is not left in 
                // an inconsistent state
                if (canvas != null) {
                    surfaceHolder.unlockCanvasAndPost(canvas);
                }
            }   // end finally
        }
    }   
    

    }

这是该应用程序的最简单版本,我已经尝试过并且能够再次重现相同的问题。在 S5 上加载有时需要 5-10 秒,而在 Nexus 4 和 S3 上加载不到 1 秒。

【问题讨论】:

  • 请输入代码,我们不是强大的预言机 :)
  • 最初的猜测是事件正在到达应用程序,但要么 UI 线程停止,要么处理输入事件的线程(SurfaceView 渲染线程?)无法在及时。

标签: android surfaceview touch-event s5


【解决方案1】:

看起来 MainThread 正在耗尽 UI 线程。

最终执行的代码(删除了很​​多东西)如下所示:

canvas = this.surfaceHolder.lockCanvas();
// Do a ton of stuff
surfaceHolder.unlockCanvasAndPost(canvas);
canvas = this.surfaceHolder.lockCanvas();
// Do a ton of stuff
surfaceHolder.unlockCanvasAndPost(canvas);
canvas = this.surfaceHolder.lockCanvas();
// Do a ton of stuff
surfaceHolder.unlockCanvasAndPost(canvas);

这由 android 源支持。注意SurfaceHolder#lock 调用mSurfaceLock.lock()。这也在SurfaceHolder#updateWindow 中调用,在该文件的许多其他位置调用。

mSurfaceLock 是一个ReentrantLock,文档说明:

这个类的构造函数接受一个可选的公平参数。 当设置为 true 时,在争用情况下,锁倾向于授予对 等待时间最长的线程。否则此锁不保证任何 特定的访问顺序。

SurfaceView 没有指定公平性,所以它应该使用默认值,这可能会导致这种饥饿。

尝试移动您的一些工作,尤其是锁定/解锁调用之外的睡眠。

【讨论】:

  • 这也可能是 S5 运行缓慢的原因。
  • 感谢您的回答,它确实解决了我的神秘问题。顺便说一句,即使是谷歌的 LunarLander 样本也有同样的错误。
猜你喜欢
  • 2022-01-18
  • 2019-11-20
  • 2013-09-13
  • 2019-10-25
  • 1970-01-01
  • 2016-08-18
  • 2012-08-19
  • 2018-04-08
  • 1970-01-01
相关资源
最近更新 更多