如何更优雅的管理Android相机预览

场景

网上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中,使用时比较简单,但是逻辑比较复杂,出现问题时不大好找原因,但是思路和做法很值得参考。我只是将状态管理这部分抽取出来了,并没有做太多的工作。后续有时间可以优化这部分的封装。

你可能感兴趣的:(如何更优雅的管理Android相机预览)