释放相机后调用的方法抛出异常,无法恢复 Android 相机。

60

在开发相机应用时,我遇到了一个异常,只有当我切换到其他应用程序(对于我的应用程序,onPause())时才会发生。

01-15 17:22:15.017: E/AndroidRuntime(14336): FATAL EXCEPTION: main
01-15 17:22:15.017: E/AndroidRuntime(14336): java.lang.RuntimeException: Method called after release()
01-15 17:22:15.017: E/AndroidRuntime(14336):    at android.hardware.Camera.setPreviewDisplay(Native Method)
01-15 17:22:15.017: E/AndroidRuntime(14336):    at android.hardware.Camera.setPreviewDisplay(Camera.java:357)
01-15 17:22:15.017: E/AndroidRuntime(14336):    at com.sora.cbir.yuki.image.leaf.CameraPreview.surfaceCreated(CameraPreview.java:32)
01-15 17:22:15.017: E/AndroidRuntime(14336):    at android.view.SurfaceView.updateWindow(SurfaceView.java:551)
01-15 17:22:15.017: E/AndroidRuntime(14336):    at android.view.SurfaceView.onWindowVisibilityChanged(SurfaceView.java:213)
01-15 17:22:15.017: E/AndroidRuntime(14336):    at android.view.View.dispatchWindowVisibilityChanged(View.java:4075)
01-15 17:22:15.017: E/AndroidRuntime(14336):    at android.view.ViewGroup.dispatchWindowVisibilityChanged(ViewGroup.java:742)
01-15 17:22:15.017: E/AndroidRuntime(14336):    at android.view.ViewGroup.dispatchWindowVisibilityChanged(ViewGroup.java:742)
01-15 17:22:15.017: E/AndroidRuntime(14336):    at android.view.ViewGroup.dispatchWindowVisibilityChanged(ViewGroup.java:742)
01-15 17:22:15.017: E/AndroidRuntime(14336):    at android.view.ViewGroup.dispatchWindowVisibilityChanged(ViewGroup.java:742)
01-15 17:22:15.017: E/AndroidRuntime(14336):    at android.view.ViewRoot.performTraversals(ViewRoot.java:858)
01-15 17:22:15.017: E/AndroidRuntime(14336):    at android.view.ViewRoot.handleMessage(ViewRoot.java:1995)
01-15 17:22:15.017: E/AndroidRuntime(14336):    at android.os.Handler.dispatchMessage(Handler.java:99)
01-15 17:22:15.017: E/AndroidRuntime(14336):    at android.os.Looper.loop(Looper.java:150)
01-15 17:22:15.017: E/AndroidRuntime(14336):    at android.app.ActivityThread.main(ActivityThread.java:4389)
01-15 17:22:15.017: E/AndroidRuntime(14336):    at java.lang.reflect.Method.invokeNative(Native Method)
01-15 17:22:15.017: E/AndroidRuntime(14336):    at java.lang.reflect.Method.invoke(Method.java:507)
01-15 17:22:15.017: E/AndroidRuntime(14336):    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:849)
01-15 17:22:15.017: E/AndroidRuntime(14336):    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:607)
01-15 17:22:15.017: E/AndroidRuntime(14336):    at dalvik.system.NativeStart.main(Native Method)

我做了一些研究,发现需要添加

mCamera.setPreviewCallback(null);

作为Android相机栈的一种解决方法

我的onPause()现在看起来是这样的:

@Override
protected void onPause() {
    super.onPause();
    try
    {    
        // release the camera immediately on pause event   
        //releaseCamera();
         mCamera.stopPreview(); 
         mCamera.setPreviewCallback(null);
         mCamera.release();
         mCamera = null;

    }
    catch(Exception e)
    {
        e.printStackTrace();
    }
}

以及我的 onResume() 方法:

@Override
protected void onResume()
{
    super.onResume();
    try
    {
        mCamera.setPreviewCallback(null);
        mCamera = getCameraInstance();
        //mCamera.setPreviewCallback(null);
        mPreview = new CameraPreview(Imageupload.this, mCamera);//set preview
        preview.addView(mPreview);
    } catch (Exception e){
        Log.d(TAG, "Error starting camera preview: " + e.getMessage());
    }
}   
}

最后是我的getCameraInstance()方法:

public Camera getCameraInstance(){
    Camera camera = null;
    try {
        camera = Camera.open(); // attempt to get a Camera instance
    }
    catch (Exception e){
        // Camera is not available (in use or does not exist)
    }
    Camera.Parameters parameters = camera.getParameters();
    //mPreviewSize = getBestPreviewSize(parameters, wt, ht);
    //mPictureSize = getBestPictureSize(parameters, wt, ht);
    //Shift W & H => if camera rotates 90 deg

    mPreviewSize = getOptimalPreviewSize(parameters, wt, ht); //original => wt,ht
    mPictureSize = getOptimalPictureSize(parameters, wt, ht); //original => wt,ht

    Log.d("CAMERA", "SCREEN RESOLUTION H: "+ht);
    Log.d("CAMERA", "SCREEN RESOLUTION W: "+wt);

    Log.d("CAMERA", "PREVIEW RESOLUTION H: "+mPreviewSize.height);
    Log.d("CAMERA", "PREVIEW RESOLUTION W: "+mPreviewSize.width);

    Log.d("CAMERA", "PICTURE RESOLUTION H: "+mPictureSize.height);
    Log.d("CAMERA", "PICTURE RESOLUTION W: "+mPictureSize.width);
    //set preview size based on device screen
    parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
    //set picture size based on device screen
    parameters.setPictureSize(mPictureSize.width, mPictureSize.height);
    //set output camera mode
    parameters.setPictureFormat(PixelFormat.JPEG);
    //set focous mode
    parameters.setFocusMode(FOCUS_MODE_AUTO);
    //set flash mode
    parameters.setFlashMode("auto");
    List<int[]> fps = parameters.getSupportedPreviewFpsRange();
    //System.out.println("FPS size: " +fps.size());
    //System.out.println("MAX FPS:"+(fps.get(fps.size()-1)[1])/1000);
    //log min and max camera supported fps
    Log.d("CAMERA", "CAMERA MAX FPS: "+(fps.get(fps.size()-1)[1])/1000);
    Log.d("CAMERA", "CAMERA MIN FPS: "+(fps.get(fps.size()-1)[0])/1000);
    if(camera_fps)
    {
        parameters.setPreviewFpsRange(fps.get(fps.size()-1)[1], fps.get(fps.size()-1)[1]);
    }
    //set camera parameters
    camera.setParameters(parameters);

    Toast.makeText(getApplicationContext(), "Your device are capable of previewing @" + fps.get(fps.size()-1)[1]/1000+"fps!",Toast.LENGTH_SHORT).show();
    return camera; // returns null if camera is unavailable
}

有没有解决这个问题的想法?

9个回答

146

我遇到了相同的问题。 mCamera.setPreviewCallback(null); 没有起作用。在我的活动中,我已经添加了以下内容到 releaseCamera

mPreview.getHolder().removeCallback(mPreview);

现在它已经可用。


23
这对我很有效。我把它放在 onPause 函数中的 mCamera.setPreviewCallback(null)mCamera.release() 之间。 - Samir Unni
6
在这段代码中,“mPreview”具体是什么?我也遇到了相同的错误,但好像没有任何适合传递给这个方法的对象。我唯一拥有的回调是“Camera.PreviewCallback”,而不是“SurfaceHolder.Callback”(“mPreview.getHolder()”是你的“SurfaceHolder”,对吧?) - Jules
1
类似这样的代码: private CameraPreview mPreview; 在onResume方法中: mPreview = new CameraPreview(this, mCamera, preview, null);public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback抱歉,那是很久以前的事情了,我记不太清楚了。 - Kirill Bubochkin

20

@ookami.kb的解决方案对我也管用,以及@srunni的评论。

public void onPause() {
    super.onPause();

    if (mCamera != null) {
        mCamera.setPreviewCallback(null);
        mPreview.getHolder().removeCallback(mPreview);
        mCamera.release();
    }
}

我也删除了 onDestroy 方法。


为什么你会从OnDestroy中移除 - 完全没有意义! - slott

10

文档明确指出,camera.release() 释放所有相机资源。调用此方法后,相机引用将无法再使用。

如果您想再次使用相机,必须通过 open(int) 方法获取它。

这些内容都在相机文档中有详细说明。


你能再检查一下代码吗?我已经按照你的建议进行了修改,并根据Android开发指南修复了其余部分,但仍然没有结果。 - tom91136
还是出现同样的错误吗?它明确指出了“在释放后调用方法”和下一行“在android.hardware.Camera.setPreviewDisplay(..)处”。因此,您必须在“release()”之后调用“setPreviewDisplay()”。 - Peter Knego
在我的onResume中,我需要恢复我的预览,因此使用mCamera.open()...,然后再次设置所有参数和预览显示表面,对吗?这必须在release()之后调用。如果这导致异常,那么我该如何正确地恢复它呢? - tom91136
无论如何,我已经用try catch语句包围了我的setPreviewDisplay(..)方法,现在系统会报告异常但继续我的任务;那么我将把这视为一种解决方法 :) 如果您认为还有其他需要补充的,请随时添加 :) - tom91136
3
这不是正确的答案。根据其他回答,问题可以通过使用removeCallback()来解决。谷歌文档不完整。 - Adam Mackler

5
为了正确地恢复,您需要执行以下操作:
@Override
public void onResume() {
    super.onResume();  

    // Get the Camera instance as the activity achieves full user focus
    if (mCamera == null) {
        initializeCamera(); // Local method to handle camera initialization
    }
}



protected void initializeCamera(){
    // Get an instance of Camera Object
    mCamera = getCameraInstance();

   // create a basic camera preview class that can be included in a View layout.
    mPreview=new CameraPreview(this,mCamera);

    //add your preview class to the FrameLayout element.
    preview.addView(mPreview);

   //Trigger capturing an image by calling the Camera.takePicture() method.
    captureButton.setOnClickListener(
            new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    // get an image from the camera
                    mCamera.takePicture(null, null, mPicture);
                }
            }
        );
}

还要提醒一点,在onCreate()中仅定义FrameLayout预览和Button拍照按钮,不要做其他事情。


2
@Override public void surfaceDestroyed(SurfaceHolder surfaceHolder) {

    this.getHolder().removeCallback(this);
    mCamera.stopPreview();
    mCamera.release();
    mCamera = null;
  Log.e("surfaceDestroyed", "surfaceDestroyed");

}

在 on Resume 函数中重新初始化相机。


1
添加到okambi的答案中。
这个函数在恢复时会搞砸一切:
 public void surfaceCreated(SurfaceHolder holder) {
        // The Surface has been created, now tell the camera where to draw the preview.
        try {
            mCamera.setPreviewDisplay(holder);
            mCamera.startPreview();
        } catch (IOException e) {
            Log.d(TAG, "Error setting camera preview: " + e.getMessage());
        }
    }

try{}没有捕获抛出的异常,即mCamera不存在,当尝试调用setPreviewDisplay(holder)时,会崩溃。
因此,通过删除回调函数,避免调用surfaceCreated并避免崩溃。
这是Google非常糟糕的文档。

1
我已经放置了 <\p>。
mPreview.getHolder().removeCallback(mPreview);  

之间。

mCamera.setPreviewCallback(null);

mCamera.release();

并且对我起作用了。

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

                this.saveTextEdits();
                try {
                    mCamera.stopPreview();
                    mCamera.setPreviewCallback(null);
                    **mPreview.getHolder().removeCallback(mPreview);**
                    mCamera.release();
                    mCamera = null;
                }catch (Exception e){

                }
            }

0

我遇到了同样的问题,我通过在Preview类的surfaceDestroyed(SurfaceHolder holder)方法中添加mCamera = null;来解决它。

public void surfaceDestroyed(SurfaceHolder holder) {
    // Surface will be destroyed when we return, so stop the preview.
    if (mCamera != null) {
        mCamera.stopPreview();
        mCamera.release();
        mCamera = null;
    }
}

并且 - 添加

camera = Camera.open();
    camera.startPreview();
    params = camera.getParameters();
    preview.setCamera(camera);

在我的CameraActivity的OnResume()方法中。

0
如果你遇到了以下问题:

尝试在空对象引用上调用虚拟方法“void android.hardware.Camera.setPreviewCallback(android.hardware.Camera $ PreviewCallback)”

我同意@ookami.kb的观点 - mCamera.setPreviewCallback(null);不够,还需要在其后添加以下内容:
mCameraView.getHolder().removeCallback(mCameraView);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接