Android canvas 游戏循环在关闭并重新打开应用程序后中断 onResume()
Android canvas game loop breaks onResume() after closing and reopening app
在全新安装中,我第一次打开该应用程序时,确实没有遇到任何问题。但是,如果我要通过进程终止应用程序、强制停止或简单地按下主页按钮并重新启动应用程序,我 运行 会遇到一些问题...
第一个问题 - 当应用程序在关闭或终止后重新启动时,在实际启动应用程序之前有很长的延迟window。此外,有时显示游戏图形(绘制到 canvas 的内容)最多需要 30 秒。当我听到游戏音效在后台播放时,我只会看到黑屏,就好像 运行ning。
第二个问题 - 在等待应用程序实际显示 canvas 大约 30 秒后,有时触摸侦听器会取消注册或发生其他问题。我无法与游戏互动,就好像我的触摸没有注册一样。再一次,这只会在应用程序成功 运行 一次然后关闭/终止后重新启动时发生。
第三个问题 - 如果我不耐烦并且不等待canvas显示30秒,而是点击主页按钮或杀死"unresponsive" 应用程序,我总是收到一条错误消息,说 "app unresponsive, wait? force close?" 或 "Unexpected error, App has stop running."
所以这些问题让我相信这是我如何开始/重新启动/创建我的游戏循环的问题:
public class MainMenuActivity extends ActionBarActivity implements View.OnTouchListener{
MenuView menu;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
menu = new MenuView(this);
menu.setOnTouchListener(this);
setContentView(R.layout.activity_main_menu);
Button buttonplay = (Button)findViewById(R.id.buttonPlay);
buttonplay.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
launchCanvas();
}
});
}
public void launchCanvas(){
setContentView(menu)
}
@Override
public boolean onTouch(View v, MotionEvent event) {
////do stuff
}
return true;
}
////////////////////////////////////////inner MenuView class
public class MenuView extends SurfaceView implements Runnable {
Thread t = null;
SurfaceHolder holder;
boolean ok;
Game game;
public MenuView(Context context){
super(context);
game = new Game(this);
holder = getHolder();
}
@SuppressLint("WrongCall")
@Override
public void run() {
while(ok){
if(!holder.getSurface().isValid()){
continue;
}
Canvas c = holder.lockCanvas();
onDraw(c);
holder.unlockCanvasAndPost(c);
}
}
public void onDraw(Canvas canvas){
canvas.drawARGB(255, 1, 5, 29);
game.draw(canvas);
}
public void pause(){
ok = false;
while(true){
try{
t.join();
}catch (InterruptedException e){
e.printStackTrace();
}
break;
}
t=null;
}
public void resume(){
ok = true;
t = new Thread(this);
t.start();
}
}
////////////////////////////////////////////////////end MenuView class`
@Override
protected void onPause() {
super.onPause();
menu.pause();
}
@Override
protected void onResume() {
super.onResume();
menu.resume();
}
}
关于如何解决的任何想法?
更新 - 发现潜在问题
我相信我的问题的根源来自于不让我的线程休眠。我在线程循环的每次迭代调用的 draw() 方法中添加了以下 try 块:
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
虽然现在有点波折不平,但是我一直没能重现上面描述的问题
我刚从 10ms 改成 sleep only 1ms,不再断断续续,仍然无法重现问题。 1ms 似乎有点微不足道......为什么线程休眠一小段时间是必要的?
我自己的基于 SurfaceView 的应用程序有一个单独的线程,其中有两个方法 运行:update() 和 draw()。但是,draw() 方法只有 运行s if canvas != null
。这是我与暂停和恢复相关的所有代码,除了 surfaceCreated(SurfaceHolder sh) {...}
,运行 这段代码:
if(!thread.isAlive()) {
thread.setRunning(true);
thread.start();
}
如果这对你没有帮助,我整个框架中的相关部分:
Main.java:
将 MainView 设置为内容视图 (setContentView(mainView)
)
MainView.java:
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
Log.d(TAG, "Surface created");
tf = Typeface.create("Roboto", Typeface.BOLD);
recalc=true;
if(!thread.isAlive()) {
thread.setRunning(true);
thread.start();
}
}
MainThread.java:
public class MainThread extends Thread {
// (abridged) Game logic loop
@Override
public void run() {
Canvas canvas;
Log.d(TAG, "Starting game loop");
while (running) {
canvas = null;
// try locking the canvas for exclusive pixel editing
// in the surface
try {
canvas = this.surfaceHolder.lockCanvas();
synchronized (surfaceHolder) {
// update game state
mainView.update();
// render state to the screen
if (canvas != null) mainView.render(canvas); // canvas == null when activity is in background.
// ... my code then handles sleeping, etc. for fixed fps.
}
} finally {
// in case of an exception the surface is not left in
// an inconsistent state
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
希望这对您有意义!您应该能够根据需要将其适应您自己的项目。
请记住,SurfaceView 有两个部分,Surface 和 View。如果您在 SurfaceView sub-class 中重写 onDraw()
,您将同时执行这两项操作,并且无论您在 View 上绘制什么,都会掩盖您在 Surface 上绘制的内容。 (Surface 是一个独立层,默认情况下位于 View UI 层之后。)
我通常建议不要子 classing SurfaceView,因为这样做没有任何价值,而且它可能会产生怪异的可能性。 SurfaceView Surface 是您在其上绘制的东西,而不是您需要专门化其行为的对象。 (对比一个custom View。)
您可能需要阅读有关 SurfaceView and Activity lifecycle interaction for the pause/resume/thread logic, and game loops for some ideas about how best to structure game rendering. These point to Grafika 的具体示例。
添加睡眠调用会影响竞争条件。如果您的应用没有比赛,则无需睡眠。 (而且,正如游戏循环文章中所述,它们并不是管理帧速率的好方法。)
小点:ok
应该标记为volatile
,否则在run()
.
中允许编译器忽略它
在全新安装中,我第一次打开该应用程序时,确实没有遇到任何问题。但是,如果我要通过进程终止应用程序、强制停止或简单地按下主页按钮并重新启动应用程序,我 运行 会遇到一些问题...
第一个问题 - 当应用程序在关闭或终止后重新启动时,在实际启动应用程序之前有很长的延迟window。此外,有时显示游戏图形(绘制到 canvas 的内容)最多需要 30 秒。当我听到游戏音效在后台播放时,我只会看到黑屏,就好像 运行ning。
第二个问题 - 在等待应用程序实际显示 canvas 大约 30 秒后,有时触摸侦听器会取消注册或发生其他问题。我无法与游戏互动,就好像我的触摸没有注册一样。再一次,这只会在应用程序成功 运行 一次然后关闭/终止后重新启动时发生。
第三个问题 - 如果我不耐烦并且不等待canvas显示30秒,而是点击主页按钮或杀死"unresponsive" 应用程序,我总是收到一条错误消息,说 "app unresponsive, wait? force close?" 或 "Unexpected error, App has stop running."
所以这些问题让我相信这是我如何开始/重新启动/创建我的游戏循环的问题:
public class MainMenuActivity extends ActionBarActivity implements View.OnTouchListener{
MenuView menu;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
menu = new MenuView(this);
menu.setOnTouchListener(this);
setContentView(R.layout.activity_main_menu);
Button buttonplay = (Button)findViewById(R.id.buttonPlay);
buttonplay.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
launchCanvas();
}
});
}
public void launchCanvas(){
setContentView(menu)
}
@Override
public boolean onTouch(View v, MotionEvent event) {
////do stuff
}
return true;
}
////////////////////////////////////////inner MenuView class
public class MenuView extends SurfaceView implements Runnable {
Thread t = null;
SurfaceHolder holder;
boolean ok;
Game game;
public MenuView(Context context){
super(context);
game = new Game(this);
holder = getHolder();
}
@SuppressLint("WrongCall")
@Override
public void run() {
while(ok){
if(!holder.getSurface().isValid()){
continue;
}
Canvas c = holder.lockCanvas();
onDraw(c);
holder.unlockCanvasAndPost(c);
}
}
public void onDraw(Canvas canvas){
canvas.drawARGB(255, 1, 5, 29);
game.draw(canvas);
}
public void pause(){
ok = false;
while(true){
try{
t.join();
}catch (InterruptedException e){
e.printStackTrace();
}
break;
}
t=null;
}
public void resume(){
ok = true;
t = new Thread(this);
t.start();
}
}
////////////////////////////////////////////////////end MenuView class`
@Override
protected void onPause() {
super.onPause();
menu.pause();
}
@Override
protected void onResume() {
super.onResume();
menu.resume();
}
}
关于如何解决的任何想法?
更新 - 发现潜在问题 我相信我的问题的根源来自于不让我的线程休眠。我在线程循环的每次迭代调用的 draw() 方法中添加了以下 try 块:
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
虽然现在有点波折不平,但是我一直没能重现上面描述的问题
我刚从 10ms 改成 sleep only 1ms,不再断断续续,仍然无法重现问题。 1ms 似乎有点微不足道......为什么线程休眠一小段时间是必要的?
我自己的基于 SurfaceView 的应用程序有一个单独的线程,其中有两个方法 运行:update() 和 draw()。但是,draw() 方法只有 运行s if canvas != null
。这是我与暂停和恢复相关的所有代码,除了 surfaceCreated(SurfaceHolder sh) {...}
,运行 这段代码:
if(!thread.isAlive()) {
thread.setRunning(true);
thread.start();
}
如果这对你没有帮助,我整个框架中的相关部分:
Main.java:
将 MainView 设置为内容视图 (setContentView(mainView)
)
MainView.java:
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
Log.d(TAG, "Surface created");
tf = Typeface.create("Roboto", Typeface.BOLD);
recalc=true;
if(!thread.isAlive()) {
thread.setRunning(true);
thread.start();
}
}
MainThread.java:
public class MainThread extends Thread {
// (abridged) Game logic loop
@Override
public void run() {
Canvas canvas;
Log.d(TAG, "Starting game loop");
while (running) {
canvas = null;
// try locking the canvas for exclusive pixel editing
// in the surface
try {
canvas = this.surfaceHolder.lockCanvas();
synchronized (surfaceHolder) {
// update game state
mainView.update();
// render state to the screen
if (canvas != null) mainView.render(canvas); // canvas == null when activity is in background.
// ... my code then handles sleeping, etc. for fixed fps.
}
} finally {
// in case of an exception the surface is not left in
// an inconsistent state
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
希望这对您有意义!您应该能够根据需要将其适应您自己的项目。
请记住,SurfaceView 有两个部分,Surface 和 View。如果您在 SurfaceView sub-class 中重写 onDraw()
,您将同时执行这两项操作,并且无论您在 View 上绘制什么,都会掩盖您在 Surface 上绘制的内容。 (Surface 是一个独立层,默认情况下位于 View UI 层之后。)
我通常建议不要子 classing SurfaceView,因为这样做没有任何价值,而且它可能会产生怪异的可能性。 SurfaceView Surface 是您在其上绘制的东西,而不是您需要专门化其行为的对象。 (对比一个custom View。)
您可能需要阅读有关 SurfaceView and Activity lifecycle interaction for the pause/resume/thread logic, and game loops for some ideas about how best to structure game rendering. These point to Grafika 的具体示例。
添加睡眠调用会影响竞争条件。如果您的应用没有比赛,则无需睡眠。 (而且,正如游戏循环文章中所述,它们并不是管理帧速率的好方法。)
小点:ok
应该标记为volatile
,否则在run()
.