我有一个非常简单的游戏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没有指定公平性,因此它应该使用默认值,这可能导致这种饥饿.
尝试移动您的一些工作,特别是锁定/解锁呼叫之外的睡眠.