场景
网上demo做法大多是使用SurfaceView或者TextureView,在view创建可用(surfaceCreated,onSurfaceTextureAvailable)后打开相机,设置setPreviewDisplay/setPreviewTexture,开启预览。在surfaceDestroyed/onSurfaceTextureDestroyed后释放相机。
在demo的情况下,这种做法一般不会暴露出问题,但是在实际的场景中,比如用户频繁通过硬件启动应用关闭应用,比如用户在多个摄像头应用中切换,生命周期变化频繁,而开启相机释放相机都是比较耗时的操作,一来主线程操作的话容易anr,二来容易导致释放相机不及时,Fail to connect to camera service,黑屏等问题。
方法
1. 单独线程 状态管理摄像头的开关
2. onResume打开相机,onPause就释放相机 参考Google官方demo推荐做法
3. Activity destory时保证释放相机
代码
关键部分在CameraController ,新的线程管理摄像头开关,并且设置对应的状态(打开中,已打开,关闭中,已关闭)。
public abstract class CameraController implements
Thread.UncaughtExceptionHandler,Camera.PreviewCallback, Camera.AutoFocusMoveCallback , TextureView.SurfaceTextureListener, Camera.ErrorCallback , Camera.AutoFocusCallback{
private static final String TAG = "CameraController";
/**
* Camera is about to be stopped.
*/
private static final int STATE_STOPPING = -1;
/**
* Camera is stopped.
*/
private static final int STATE_STOPPED = 0;
/**
* Camera is about to cameraStart.
*/
private static final int STATE_STARTING = 1;
/**
* Camera is available and we can set parameters.
*/
private static final int STATE_STARTED = 2;
protected WorkerHandler mHandler;
Handler mCrashHandler;
protected int mState = STATE_STOPPED;
private Object syncObj = new Object();
private CameraManager cameraManager;
private boolean isSurfaceAvailable = false;
private SurfaceTexture surfaceTexture;
public CameraController(Context context) {
mCrashHandler = new Handler(Looper.getMainLooper());
mHandler = new WorkerHandler();
mHandler.getThread().setUncaughtExceptionHandler(this);
cameraManager = CameraManager.getInstance(context);
}
private class NoOpExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
}
}
@Override
public void uncaughtException(final Thread thread, final Throwable throwable) {
// Something went wrong. Thread is terminated (about to?).
// Move to other thread and release resources.
if (!(throwable instanceof CameraException)) {
// This is unexpected, either a bug or something the developer should know.
// Release and crash the UI thread so we get bug reports.
LogUtil.e(TAG, "Unexpected exception:", throwable);
mCrashHandler.post(new Runnable() {
@Override
public void run() {
RuntimeException exception;
if (throwable instanceof RuntimeException) {
exception = (RuntimeException) throwable;
} else {
exception = new RuntimeException(throwable);
}
throw exception;
}
});
destroy();
} else {
// At the moment all CameraExceptions are unrecoverable, there was something
// wrong when starting, stopping, or binding the camera to the preview.
final CameraException error = (CameraException) throwable;
LogUtil.e(TAG, "Interrupting thread with state:" + ss() + "due to CameraException:", error);
thread.interrupt();
}
}
public final void destroy() {
LogUtil.i(TAG, "state:" + ss());
// Prevent CameraController leaks. Don't set to null, or exceptions
// inside the standard cameraStop() method might crash the main thread.
if (mHandler != null && mHandler.getThread() != null) {
mHandler.getThread().setUncaughtExceptionHandler(new NoOpExceptionHandler());
}
// Stop if needed.
stopImmediately();
if(mHandler != null){
mHandler.destroy();
}else {
LogUtil.i(TAG, "mHandler is null!");
}
}
private String ss() {
switch (mState) {
case STATE_STOPPING: return "STATE_STOPPING";
case STATE_STOPPED: return "STATE_STOPPED";
case STATE_STARTING: return "STATE_STARTING";
case STATE_STARTED: return "STATE_STARTED";
default:
break;
}
return "null";
}
/**
* Starts the preview asynchronously.
*/
final public void cameraStart() {
LogUtil.i(TAG, "CameraController.cameraStart() posting runnable. State:" + ss());
mHandler.post(new Runnable() {
@Override
public void run() {
synchronized (syncObj){
LogUtil.i(TAG, "CameraController.cameraStart() executing. State:" + ss());
if(mState == STATE_STARTED){
cameraAttachSurface();
return;
}
if (mState >= STATE_STARTING) {
return;
}
mState = STATE_STARTING;
LogUtil.i(TAG, "about to call onStart()" + ss());
onStart();
LogUtil.i(TAG, "returned from onStart()." + "Dispatching." + ss());
mState = STATE_STARTED;
}
}
});
}
/**
* Stops the preview asynchronously.
*/
public final void cameraStop() {
LogUtil.i(TAG, "CameraController.cameraStop() posting runnable. State:" + ss());
mHandler.removeAllMsg();
mHandler.post(new Runnable() {
@Override
public void run() {
synchronized (syncObj){
LogUtil.i(TAG, "CameraController.cameraStop() executing. State:" + ss());
if (mState <= STATE_STOPPED) {
return;
}
mState = STATE_STOPPING;
LogUtil.i(TAG, "about to call onStop()");
onStop();
LogUtil.i(TAG, "returned from onStop()." + "Dispatching.");
mState = STATE_STOPPED;
}
}
});
}
/**
* Stops the preview synchronously, ensuring no exceptions are thrown.
*/
public final void stopImmediately() {
synchronized (syncObj){
try {
// Don't check, try cameraStop again.
LogUtil.i(TAG, "stopImmediately State was:" + ss());
if (mState == STATE_STOPPED) {
return;
}
mState = STATE_STOPPING;
onStop();
mState = STATE_STOPPED;
LogUtil.i(TAG, "stopImmediately Stopped. State is:" + ss());
} catch (Exception e) {
// Do nothing.
LogUtil.i(TAG, "Swallowing exception while stopping.", e);
mState = STATE_STOPPED;
}
}
}
/**
* Forces a restart.
*/
protected final void restart() {
LogUtil.i("Restart:", "posting runnable");
mHandler.post(new Runnable() {
@Override
public void run() {
synchronized (syncObj){
LogUtil.i(TAG, "executing. Needs stopping:" + (mState > STATE_STOPPED) + "," + ss());
// Don't cameraStop if stopped.
if (mState > STATE_STOPPED) {
mState = STATE_STOPPING;
onStop();
mState = STATE_STOPPED;
LogUtil.i(TAG, "stopped. Dispatching." + ss());
}
LogUtil.i("Restart: about to cameraStart. State:", ss());
mState = STATE_STARTING;
onStart();
mState = STATE_STARTED;
LogUtil.i("Restart: returned from cameraStart. Dispatching. State:", ss());
}
}
});
}
/**
* Starts the preview.At the end of this method camera must be available, e.g.
* for setting parameters.
*/
@WorkerThread
private void onStart(){
initCamera();
cameraAttachSurface();
}
/**
* Stops the preview.
*/
@WorkerThread
private void onStop(){
releaseCamera();
}
/**
* Returns current state.
* @return
*/
final int getState() {
return mState;
}
/**
* 初始化摄像头 TextureView用
*/
private void initCamera() {
cameraManager.initPictureCamera(this,this,this);
}
/**
* 释放摄像头
*/
private void releaseCamera(){
if(cameraManager != null){
cameraManager.release();
}
}
private void cameraAttachSurface() {
if(isSurfaceAvailable && surfaceTexture != null){
cameraManager.cameraAttachSurface(surfaceTexture);
}
}
public void setSurfaceTexture(SurfaceTexture surfaceTexture){
if(surfaceTexture != null){
this.surfaceTexture = surfaceTexture;
}
}
public void setSurfaceAvailable(boolean available){
isSurfaceAvailable = available;
}
}
/**
* 相机类,相机的调用
*/
public class CameraControllerImpl extends CameraController{
private static final String TAG = "CameraControllerImpl";
Context context;
private byte[] preViewBytes = null;
public CameraControllerImpl(Context context, TextureView textureView, boolean startFromHardWare, OCRTranslateMainPresenter presenter) {
super(context);
this.context = context;
this.presenter = presenter;
if(textureView != null){
setSurfaceTexture(textureView.getSurfaceTexture());
}
}
/**
* 获取camera 预览帧
* @param bytes camera预览帧
* @param camera camera
*/
@Override
public void onPreviewFrame(byte[] bytes, Camera camera) {
//preViewBytes = bytes;
//收到首帧
}
/**
* 自动连续对焦的回调
* @param start true if focus starts to move, false if focus stops to move
* @param camera the Camera service object
*/
@Override
public void onAutoFocusMoving(boolean start, Camera camera) {
LogUtil.d(TAG, "onAutoFocusMoving lockFocus: moving:" + start);
if(start){
hasMoved = true;
LogUtil.d(TAG,"开始对焦");
}else {
LogUtil.d(TAG,"对焦完成");
//做对应的操作
}
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
LogUtil.d(TAG,"onSurfaceTextureAvailable" +surface);
//设置surface已可用,重新attach到相机
setSurfaceAvailable(true);
setSurfaceTexture(surface);
cameraStart();
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
LogUtil.d(TAG,"onSurfaceTextureSizeChanged");
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
LogUtil.d(TAG,"onSurfaceTextureDestroyed" +surface);
setSurfaceAvailable(false);
cameraStop();
surface.release();
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
@Override
public void onError(int error, Camera camera) {
if (error == Camera.CAMERA_ERROR_SERVER_DIED) {
// Looks like this is recoverable.
LogUtil.w(TAG,"Recoverable error inside the onError callback.CAMERA_ERROR_SERVER_DIED");
stopImmediately();
cameraStart();
return;
}
LogUtil.e(TAG,"Error inside the onError callback:" + error);
}
@Override
public void onAutoFocus(boolean success, Camera camera) {
if(success){
LogUtil.i(TAG,"主动对焦成功");
presenter.onAutoFocused();
}else {
LogUtil.e(TAG,"主动对焦失败");
}
}
}
//部分代码
public classMainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ocr_translate);
init();
}
private void init(){
initView();
initData();
}
@Override
protected void onResume() {
LogUtil.i(TAG,"onResume");
super.onResume();
//初始化摄像头
if(textureView != null && cameraControllerImpl != null){
cameraControllerImpl.cameraStart();
}
}
@Override
protected void onPause() {
LogUtil.i(TAG, "onPause: ");
super.onPause();
if(cameraControllerImpl != null){
cameraControllerImpl.cameraStop();
}
}
@Override
protected void onDestroy() {
LogUtil.i(TAG, "onDestroy: ");
super.onDestroy();
if(cameraControllerImpl != null){
cameraControllerImpl.destroy();
}
}
public class CameraManager {
private static final String TAG = "CameraManager";
public static CameraManager instance;
private Camera camera;
private static final String KEY_QC_SNAPSHOT_PICTURE_FLIP = "snapshot-picture-flip";
private static final String KEY_QC_PREVIEW_FLIP = "preview-flip";
private static final String KEY_QC_VIDEO_FLIP = "video-flip";
// Values for FLIP settings.
private static final String FLIP_MODE_OFF = "off";
private static final String FLIP_MODE_V = "flip-v";
private static final String FLIP_MODE_H = "flip-h";
private static final String FLIP_MODE_VH = "flip-vh";
// 0~parameters.getMaxZoom()
private static int ZOOM = 10;
public interface PictureResolution {
int RESOLUTION_480 = 540;
int RESOLUTION_600 = 640;
int RESOLUTION_1080 = 1080;
int RESOLUTION_1944 = 1944;
}
public interface Size {
int PREVIEW_SIZE_WIDTH = 360;
int PREVIEW_SIZE_HEIGHT = 320;
/*int PREVIEW_SIZE_WIDTH = 720;
int PREVIEW_SIZE_HEIGHT = 640;*/
int PICTURE_SIZE_WIDTH = 3264;
int PICTURE_SIZE_HEIGHT = 2448;
}
public static synchronized CameraManager getInstance(Context context) {
if (instance == null) {
instance = new CameraManager(context);
}
return instance;
}
private CameraManager(Context context) {
}
private void setCameraMode(Camera.Parameters parameters, int cameraMode) {
try {
Class> cls = Class.forName("android.hardware.Camera$Parameters");
Method setCameraMode = cls.getMethod("setCameraMode", int.class);
setCameraMode.invoke(parameters, cameraMode);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
private void setZSLMode(Camera.Parameters parameters, String zsl) {
try {
Class> cls = Class.forName("android.hardware.Camera$Parameters");
Method setZSLMode = cls.getMethod("setZSLMode", String.class);
setZSLMode.invoke(parameters, zsl);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public void lock() {
if (camera != null) {
camera.lock();
}
}
public void unlock() {
if (camera != null) {
camera.unlock();
}
}
public void stopPreview() {
if (camera != null) {
camera.stopPreview();
}
}
public void startPreview() {
if (camera != null) {
camera.startPreview();
}
}
private int getCameraIndex(int type) {
int cameraCount = Camera.getNumberOfCameras();
//LogUtil.d(TAG, "cameraCount = " + cameraCount);
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
for (int i = 0; i < cameraCount; i++) {
Camera.getCameraInfo(i, cameraInfo);
if (cameraInfo.facing == type) {
return i;
}
}
return -1;
}
public synchronized void release() {
if(camera != null) {
LogUtil.d(TAG, "释放摄像头");
camera.setPreviewCallback(null);
camera.stopPreview();
camera.release();
camera = null;
}
}
public synchronized Camera getCamera() {
return camera;
}
public synchronized void initPictureCamera(Camera.PreviewCallback previewCallback, Camera.AutoFocusMoveCallback focusMoveCallback,Camera.ErrorCallback errorCallback) {
if(camera == null) {
try {
int frontIndex = getCameraIndex(Camera.CameraInfo.CAMERA_FACING_BACK);
if (frontIndex == -1) {
camera = Camera.open();
} else {
camera = Camera.open(frontIndex);
}
Camera.Parameters parameters = camera.getParameters();
for (Camera.Size size : parameters.getSupportedPreviewSizes()) {
LogUtil.d(TAG, "getSupportedPreviewSizes size.width = " + size.width + "---size.height = " + size.height);
}
//parameters.set("zsl", "on");
parameters.setPreviewSize(Size.PREVIEW_SIZE_WIDTH, Size.PREVIEW_SIZE_HEIGHT);
LogUtil.d(TAG, "setPreviewSize size.width = " + Size.PREVIEW_SIZE_WIDTH + "---size.height = " + Size.PREVIEW_SIZE_HEIGHT);
//parameters.setPictureSize(Size.PICTURE_SIZE_WIDTH, Size.PICTURE_SIZE_HEIGHT);
//parameters.setPictureFormat(ImageFormat.JPEG);
//数码变焦 0-parameters.getMaxZoom()
/*if(parameters.isZoomSupported()){
LogUtil.d(TAG,"parameters.setZoom("+ZOOM + ")");
parameters.setZoom(ZOOM);
}*/
//auto fouce
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
camera.setAutoFocusMoveCallback(focusMoveCallback);
//camera.setPreviewCallback(previewCallback);
camera.setOneShotPreviewCallback(previewCallback);
camera.setErrorCallback(errorCallback);
camera.setParameters(parameters);
camera.setDisplayOrientation(90);
} catch (Exception e) {
e.printStackTrace();
LogUtil.e(TAG, "initPictureCamera e = " + e.toString());
}
}
}
public synchronized void cameraAttachSurface(SurfaceTexture surface){
if(camera != null){
try {
camera.setPreviewTexture(surface);
camera.startPreview();
LogUtil.d(TAG, "setPreviewTexture and startPreview");
} catch (IOException e) {
e.printStackTrace();
}
}
}
public synchronized void setZoom(int zoom){
if(getCamera() == null){
ZOOM = zoom;
return;
}
Camera.Parameters parameters = getCamera().getParameters();
if(parameters.isZoomSupported()){
LogUtil.d(TAG,"parameters.setZoom():"+zoom);
if(zoom <= parameters.getMaxZoom()){
parameters.setZoom(zoom);
}else {
parameters.setZoom(parameters.getMaxZoom());
}
}
getCamera().setParameters(parameters);
}
public interface OnInitCompleteListener{
void onComplete(Camera.Parameters parameters);
}
public interface OnPreViewFrameListener{
void onPreviewFrame();
}
}
优化
实际上有位前辈封装的更加完善,直接将camera的管理和textureView封装到新增的一个CameraView中,使用时比较简单,但是逻辑比较复杂,出现问题时不大好找原因,但是思路和做法很值得参考。我只是将状态管理这部分抽取出来了,并没有做太多的工作。后续有时间可以优化这部分的封装。