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

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

>主要活动:

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

}

> MainGamePanel

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

}

> MainThread

公共类MainThread扩展Thread {

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

}

这是应用程序最简单的版本,我已经尝试过,我可以再次重现同样的问题.它还有时需要5到10秒才能在S5上加载,而在Nexus 4和S3上加载的时间不到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,文档说明:

The constructor for this class accepts an optional fairness parameter.
When set true, under contention, locks favor granting access to the
longest-waiting thread. Otherwise this lock does not guarantee any
particular access order.

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

尝试移动您的一些工作,特别是锁定/解锁呼叫之外的睡眠.

上一篇:RAFT选举算法-分布式数据库困惑


下一篇:【自然语言处理(一)】--相关基础技能