简体   繁体   中英

SurfaceView crashes on orientation change

I am using a SurfaceView to draw on top of the camera preview, adapting code from this tutorial . The app works at first then crashes on orientation change, sometimes on the first change, sometimes after changing two or three times. I've seen quite a few similar questions but none has a solution (for my case). This is the exception:

05-09 22:14:48.384: D/libEGL(829): loaded /vendor/lib/egl/libEGL_POWERVR_SGX540_120.so
05-09 22:14:48.400: D/libEGL(829): loaded /vendor/lib/egl/libGLESv1_CM_POWERVR_SGX540_120.so
05-09 22:14:48.408: D/libEGL(829): loaded /vendor/lib/egl/libGLESv2_POWERVR_SGX540_120.so
05-09 22:14:48.486: D/OpenGLRenderer(829): Enabling debug mode 0
05-09 22:14:49.056: I/Choreographer(829): Skipped 40 frames!  The application may be doing too much work on its main thread.
05-09 22:14:49.337: D/dalvikvm(829): GC_FOR_ALLOC freed 113K, 2% free 8736K/8876K, paused 50ms, total 64ms
05-09 22:14:49.353: I/dalvikvm-heap(829): Grow heap (frag case) to 11.521MB for 3110416-byte allocation
[snip lots]
05-09 22:14:56.423: D/AndroidRuntime(829): Shutting down VM
05-09 22:14:56.423: W/dalvikvm(829): threadid=1: thread exiting with uncaught exception (group=0x4180a930)
05-09 22:14:56.439: E/AndroidRuntime(829): FATAL EXCEPTION: main
05-09 22:14:56.439: E/AndroidRuntime(829): java.lang.RuntimeException: Method called after release()
05-09 22:14:56.439: E/AndroidRuntime(829):  at android.hardware.Camera.setHasPreviewCallback(Native Method)
05-09 22:14:56.439: E/AndroidRuntime(829):  at android.hardware.Camera.access$600(Camera.java:131)
05-09 22:14:56.439: E/AndroidRuntime(829):  at android.hardware.Camera$EventHandler.handleMessage(Camera.java:784)
05-09 22:14:56.439: E/AndroidRuntime(829):  at android.os.Handler.dispatchMessage(Handler.java:99)
05-09 22:14:56.439: E/AndroidRuntime(829):  at android.os.Looper.loop(Looper.java:137)
05-09 22:14:56.439: E/AndroidRuntime(829):  at android.app.ActivityThread.main(ActivityThread.java:5041)
05-09 22:14:56.439: E/AndroidRuntime(829):  at java.lang.reflect.Method.invokeNative(Native Method)
05-09 22:14:56.439: E/AndroidRuntime(829):  at java.lang.reflect.Method.invoke(Method.java:511)
05-09 22:14:56.439: E/AndroidRuntime(829):  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
05-09 22:14:56.439: E/AndroidRuntime(829):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
05-09 22:14:56.439: E/AndroidRuntime(829):  at dalvik.system.NativeStart.main(Native Method)
05-09 22:14:58.236: I/Process(829): Sending signal. PID: 829 SIG: 9

My main activity contains the camera preview:

public class MainActivity extends Activity implements SurfaceHolder.Callback, LocationListener {

private Camera camera;
private SurfaceView cameraSV;
private SurfaceHolder cameraSH;
private OverlayView overlay;

/* Activity event handlers */
// Called when activity is initialised by OS
@Override
public void onCreate(Bundle inst) {
    super.onCreate(inst);
    setContentView(R.layout.activity_main);
    initCamera();
}

// Called when activity is closed by OS
@Override
public void onDestroy() {
    super.onDestroy();
    // Turn off the camera
    stopCamera();
}

/* SurfaceHolder event handlers */
// Called when the surface is first created
public void surfaceCreated(SurfaceHolder holder) {
}

// Called when surface dimensions etc change
public void surfaceChanged(SurfaceHolder sh, int format, int width,
        int height) {
    // Start camera preview
    startCamera(sh, width, height);
}

// Called when the surface is closed/destroyed
public void surfaceDestroyed(SurfaceHolder sh) {
    stopCamera();
}

private void initCamera() {
    cameraSV = (SurfaceView) findViewById(R.id.surface_camera);
    cameraSH = cameraSV.getHolder();
    cameraSH.addCallback(this);

    camera = Camera.open();

    overlay = (OverlayView) findViewById(R.id.surface_overlay);
    overlay.getHolder().setFormat(PixelFormat.TRANSLUCENT);
    overlay.setCamera(camera);
}

// Setup camera based on surface parameters
private void startCamera(SurfaceHolder sh, int width, int height) {
    Camera.Parameters p = camera.getParameters();
    for (Camera.Size s : p.getSupportedPreviewSizes()) { 
        p.setPreviewSize(s.width, s.height);
        overlay.setPreviewSize(s);
        break;
    }
    if (this.getResources().getConfiguration().orientation != Configuration.ORIENTATION_LANDSCAPE) {
        // p.set("orientation", "portrait");
        // p.setRotation(90);
        camera.setDisplayOrientation(90);
    } else {
        // p.set("orientation", "landscape");
        // p.setRotation(0);
        camera.setDisplayOrientation(0);
    }
    camera.setParameters(p);

    try {
        camera.setPreviewDisplay(sh);
    } catch (Exception e) { // Log surface setting exceptions

    }
    camera.startPreview();
}

// Stop camera when application ends
private void stopCamera() {
    if (cameraSH != null) cameraSH.removeCallback(this);
    if (camera != null) {
        camera.stopPreview();
        camera.release();
    }
}   
}

My custom SurfaceView class is drawn on top of the camera preview:

public class OverlayView extends SurfaceView {
private SurfaceHolder surfaceHolder;
private Camera camera;
private Camera.Size frameSize;

public OverlayView(Context ctx) {
    super(ctx);
    surfaceHolder= getHolder();
}

public OverlayView(Context ctx, AttributeSet attr) {
    super(ctx, attr);
    surfaceHolder = getHolder();
}

public void setPreviewSize(Camera.Size s) {
    frameSize = s;
}

// Called by initCamera, to set callback
public void setCamera(Camera c) {
    camera = c;
    camera.setPreviewCallback(new PreviewCallback() {
        // Called by camera hardware, with preview frame
        public void onPreviewFrame(byte[] frame, Camera c) {
            Canvas canvas = surfaceHolder.lockCanvas(null);
            canvas.drawColor( 0, PorterDuff.Mode.CLEAR );
            try {
                // Perform overlay rendering here
                Paint pt = new Paint();
                pt.setColor(Color.BLACK);
                pt.setTextSize(70);

                canvas.drawText("Hi", 10, frameSize.height-10, pt);
            } catch (Exception e) {
                // Log/trap rendering errors
            } finally {
                surfaceHolder.unlockCanvasAndPost(canvas);
            }
        }
    });
}
}

The layout:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">

    <com.example.OverlayView android:id="@+id/surface_overlay"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent" />

    <SurfaceView android:id="@+id/surface_camera"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent" />
</FrameLayout>

Finally, my manifest has these permissions:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

It looks to me that your error arises from calling camera.stopPreview() after camera.release() since stopCamera() method is probably being called twice, once in onDestroy() and once again in onSurfaceDestroyed(). If you set camera and cameraSH to null after their respective teardown code, it will prevent this from happening. Suggested fix in stopCamera():

if (cameraSH != null) {
    cameraSH.removeCallback(this);
    cameraSH = null; 
}
if (camera != null) {
    camera.stopPreview();
    camera.release();
    camera = null;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM