2

我刚刚制作了一个 Activity,它在“setContentView”处使用了一个扩展 SurfaceView 的类的视图。问题是:它工作正常,但是当我退出它(BACK 键)时它崩溃了。代码:

package ro.etrandafir.mate.appCreator;

import android.app.Activity;
import android.os.Bundle;
import android.content.Context;
import android.view.View;
import android.view.SurfaceView;
import android.graphics.Canvas;
import android.view.MotionEvent;
import android.graphics.Color;
import android.graphics.Paint;

public class Sample2 extends Activity implements View.OnTouchListener {

    float x = 0, y = 0;
    SampleTwoView theView;

    public boolean onTouch(View v, MotionEvent event) {
        // TODO: Implement this method
        x = event.getX();
        y = event.getY();
        return true;
    }

    @Override
    protected void onPause() {
        super.onPause();
        finish();
    }

    @Override
    protected void onCreate(Bundle b) {
        super.onCreate(b);
    theView = new SampleTwoView(this);
        theView.setOnTouchListener(this);
        setContentView(theView);
    }

    public class SampleTwoView extends SurfaceView implements Runnable {

        Paint p = new Paint();

        public SampleTwoView(Context context) {
            super(context);
            p.setColor(Color.RED);
            Thread theThread = new Thread(this);
            theThread.start();
        }

        public void run() {
            while (true) {
                if (!getHolder().getSurface().isValid()) continue;
                Canvas canvas;
                canvas = getHolder().lockCanvas();
                canvas.drawColor(Color.BLUE);
                if ((x != 0) && (y != 0)) canvas.drawCircle(x, y, 40, p);
                getHolder().unlockCanvasAndPost(canvas);
            }
        }
    }
}

我能做些什么?我应该添加 onDestroy 还是什么?

提前致谢,马太

4

2 回答 2

2

您遇到的问题与此代码有关:

Canvas canvas;
canvas = getHolder().lockCanvas();
canvas.drawColor(Color.BLUE);

当您的活动结束时,您的线程仍在运行,但您的自定义SurfaceView不再可用,因此您将获得 null ptr 异常。onPause通过添加一个在调用 fn 时设置为 false 的布尔值,可以轻松地修补现有代码:

public void run() {
    while (booleanThatGetsSetToFalseWhenActivityPauses) {
        if (!getHolder().getSurface().isValid()) continue;
        Canvas canvas;
        canvas = getHolder().lockCanvas();
        canvas.drawColor(Color.BLUE);
        if ((x != 0) && (y != 0)) canvas.drawCircle(x, y, 40, p);
        getHolder().unlockCanvasAndPost(canvas);
    }
}

但是,我建议您整体更改应用程序的结构。这可能只是为了练习,但我认为实现目标的更有效且无错误的方法是简单地使用标准SurfaceView并将绘图逻辑与任何自定义视图完全分离。


下面是我重新设计的活动,但它使用了一个Ball用于维护球逻辑的类,在您当前的代码中,该类分别与活动(坐标)和视图(的Paint)耦合。在这个新的球类中,球具有位置(由 a 指定PointF)、aPaint和直径。除了设置一些变量之外,它还具有获取大多数这些变量的方法。

public class Ball {

    private Paint mPaint;
    private PointF mCoordinates;
    private int mDiameter;

    public Ball (int color, int diameter) {
        mPaint = new Paint();
        mPaint.setColor(color);
        mCoordinates = new PointF();
        mCoordinates.x = 0;
        mCoordinates.y = 0;
        mDiameter = diameter;
    }

    public void setCoordinates (float x, float y) {
        mCoordinates.x = x;
        mCoordinates.y = y;
    }

    public PointF getCoordinates() {
        return mCoordinates;
    }

    public Paint getPaint() {
        return mPaint;
    }

    public int getDiameter() {
        return mDiameter;
    }

    /* You did not want to draw the uninitialized ball, so this method checks that */
    public boolean hasNonZeroLocation () {
        return (mCoordinates.x != 0 && mCoordinates.y != 0);
    }
}

Ball在活动中使用该类,如下所示。请注意,现在仅当用户触摸画布时才会重新绘制到画布,而不是无限的 while 循环。这是由于使用了将Handler要运行的操作发布到 UI 线程的类。另外,现在我们不需要自定义视图,并且我们的球的逻辑已经从活动和视图中解耦了。

public class RedBallActivity extends Activity {

Handler mDrawingHandler;
SurfaceView mDrawingSurfaceView;
Ball mBall;

private final Runnable drawRedBallOnBlueSurface = new Runnable() {
    @Override
    public void run() {
        if (!mDrawingSurfaceView.getHolder().getSurface().isValid()) return;

        Canvas canvas = mDrawingSurfaceView.getHolder().lockCanvas();
        canvas.drawColor(Color.BLUE);
        if (mBall.hasNonZeroLocation()) 
            canvas.drawCircle(mBall.getCoordinates().x, mBall.getCoordinates().y, mBall.getDiameter(), mBall.getPaint());

        mDrawingSurfaceView.getHolder().unlockCanvasAndPost(canvas);
    }
};

private final OnTouchListener mCanvasTouchListener = new OnTouchListener() {

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        mBall.setCoordinates(event.getX(), event.getY());
        mDrawingHandler.post(drawRedBallOnBlueSurface);
        return true;
    }
};

@Override
protected void onCreate(Bundle b) {
    super.onCreate(b);
    mDrawingSurfaceView = new SurfaceView(this);
    mDrawingSurfaceView.setOnTouchListener(mCanvasTouchListener);
    setContentView(mDrawingSurfaceView);
    mBall = new Ball(Color.RED, 40); 
    mDrawingHandler = new Handler();
}
}

现在,如果您实际运行此代码,您会注意到最初屏幕不是用蓝色背景绘制的。您可能很想mDrawingHandler.post(drawRedBallOnBlueSurface);在方法结束时简单地调用onCreate,但不能保证 SurfaceView 将准备好被绘制(请参阅此 lockCanvas 方法的文档)。如果你想让表面最初是蓝色的,你需要实现一个[SurfaceHolder.Callback][2],它需要连接到SurfaceView的SurfaceHolder,并且在surfaceCreated被调用的方法上,我们知道表面已经准备好了,所以我们可以调用mDrawingHandler.post(drawRedBallOnBlueSurface);

现在,添加了这个,我将 Activity 更改[SurfaceHolder.Callback][2]为如下实现:

public class FriendManagerActivity extends Activity implements SurfaceHolder.Callback {

并将这一行添加到构造函数中:

mDrawingSurfaceView.getHolder().addCallback(this);

并实现接口:

@Override
public void surfaceCreated(SurfaceHolder holder) {
    mDrawingHandler.post(drawRedBallOnBlueSurface);
}

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
        int height) {
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}

随时就我的小重新设计提出任何问题!虽然您的问题可以很容易地修补,但我觉得您将逻辑与视图耦合的方式有点缺陷,并且认为更多关于 SurfaceView 编码的信息会有所帮助。

于 2013-03-14T19:50:03.193 回答
2

正如上面有人提到的,当你的activity结束时,你的线程还在运行,但是你自定义的SurfaceView不再可用,所以你会得到一个Null Point Exception。您现有的代码可以通过添加一个布尔值来轻松修补,一旦 onPause fn 被调用,该布尔值就会设置为 false:我遇到了同样的问题。为了解决这个问题,我在 SampleTwoView 类中添加了以下 onPause() :

// pause method will destroy the Thread
    public void pause() {
        isRunning = false;
        while (true) {
            try {
                myThread.join();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            break;
        }
        myThread = null;
    }

然后在 Sample2 类的 onPause() 方法中调用这个 onPause() 方法,如下所示:

@Override
protected void onPause() {
    super.onPause();
    SampleTwoView.onPause();
    finish();
}

因此,每次调用主 Activity 类的 onPause() 方法时,Thread 都会被销毁。我希望这能帮到您。干杯!

于 2014-01-08T03:56:04.683 回答