使用 xmlrelativelayout 中的自定义 SurfaceView 第二次运行 Android 应用程序时出现问题
我是 stackoverflow 和 Android 开发的新手。似乎每次我使用 Google 搜索 Android 问题的答案时,我都会被引导到这里。
我有一个简单的应用程序,以欢迎屏幕开头。当用户按下“播放”按钮时,将调用以下方法:
public void onClickPlay(View v)
{
finish();
Intent intent = new Intent();
intent.setClassName("ca.mytest.player", "ca.mytest.player.GameActivity");
startActivity(intent);
}
在 GameActivity 类(扩展了 Activity)中,我使用:
setContentView(R.layout.game)
并且此布局如下所示:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ca.mytest.player.GamePanel
android:id="@+id/gamePanel"
android:layout_width="fill_parent"
android:layout_height="768px"/>
<Button
android:id="@+id/quitButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:onClick="onClickQuit"
android:layout_alignParentBottom="true"
android:text="Quit" />
<Button
android:id="@+id/decoyButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/quitButton"
android:onClick="onClickDecoy"
android:text="Decoy" />
<Button
android:id="@+id/hideButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/decoyButton"
android:onClick="onClickHide"
android:text="Hide" />
<Button
android:id="@+id/shootButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_above="@id/hideButton"
android:onClick="onClickShoot"
android:text="Assassinate" />
<TextView
android:id="@+id/status"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Test of text view"
android:layout_above="@id/shootButton"
/>
</RelativeLayout>
我选择了这种方法(带有 xml 布局的自定义 SurfaceView 以简化游戏的交互部分) 我遇到的问题
是,虽然这个实现在我第一次运行应用程序时工作正常,但当用户通过按“退出”按钮(我在其中调用 finish())或按设备上的“后退”按钮退出时,我无法重新运行该应用程序。再次按应用程序图标将打开一个带有标题栏的空白应用程序,按此屏幕上的“后退”按钮没有任何效果(我需要按“主页”按钮退出,
如果我使用:
setContentView(new GamePanel(this))
而不是:
setContentView(R.layout.game)
即我只是使用 )。带有 onTouch() 事件的自定义 SurfaceView 来调用 finish(),然后应用程序将正常运行(可以根据需要随时停止和重新启动)。
类的代码如下:
package ca.mytest.player;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.WindowManager;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
public class GamePanel extends SurfaceView implements SurfaceHolder.Callback
{
private static final String TAG = GamePanel.class.getSimpleName();
//private Context context;
private GameThread thread;
private final float dotSize = 20.0f;
private final int numberOfButtons = 4;
private ActionButton[] actionButtons;
private WindowManager windowManager;
public GamePanel(Context context)
{
super(context);
init(context);
}
public GamePanel(Context context, AttributeSet attrs)
{
super(context, attrs);
init(context);
}
public GamePanel(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
init(context);
}
private void init(Context context)
{
Log.d(TAG, "*********************************");
Log.d(TAG, "Creating Game Panel");
Log.d(TAG, "*********************************");
// add callback (this) to surface holder to intercept events
getHolder().addCallback(this);
// make the GamePanel focusable so it can handle events
setFocusable(true);
windowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
actionButtons = new ActionButton[numberOfButtons];
thread = new GameThread(getHolder(), this);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
}
@Override
public void surfaceCreated(SurfaceHolder holder)
{
thread.setRunning(true);
thread.start();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder)
{
boolean retry = true;
while(retry)
{
try
{
thread.join();
retry = false;
}
catch (InterruptedException e)
{
Log.d(TAG, "surface destroyed");
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
if (event.getAction() == MotionEvent.ACTION_DOWN)
{
if (event.getY() > getHeight() - 50)
{
thread.setRunning(false);
((Activity)getContext()).finish();
}
else
{
Log.d(TAG, "Coords: x = " + event.getX() + " ,y = " + event.getY());
}
}
return super.onTouchEvent(event);
}
@Override
protected void onDraw(Canvas canvas)
{
canvas.drawColor(Color.BLACK);
drawGameCircle(canvas);
}
private void drawGameCircle(Canvas canvas)
{
// Find centre of game circle
// Game circle fits in a square at far right, full height of display
float x = (float)(getWidth()/2);
float y = (float)(getWidth()/2);
float radius = (float)(getWidth()/2);
Paint paint = new Paint();
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(x, y, radius, paint);
// Draw dot for player
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(x, y, dotSize, paint);
// draw a horizontal line to show game area
canvas.drawLine(0, 767, 600, 767, paint);
}
}
GamePanel 任何帮助都会很大吗?赞赏。谢谢。
I'm new to stackoverflow and to Android development. It seems that every time I use Google to search of an answer to an Android question, I'm directed here.
I have as simple app that starts with a welcome screen. When the user presses the Play button, the following method is called:
public void onClickPlay(View v)
{
finish();
Intent intent = new Intent();
intent.setClassName("ca.mytest.player", "ca.mytest.player.GameActivity");
startActivity(intent);
}
In the class GameActivity (which extends Activity), I use:
setContentView(R.layout.game)
and this layout looks as follows:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ca.mytest.player.GamePanel
android:id="@+id/gamePanel"
android:layout_width="fill_parent"
android:layout_height="768px"/>
<Button
android:id="@+id/quitButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:onClick="onClickQuit"
android:layout_alignParentBottom="true"
android:text="Quit" />
<Button
android:id="@+id/decoyButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/quitButton"
android:onClick="onClickDecoy"
android:text="Decoy" />
<Button
android:id="@+id/hideButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/decoyButton"
android:onClick="onClickHide"
android:text="Hide" />
<Button
android:id="@+id/shootButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_above="@id/hideButton"
android:onClick="onClickShoot"
android:text="Assassinate" />
<TextView
android:id="@+id/status"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Test of text view"
android:layout_above="@id/shootButton"
/>
</RelativeLayout>
I chose this approach (custom SurfaceView with an xml layout to simplify interactive portion of the interface.
The problem that I have is that, while this implementation works fine the first time that I run the app, when the user quits by pressing the Quit button (where I call finish()) or presses the Back button on the device, I cannot re-run the app. Pressing on the app icon a second time opens a blank app with a title bar and pressing the Back button on this screen has no effect (I need to press the Home button to exit).
If I use:
setContentView(new GamePanel(this))
instead of:
setContentView(R.layout.game)
i.e. I just use the custom SurfaceView with an onTouch() event to call finish(), then the app behaves properly (it can be stopped and restarted as often as I wish).
Code for the GamePanel class is as follows:
package ca.mytest.player;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.WindowManager;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
public class GamePanel extends SurfaceView implements SurfaceHolder.Callback
{
private static final String TAG = GamePanel.class.getSimpleName();
//private Context context;
private GameThread thread;
private final float dotSize = 20.0f;
private final int numberOfButtons = 4;
private ActionButton[] actionButtons;
private WindowManager windowManager;
public GamePanel(Context context)
{
super(context);
init(context);
}
public GamePanel(Context context, AttributeSet attrs)
{
super(context, attrs);
init(context);
}
public GamePanel(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
init(context);
}
private void init(Context context)
{
Log.d(TAG, "*********************************");
Log.d(TAG, "Creating Game Panel");
Log.d(TAG, "*********************************");
// add callback (this) to surface holder to intercept events
getHolder().addCallback(this);
// make the GamePanel focusable so it can handle events
setFocusable(true);
windowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
actionButtons = new ActionButton[numberOfButtons];
thread = new GameThread(getHolder(), this);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
{
}
@Override
public void surfaceCreated(SurfaceHolder holder)
{
thread.setRunning(true);
thread.start();
}
@Override
public void surfaceDestroyed(SurfaceHolder holder)
{
boolean retry = true;
while(retry)
{
try
{
thread.join();
retry = false;
}
catch (InterruptedException e)
{
Log.d(TAG, "surface destroyed");
}
}
}
@Override
public boolean onTouchEvent(MotionEvent event)
{
if (event.getAction() == MotionEvent.ACTION_DOWN)
{
if (event.getY() > getHeight() - 50)
{
thread.setRunning(false);
((Activity)getContext()).finish();
}
else
{
Log.d(TAG, "Coords: x = " + event.getX() + " ,y = " + event.getY());
}
}
return super.onTouchEvent(event);
}
@Override
protected void onDraw(Canvas canvas)
{
canvas.drawColor(Color.BLACK);
drawGameCircle(canvas);
}
private void drawGameCircle(Canvas canvas)
{
// Find centre of game circle
// Game circle fits in a square at far right, full height of display
float x = (float)(getWidth()/2);
float y = (float)(getWidth()/2);
float radius = (float)(getWidth()/2);
Paint paint = new Paint();
paint.setColor(Color.WHITE);
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(x, y, radius, paint);
// Draw dot for player
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(x, y, dotSize, paint);
// draw a horizontal line to show game area
canvas.drawLine(0, 767, 600, 767, paint);
}
}
Is there something fundamentally wrong with my implementation? Any help would be very much appreciated. Thanks.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
啊,这个问题。在我弄清楚问题所在之前,我自己也遇到过这个问题几次。
问题是,finish()仅在整个类完成运行时被调用,从而关闭你的线程。
但是,在对 thread.join() 的调用完成之前,不会调用 finish(),从而导致死锁情况。
我不知道建议的操作是什么,但我更愿意在调用 thread.join() 之前在 surfaceDestroyed() 方法中终止线程。
这样,它告诉线程在等待之前完成事情。
Ah, this problem. I ran into this problem myself a few times, before I figured out what was wrong.
The problem is that finish() is only called when the whole class is finished running, thereby shutting down your Thread.
However, finish() will not be called until the call to thread.join() is done, causing a deadlock condition.
I don't know what the recommended action is, but I'd prefer to terminate the thread in the surfaceDestroyed() method, right before calling thread.join().
That way, it tells the thread to finish things up before waiting on it.