我在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().