Android即拿即用的相机拍照类

这篇文章主要是用来讲解我开发项目中相机开发所遇到的问题和解决方案。

并且将相机功能集成到一个类中,对于不复杂的拍照需求可以直接拿直接用。

相机开发的两种选择:

1.调用系统相机

优点:开发快速,不用解决各种适配问题。

缺点:功能受系统相机限制,无法实现一些自定义的功能,比如自定义的拍照界面。

2.使用Android提供的Camera或者Camera2类来进行自定义相机功能

优点:只要本领大就能够实现各种叼炸天的效果。。特别是UI效果。。

缺点:对于Android市场的碎片化来说,拍照功能又是一个比较重要而且容易出问题的模块。从而需要解决的适配问题也是需要很大精力的。

教训和建议:如果不是不可避免,尽量使用系统相机。就我个人经历而言从网上找的一些Camera开发的资料基本都有适配问题,包括我自己折腾了很久代码,也只能说现有的用户机型正常使用。

核心类的选择:

1.选择Camera还是Camera2

想适配还选Camera2?呵呵无脑选Camera

2.拍摄信息的载体选SurfaceView还是TextureView

这个是可以根据版本选择的,但是出于老的一般比较稳定的。。我选择SurfaceView

关于SurfaceView的使用可以看的博客:

https://blog.csdn.net/lmj623565791/article/details/41722441

出于该篇文章的目的性,我将拍照功能集成到一个类里,当然这作为设计来说是不规范的。

开发流程:

1.自定义个SurfaceView类,用其来展示相机图像信息和和控制相机资源的释放。

2.配置相机参数

3.编写聚焦功能

3.完成拍照和前后置摄像头切换的功能

一.自定义个SurfaceView类,用其来展示相机图像信息和和控制相机资源的释放

首先看下类的继承吧

public class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback ,SensorEventListener, Camera.AutoFocusCallback {

首先可以看到自定义CameraSurfaceView继承的是SurfaceView

实现了三个接口,其中SensorEventListener是一个传感器的一个监听,后续会用到

Camera.AutoFocusCallback是聚焦的回调,调用相机的聚焦api后会被调用

构造函数:

public CameraSurfaceView(Context context, AttributeSet attrs) {
    super(context, attrs);
    mHolder=getHolder();
    mHolder.addCallback(this);
    mSensorManager= (SensorManager) context.getSystemService(Activity.SENSOR_SERVICE);
    mSensor= mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
    setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            isCanFocus=false;
            mCamera.autoFocus(CameraSurfaceView.this);
        }
    });
}

3,4行,是使用SurfaceView的套路了

5,6行,是获取了一个加速度传感器,后续会用来做手机角度和移动停止的判断。

7行设置了一个点击监听,点击就自动聚焦,具体为什么这么做,后续讲到聚焦功能的时候再说。

逻辑还是比较简单的

接下里看SurfaceView生命周期的函数,做了些什么

@Override
public void surfaceCreated(SurfaceHolder holder) {
    mCamera=getCamera(mCameraPos);
    if (mCamera==null){
        //TODO 执行异常处理
    }
    preparePreview();
    mCamera.startPreview();
    mSensorManager.registerListener(CameraSurfaceView.this, mSensor, SensorManager.SENSOR_DELAY_NORMAL);
    isFirst=true;
}

做了4个操作

1.第3行获取相机对象,这里我做了封装,稍后讲解

2.第7行实际是做了一些相机参数的配置,也是稍后讲解

3.第8行开启预览,开启预览后,相机搜集的图像信息才能展示到SurfaceView中

4.第9行是将构造函数获取的传感器注册使用,回调的逻辑和定点聚焦一块讲

获取相机对象

mCamera=getCamera(mCameraPos);

mCameraPos是记录当前使用的相机下标,因为涉及到切屏SurfaceView会被销毁的情况,我们需要用一个变量存储相机的下标,使得再次展示界面能正确的使用上次的相机。mCameraPos默认是-1;

/**
 * 获取相机对象,容易出发Crash,进行统一解决
 * @param position  相机id
 * @return 正常情况返回相机对象,如果出现异常返回null
 */
private Camera getCamera(int position){
    Camera camera=null;
    try {
        Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
        if (position==CAMERA_NO_POS){
            for (int i=0;i<Camera.getNumberOfCameras();i++){
                    Camera.getCameraInfo(i,cameraInfo);
                    if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
                        camera=Camera.open(i);
                        mCameraPos=i;
                        return camera;
                    }
                }
        }
        if (position!=-1&&position<Camera.getNumberOfCameras()){
            camera=Camera.open(position);
            return camera;
        }
    }catch (Exception e){
        //TODO 提示用户,日志记录操作
    }
    return camera;
}

9行,创建了个CameraInfo信息对象,这个对象是个承载Camera信息用的。

第10行做了个判断CAMERA_NO_POS是一个等于-1的常量,也就是说第一次会走if里面的逻辑。

if里面会遍历手机的所有相机,找到第一个后置摄像头,会返回相机对象,并且把mCameraPos设置成当前相机的下标。

第20行,判断如果不是初次获取相机对象。则直接根据传入position返回相机对象。

总体而言getCamera()逻辑还是比较简单,关于preparePreview()内容较多,放到后面讲。

剩下的两个函数都是比较简单的操作了

surfaceChanged():

@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    mCamera.stopPreview();
    preparePreview();
    mCamera.startPreview();
}

如果发生尺寸变化的情况,停止预览,重新配置参数,最后开启预览就是,当然一般情况下,拍照功能应该没有作死弄什么横竖屏切换,或者动态控制拍摄窗口的做法吧。。

surfaceDestroyed():

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
    mSensorManager.unregisterListener(CameraSurfaceView.this);
    mCamera.stopPreview();
    mCamera.release();//释放相机资源
    mCamera=null;
}

销毁时,做的是释放操作。

二.配置参数preparePreview()

/**
 * 1>设置相关尺寸
 * 尺寸有三种,surfaceView的尺寸,预览尺寸,拍照尺寸,三种尺寸的宽高比要保持一致,否则会出现各种问题
 * 2>设置相机预览角度
 * 1)竖屏与系统默认的图像采集方向不一致,所以要设置预览角度
 * 2)拍照的方向也需要矫正,使用camRotation用来记录角度
 * 3>设置聚焦模式
 * 聚焦模式有多种方式,但是在实际效果当中,效果好的自动聚焦模式,也在部分手机中会出现只能聚焦一次的情况,故采用两种方式聚焦
 * 1)设置模式为自动聚焦,点击surfaceView,会进行自动聚焦
 * 2)监测屏幕的移动情况,停止时进行定点聚焦
 */
private void preparePreview(){
    try {
        mCamera.setPreviewDisplay(mHolder); // 设置预览图像
    } catch (IOException e) {
        e.printStackTrace();
    }
    int width=getWidth();
    int height=getHeight();
    Camera.Parameters params = mCamera.getParameters();
    //获取相机支持的预览和拍照格式,并从中选出符合宽高比的尺寸。
    List<Camera.Size> previewSizes = params.getSupportedPreviewSizes();
    List<Camera.Size> picSizes = params.getSupportedPictureSizes();
    Camera.Size preSize;
    Camera.Size picSize;
    //Camera.Size里的尺寸,都是w大于h,所以获取尺寸时,需要区别对待
    if (width>height){
        preSize=getOptimalPreviewSize(previewSizes,width,height);
        picSize=getOptimalPreviewSize(picSizes,width,height);
    }else {
        preSize=getOptimalPreviewSize(previewSizes,height,width);
        picSize=getOptimalPreviewSize(picSizes,height,width);
    }
    if (preSize==null||picSize==null){
        //TODO 执行异常处理
    }
    params.setPreviewSize(preSize.width,preSize.height);
    params.setPictureSize(picSize.width,picSize.height);

    Configuration mConfiguration = getResources().getConfiguration(); //获取设置的配置信息
    int ori = mConfiguration.orientation; //获取屏幕方向
    if (ori == mConfiguration.ORIENTATION_LANDSCAPE) {
        //横屏
        mCamera.setDisplayOrientation(0);
    } else if (ori == mConfiguration.ORIENTATION_PORTRAIT) {
        //竖屏
        mCamera.setDisplayOrientation(90);
    }

    // 设置为自动对焦
    List<String> list = params.getSupportedFocusModes();
    if (list.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
        params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
    }
    mCamera.setParameters(params);
}

第14行,将CameraSurfaceView的mHolder对象设置给mCamera,这样CameraSurfaceView才能展示相机的预览图像信息。

第20行,获取相机的params。

第22和23行分别是获取手机支持的预览尺寸和拍摄尺寸。返回的是Camera.Size集合。Camera.Size里面有封装的是具体的尺寸信息。这两个集合非常重要,因为CameraSurfaceView的宽高比,要和某一个Camera.Size一致才行,否则会出现预览问题。而且拍照图片的宽高最好也从对应的尺寸集合里取值。

24和25行分别声明了两个Camera.Size变量,是用来存储预览尺寸和拍照尺寸的。

27到33行是根据CameraSurfaceView宽高和手机支持的尺寸集合,获取最佳的预览尺寸和拍照尺寸,具体的获取逻辑,为了不干扰,主流程的分析,等会单独给出。

34行的逻辑是当从手机支持的尺寸集合中找不到符合的尺寸的情况,该怎么处理,这个段逻辑就得自己写了。

37行是分别设置预览和拍照尺寸。

40到48行,是因为Android系统默认手机图像信息是以横屏为参考坐标的。所以如果手机竖直的话,会出现预览图像角度不正确,所以在第47行,要对竖屏设置90度的旋转角度。

第51到54行是设置聚焦模式。聚焦模式有多种,具体的不同方式的特点,可以百度。。。我实际情况而言,只有自动聚焦出现的适配问题最少,所以这里采用的是自动聚焦,后续还会讲解使用定点聚焦,解决部分手机自动聚焦失败的问题。

第55行别忘了把配置的参数设置给相机。

讲一下获取最佳尺寸的代码:

getOptimalPreviewSize():

/**
 * 官方demo获取符合宽高比并且最接近目标尺寸的方法
 * 官方demo中ASPECT_TOLERANCE=0.1,代码进行了修改
 * 注释了代码后半部分,当没有找到符合宽高比的尺寸,不会再做选取
 */
private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {
    final double ASPECT_TOLERANCE = 0.2;
    double targetRatio = (double) w / h;
    if (sizes == null) return null;
    Camera.Size optimalSize = null;
    double minDiff = Double.MAX_VALUE;
    int targetHeight = h;
    // Try to find an size match aspect ratio and size
    for (Camera.Size size : sizes) {
        double ratio = (double) size.width / size.height;
        if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
        if (Math.abs(size.height - targetHeight) < minDiff) {
            optimalSize = size;
            minDiff = Math.abs(size.height - targetHeight);
        }
    }

    /*
    // Cannot find the one match the aspect ratio, ignore the requirement
    if (optimalSize == null) {
        minDiff = Double.MAX_VALUE;
        for (Camera.Size size : sizes) {
            if (Math.abs(size.height - targetHeight) < minDiff) {
                optimalSize = size;
                minDiff = Math.abs(size.height - targetHeight);
            }
        }
    }
    */
    return optimalSize;
}

这份代码是官方demo中获取最佳尺寸的。具体作用就是寻找尺寸比例误差小于ASPECT_TOLERANCE,并且大小最接近的尺寸。一般来说,只要CameraSurfaceView是全屏展示都能够找到尺寸,最后我把24行和34的代码注释掉了,这部分代码,会在没有合适的尺寸比的情况下,还会返回一下最佳尺寸,实际上,如果是不符合尺寸比的尺寸,使用过程中肯定会出现预览或者拍照问题,还不如直接返回空,好作为异常处理调。

三.编写聚焦功能

本来聚焦功能是非常简单的,但是因为碎片化的原因,很有一段时间被折腾的焦头烂额,Android系统支持的几种聚焦模式,在实际过程中,总有各种无法聚焦的问题,特别是oppo和vivo两款手机。

首先根据我实际的开发情况,自动聚焦是出现问题最少的,只有几个用户报过,自动聚焦只会聚焦一次的问题。

为了解决部分手机出现的自动聚焦bug的问题,所以额外添加了定点聚焦。

聚焦开发流程:

1.给CameraSurfaceView添加监听事件,点击时进行自动聚焦。这部分逻辑在构造方法中

2.对手机移动状态作监听,当手机移动后停止则进行定点聚焦

补充一点对于自动聚焦,只需要设置参数时,设置聚焦模式为Camera.Parameters.FOCUS_MODE_AUTO。

下面讲解定点聚焦的功能:

定点聚焦:

在surfaceCreated()中我们注册了手机加速度的传感器的监听,现在我们看一下监听的回调函数

@Override
public void onSensorChanged(SensorEvent event) {
    float[] values = event.values;
    int orientation = ORIENTATION_UNKNOWN;
    float X = -values[0];
    float Y = -values[1];
    float Z = -values[2];
    //计算手机当前角度,并且在拍照的时候记录
    computerCamRotation(orientation, X, Y, Z);

    float px =  Math.abs(mOldX - X);
    float py =  Math.abs(mOldY - Y);
    float pz =  Math.abs(mOldZ - Z);
    double value = Math.sqrt(px * px + py * py + pz * pz);
    if (value>1.4){
        mOldMoveTime=System.currentTimeMillis();
        isAreFocus=true;
    }else {
        if (isFirst||isAreFocus&&isCanFocus&&System.currentTimeMillis()-mOldMoveTime>1000){
                //定点聚焦
                focusOnTouch(getWidth()/2,getHeight()/2);
                isAreFocus=false;
                isFirst=false;
        }
    }
    mOldX=  X;
    mOldY=  Y;
    mOldZ=  Z;
}

第3行到第14行是得出当前是否是在移动,具体这一套算法是什么个意思。。。我也没深究。需要知道的是当value>1.4表示当前正在移动手机,

16行是记录上次移动时刻的时间,因为停止时,不是立马聚焦,得停止一定时间,所以需要记录该时刻。

17行是作为是否定点聚焦的一个标志位,因为这个回调函数在停止后也会一直调用,我们不能在用户保持停止后还一直聚焦,所以标准就是,上一时刻必须是移动的状态,下一时刻停止状态才进行定点聚焦。

然后看else的逻辑,也就是当停止时的逻辑。

ifFirst:界面第一次展示的时候,直接进行定位聚焦

其次定点聚焦还要满足,当前可以定点聚焦,当前可以聚焦,停止时间超过1秒这些判断,当然1000这种硬编码的写法也是不值得推荐的。

第21行,进行定点聚焦,具体代码如下:

private void focusOnTouch(int x, int y) {
    Rect rect = new Rect(x - 100, y - 100, x + 100, y + 100);
    int left = rect.left * 2000 / getWidth() - 1000;
    int top = rect.top * 2000 / getHeight() - 1000;
    int right = rect.right * 2000 / getWidth() - 1000;
    int bottom = rect.bottom * 2000 /getHeight() - 1000;
    // 如果超出了(-1000,1000)到(1000, 1000)的范围,则会导致相机崩溃
    left = left < -1000 ? -1000 : left;
    top = top < -1000 ? -1000 : top;
    right = right > 1000 ? 1000 : right;
    bottom = bottom > 1000 ? 1000 : bottom;
    focusOnRect(new Rect(left, top, right, bottom));
}
private void focusOnRect(Rect rect) {
    if (mCamera != null) {
        Camera.Parameters parameters = mCamera.getParameters();
        parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
        if (parameters.getMaxNumFocusAreas() > 0) {
            List<Camera.Area> focusAreas = new ArrayList<Camera.Area>();
            focusAreas.add(new Camera.Area(rect, 1000));
            parameters.setFocusAreas(focusAreas);
        }
        mCamera.cancelAutoFocus(); // 先要取消掉进程中所有的聚焦功能
        mCamera.setParameters(parameters);
        isCanFocus=false;
        mCamera.autoFocus(this);
    }
}

这部分功能可以参考https://blog.csdn.net/afei__/article/details/52033466

因为涉及到画图才能说明白。。我就偷了个懒。。

需要说明的是,我定点聚焦位置,是CameraSurfaceView的中心

最后看一下聚焦的控制:

我在每次调用聚焦功能都会把聚焦的标志位isCanFocus设置为false;

在聚焦的回调里,会设置成true。

@Override
public void onAutoFocus(boolean success, Camera camera) {
    isCanFocus=true;
}

以上聚焦逻辑就讲完了。不过中间还有个函数没讲

//计算手机当前角度,并且在拍照的时候记录
computerCamRotation(orientation, X, Y, Z);

在讲配置参数的时候,说过Android系统默认采集相机图像信息是以横屏的方式采集,所以我们会根据横竖屏来动态的设置相机的预览角度。实际上除了预览角度,还会影响相机拍照的照片的角度。

实际上拍照图片还存在一种情况:

当用户是正着拿相机拍照,这时候图片应该是正在的,这是没有疑问的。

当用户以正的旋转180度,也就是倒着拍照的时候,从预览看图像是正常的图片,如果不作处理的话,照片是倒着的,这也需要我们解决。

这块逻辑,如果不试验的话,可能会被我说糊涂了。。。。不过好消息是,我会贴出代码,直接使用即可。。。

拍照旋转问题的解决方案:

1.定义一个变量用来记录,当拍照时,照片应该旋转多少度

private int camRotation;//相机照片的旋转角度

其次看computerCamRotation()的代码

private void computerCamRotation(int orientation, float x, float y, float z) {
    float magnitude = x * x + y * y;
    // Don't trust the angle if the magnitude is small compared to the y value
    if (magnitude * 4 >= z * z) {
        float OneEightyOverPi = 57.29577957855f;
        float angle = (float)Math.atan2(-y, x) * OneEightyOverPi;
        orientation = 90 - (int)Math.round(angle);
        // normalize to 0 - 359 range
        while (orientation >= 360) {
            orientation -= 360;
        }
        while (orientation < 0) {
            orientation += 360;
        }
    }

     if (orientation != ORIENTATION_UNKNOWN){
            if (isCameraBack) {//如果是后摄像头
                if (orientation >= 305 || orientation < 45) { //0度
                    camRotation = 90;
                } else if (orientation >= 45 && orientation < 135) { //90度
                    camRotation = 180;
                } else if (orientation >= 135 && orientation < 225) { //180度
                    camRotation = 270;
                } else if (orientation >= 225 && orientation < 305) { //270度
                    camRotation = 0;
                }
            } else  {//如果是前摄像头
                if (orientation >= 305 || orientation < 45) { //0度
                    camRotation = 270;
                } else if (orientation >= 45 && orientation < 135) { //90度
                    camRotation = 180;
                } else if (orientation >= 135 && orientation < 225) { //180度
                    camRotation = 90;
                } else if (orientation >= 225 && orientation < 305) { //270度
                    camRotation = 0;
                }
            }
     }
}

2至15行,这是我从系统源码中偷的代码,主要是根据感应器的数据换算出手机当前的角度。具体怎么个算法。。我也不会。也没研究。。核心就是最终orientation代表了当前手机的角度。

第17和39行,就是根据前置还是后置,当前手机的角度,来决定camRotation值,而camRotation决定了拍照后,照片旋转的角度。

这也是一套脑大的判断。。。。所幸。。可以直接拿来用即可。。

定点聚焦和照片角度的问题还是不少内容。。。下面进行最终的拍照和前后置相机切换的问题:

四.完成拍照和前后置摄像头切换的功能

正常相机的拍照API如下:

mCamera.takePicture(mShutterCallback,null,mPicCallback);

mShutterCallback:是拍照快门的一个回调,可以实现拍照快门声音。

参数2和3都是图像信息的回调PictureCallback,不过是不同的数据格式。

我选择的是的jpeg格式

看一下mPicCallback里的逻辑

//拍照图形的回调
private Camera.PictureCallback mPicCallback=new Camera.PictureCallback() {
    @Override
    public void onPictureTaken(byte[] data, Camera camera) {
        if (takeCallback!=null){
            try {
            Bitmap srcBitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
            Matrix matrix = new Matrix();
            // 设置旋转角度
            matrix.setRotate(camRotation);
            // 重新绘制Bitmap,调整图片角度
            Bitmap destBitmap = Bitmap.createBitmap(srcBitmap, 0, 0, srcBitmap.getWidth(), srcBitmap.getHeight(), matrix, true);
            takeCallback.onPictureTaken(destBitmap);
            if (mCamera==null){
                mCamera=getCamera(mCameraPos);
            }
                mCamera.startPreview();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        isCanTake=true;
    }
};

第5行takeCallback是暴露给外部使用的拍照回调接口,因为对于拍照,我们需要限制其拍照间隔,和图像信息的角度旋转,这部分咱们内部直接作处理,外部调用拍照的对象不需要处理。

第7行,使用系统传入的数据源生成bitmap,8,9行,利用camRotation进行角度矫正,camRotation数值的来源前面讲过。

在第13行将矫正后的bitmap返回给外部。

17行需要注意的,拍照时,系统会停止预览,所以拍照结束后我们需要重新开启预览

22行是isCanTake标志位。用来控制拍照间隔。

现在把快门回调的逻辑直接给出来,这部分代码不是我自己写的也不复杂,就不讲解了,就是一个播放快门声音的逻辑。

//拍照快门的回调
private Camera.ShutterCallback mShutterCallback=new Camera.ShutterCallback() {
    @Override
    public void onShutter() {
        try {
            AudioManager meng = (AudioManager)getContext().getSystemService(Context.AUDIO_SERVICE);
            int volume = meng.getStreamVolume(AudioManager.STREAM_NOTIFICATION);
            if (volume != 0) {
                if (mShootSound == null) {
                    mShootSound = MediaPlayer.create(getContext(), Uri.parse("file:///system/media/audio/ui/camera_click.ogg"));
                }
                if (mShootSound != null) {
                    mShootSound.start();
                }
            }
        }catch (Exception e){
            e.getStackTrace();
        }
    }
};

现在看我暴露给外部调用的拍照API:

    public void takePicture(ITakeCallback takeCallback){
        this.takeCallback=takeCallback;
        try {
            if (isCanTake&&System.currentTimeMillis()-mOldTakeTime>500){
                mOldTakeTime=System.currentTimeMillis();
                isCanTake=false;
                mCamera.takePicture(mShutterCallback,null,mPicCallback);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

看一下ITakeCallback的声明:

interface  ITakeCallback{
    void onPictureTaken(Bitmap bitmap) ;
}

很简单。

第2行,将传入的回调对象赋值。

第4行是拍照的判断

1.当前一次拍照还未结束isCanTake=false,此时肯定不能拍照的,不然会报错的。

2.当拍照间隔小于500ms也不能拍照,这是因为部分极端的情况下,仅仅用isCanTake这个标志位并不能保证拍照不崩溃。

里面的内容很简单。

最后是摄像头切换的功能:

public void switchCam(){
    Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
    Camera.getCameraInfo(mCameraPos, cameraInfo);
    try {
        for (int i = 0; i < Camera.getNumberOfCameras(); i++) {
            Camera.getCameraInfo(i, cameraInfo);//得到每一个摄像头的信息
            //现在是后置,变更为前置
            if (isCameraBack&&cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                mCamera.stopPreview();
                mCamera.release();
                mCamera = getCamera(i);
                mCameraPos = i;
                preparePreview();
                mCamera.startPreview();
                isCameraBack=false;
                return;
            }
            //现在是前置, 变更为后置
            if (!isCameraBack&&cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
                mCamera.stopPreview();
                mCamera.release();
                mCamera = getCamera(i);
                mCameraPos = i;
                preparePreview();
                mCamera.startPreview();//开始预览
                isCameraBack=true;
               return;
            }
        }
    }catch (Exception e){
        e.printStackTrace();
    }
}

代码虽然不短,但是都是简单的逻辑。就不讲解了。

源码。链接:https://download.csdn.net/download/guliang1991/10551676

最后需要申明的是,上面的代码和我项目代码还是有区别的,上面是我对项目代码进行抽离优化的。但是因为可能存在风险,并且目前用户拍照并没有报出问题。所以并没有投入生产到上。所以并不能说,在大量用户的情况不会出问题。但是我自己大概使用了20多款手机进行测试,并未出现问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值