自定义SurfaceView与线程崩溃应用程序退出活动时

时间:2013-03-14 19:41:44

标签: android surfaceview

我刚刚创建了一个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还是什么?

提前致谢, 马太

2 个答案:

答案 0 :(得分: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)。在这个新的球类中,球具有位置(由PointF指定),Paint和直径。除了设置一些变量外,它还有获取大多数变量的方法。

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类,如下所示。请注意,重绘到画布现在只在用户触摸画布而不是无限循环时才会发生。这是由于使用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已准备就绪(see the documentation on this lockCanvas method)。如果您希望曲面最初为蓝色,则需要实现[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编码的更多信息会有所帮助。

答案 1 :(得分:2)

正如有人在上面提到的,当你的活动结束时,你的线程仍然在运行,但你的自定义SurfaceView不再可用,所以你会得到一个Null Point异常。一旦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都将被销毁。 我希望这能帮到您。 干杯!