我在Galaxy s6边缘设备上有以下例外
java.lang.RuntimeException: Camera is being used after Camera.release() was called at android.hardware.Camera.setPreviewSurface(Native Method) at android.hardware.Camera.setPreviewDisplay(Camera.java:702) at com.forsale.forsale.view.uicomponent.qrcode.CameraPreview.surfaceCreated(CameraPreview.java:59) at android.view.SurfaceView.updateWindow(SurfaceView.java:712) at android.view.SurfaceView.onWindowVisibilityChanged(SurfaceView.java:316) at android.view.View.dispatchWindowVisibilityChanged(View.java:10434) at android.view.ViewGroup.dispatchWindowVisibilityChanged(ViewGroup.java:1328) at android.view.ViewGroup.dispatchWindowVisibilityChanged(ViewGroup.java:1328) at android.view.ViewGroup.dispatchWindowVisibilityChanged(ViewGroup.java:1328) at android.view.ViewGroup.dispatchWindowVisibilityChanged(ViewGroup.java:1328) at android.view.ViewGroup.dispatchWindowVisibilityChanged(ViewGroup.java:1328) at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1750) at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1437) at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7397) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:920) at android.view.Choreographer.doCallbacks(Choreographer.java:695) at android.view.Choreographer.doFrame(Choreographer.java:631) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:906) at android.os.Handler.handleCallback(Handler.java:739) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:158) at android.app.ActivityThread.main(ActivityThread.java:7224) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)
这是我的代码:
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback { private SurfaceHolder mHolder; private Camera mCamera; private PreviewCallback previewCallback; private AutoFocusCallback autoFocusCallback; public CameraPreview(Context context,Camera camera,PreviewCallback previewCb,AutoFocusCallback autoFocusCb) { super(context); mCamera = camera; previewCallback = previewCb; autoFocusCallback = autoFocusCb; /* * Set camera to continuous focus if supported,otherwise use * software auto-focus. Only works for API level >=9. */ /* Camera.Parameters parameters = camera.getParameters(); for (String f : parameters.getSupportedFocusModes()) { if (f == Parameters.FOCUS_MODE_CONTINUOUS_PICTURE) { mCamera.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); autoFocusCallback = null; break; } } */ // Install a SurfaceHolder.Callback so we get notified when the // underlying surface is created and destroyed. mHolder = getHolder(); mHolder.addCallback(this); // deprecated setting,but required on Android versions prior to 3.0 mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); } public void surfaceCreated(SurfaceHolder holder) { // The Surface has been created,now tell the camera where to draw the preview. try { mCamera.setPreviewDisplay(holder); } catch (IOException e) { Log.d("DBG","Error setting camera preview: " + e.getMessage()); } } public void surfaceDestroyed(SurfaceHolder holder) { // Camera preview released in activity } public void surfaceChanged(SurfaceHolder holder,int format,int width,int height) { /* * If your preview can change or rotate,take care of those events here. * Make sure to stop the preview before resizing or reformatting it. */ if (mHolder.getSurface() == null){ // preview surface does not exist return; } // stop preview before making changes try { mCamera.stopPreview(); } catch (Exception e){ // ignore: tried to stop a non-existent preview } try { // Hard code camera surface rotation 90 degs to match Activity view in portrait mCamera.setDisplayOrientation(90); mCamera.setPreviewDisplay(mHolder); mCamera.setPreviewCallback(previewCallback); mCamera.startPreview(); mCamera.autoFocus(autoFocusCallback); } catch (Exception e){ Log.d("DBG","Error starting camera preview: " + e.getMessage()); } } }
这是活动的代码
public class QRCodeActivity extends BaseActivity { private Camera mCamera; private CameraPreview mPreview; private Handler mAutoFocusHandler; private ImageScanner mScanner; private boolean mBarcodeScanned = false; private boolean mPreviewing = true; static { System.loadLibrary("iconv"); } public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_qrcode); initializeActionBar(); setActionBarTitle(getString(R.string.qrcode)); setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); mAutoFocusHandler = new Handler(); mCamera = getCameraInstance(); mScanner = new ImageScanner(); mScanner.setConfig(0,Config.X_DENSITY,3); mScanner.setConfig(0,Config.Y_DENSITY,3); mPreview = new CameraPreview(this,mCamera,previewCb,autoFocusCB); FrameLayout preview = (FrameLayout)findViewById(R.id.cameraPreview); preview.addView(mPreview); } public void onPause() { super.onPause(); releaseCamera(); } public static Camera getCameraInstance(){ Camera c = null; try { c = Camera.open(); } catch (Exception e){ } return c; } private void releaseCamera() { if (mCamera != null) { mPreviewing = false; mCamera.setPreviewCallback(null); mCamera.release(); mCamera = null; } } private Runnable doAutoFocus = new Runnable() { public void run() { if (mPreviewing) mCamera.autoFocus(autoFocusCB); } }; PreviewCallback previewCb = new PreviewCallback() { public void onPreviewFrame(byte[] data,Camera camera) { Parameters parameters = camera.getParameters(); Size size = parameters.getPreviewSize(); Image barcode = new Image(size.width,size.height,"Y800"); barcode.setData(data); int result = mScanner.scanImage(barcode); if (result != 0) { mPreviewing = false; mCamera.setPreviewCallback(null); mCamera.stopPreview(); SymbolSet syms = mScanner.getResults(); for (Symbol sym : syms) { showProgressDialog(); ForSaleServerManager.getInstance().verifyQRCode(QRCodeActivity.this,PhoneUtils.getDeviceId(QRCodeActivity.this),sym.getData(),new VerifyQRCodeUIListener() { @Override public void onVerifyQRCodeCompleted(QRCodeResponse response,AppError error) { hideProgressDialog(); if (error != null) { DialogUtils.showDialogMessage( QRCodeActivity.this,getString(R.string.error),PhoneUtils.getErrorMessage(QRCodeActivity.this,error),getString(R.string.ok),null); } else { if (response != null && response.getError() == null) { DialogUtils.showDialogMessage(QRCodeActivity.this,getString(R.string.info),response.getMessage(),new View.OnClickListener() { @Override public void onClick(View view) { finish(); } }); } else if (response != null && response.getError() != null) { DialogUtils.showDialogMessage( QRCodeActivity.this,response.getError().getMessage(),null); } } } }); mBarcodeScanned = true; } } } }; AutoFocusCallback autoFocusCB = new AutoFocusCallback() { public void onAutoFocus(boolean success,Camera camera) { mAutoFocusHandler.postDelayed(doAutoFocus,1000); } }; @Override protected void initializeUIComponents() { } @Override protected void initializeUIComponentsData() { } @Override protected void initializeUIComponentsTheme() { } @Override protected void initializeUIComponentsAction() { } @Override public void goodTimeToReleaseMemory() { /*mCamera = null; mPreview = null; mAutoFocusHandler = null; mScanner = null;*/ Runtime.getRuntime().gc(); finish(); } }
解决方法
代码中存在严重缺陷:每个onPause都会调用mCamera.release(),但Camera.open()只调用onCreate.
这就是说,在主(UI)线程上打开相机是一种不好的做法:这种调用在某些设备上可能需要很长时间(不在三星Galaxy 6上)甚至会导致ANR.
最佳做法是在后台HandlerThread上打开相机(参见https://stackoverflow.com/a/19154438/192373).这将保证相机回调(包括onPictureTaken())不会冻结您的UI线程.
无论如何,为了使mCamera对象与您的活动及其SurfaceView的生命周期保持同步,我建议从surfaceCreated启动Camera.open()并从surfaceDestroyed启动mCamera.release().