为什么SurfaceView的onTouchEvent被叫几秒延迟?

时间:2014-10-15 20:43:24

标签: android surfaceview ontouchevent s5

我有一个非常简单的游戏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
  2. 公共类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

      公共类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
          }
      }   
      

      }

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

1 个答案:

答案 0 :(得分:5)

看起来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中调用,该文件在该文件的其他各个地方调用。

mSurfaceLockReentrantLock,文档说明:

  

此类的构造函数接受可选的fairness参数。   当设置为true时,在争用下,锁有利于授予访问权限   最长等待的线程。否则此锁不保证任何   特定的访问顺序。

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

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