Android 相机开发中的坑

android 开发中经常遇到拍照的需求,android 系统帮我们把相机封装成了Camera类,除了Camera还有个SurfaceView 需要用到,核心的就这2个。

# 先说下简单实现,在说里面的坑

一般实现是写个自定义view  例如(CameraView)继承SurfaceView在View 的构造方法中完成相机的初始化 重要的函数 就是 Camera.open() 和 Camera.open(i); 前一个打开时直接打开后置摄像头,后面的打开摄像头方法可以选择 打开具体的摄像头  (前置或后置)  代码实现如下:

/**
* 根据当前照相机状态(前置或后置),打开对应相机
*/
private boolean openCamera() {
    if (mCamera != null) {
        mCamera .stopPreview();
        mCamera .release();
        mCamera = null;
    }
    if (mIsFrontCamera) {
        Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
        for ( int i = 0 ; i < Camera.getNumberOfCameras (); i++) {
            Camera.getCameraInfo(i, cameraInfo);
            if (cameraInfo. facing == Camera.CameraInfo.CAMERA_FACING_FRONT ) {
                try {
                    mCamera = Camera. open(i) ;
                } catch (Exception e) {
                    mCamera = null;
                    return false;
                }

}
        }
    } else {
        try {
            mCamera = Camera.open();
        } catch (Exception e) {
            mCamera = null;
            return false;
        }

}
    return true;

}

这里多说一句,因为系统相机这个资源全局是只有一个的,如果正在使用,就会进入锁定状态,这个时候想重新打开就必须先释放。其他代码都很简单

第二步 就是把SurfaceView 和Camera 关联起来了

使用SurfaceView 的时候需要设置一个SurfaceHolder.Callback(不清楚可以去看下SurfaceView的用法) 然后再SurfaceView 创建时候

getHolder().addCallback(callback);

这个callback 就是surfaceView的一些生命周期方法了,为什么要像这样这么麻烦的关联起来,也是和SurfaceView 特性有关了,这里也不多说了。

Callback的代码 实现如下:

private SurfaceHolder.Callback callback = new SurfaceHolder.Callback() {
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        try {
            if (mCamera == null) {
                openCamera();
            }
            setCameraParameters();
            mCamera .setPreviewDisplay(getHolder());
        } catch (Exception e) {
        }
        if (mCamera != null) {
            mCamera .startPreview();
        }
    }
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
                               int height) {
        updateCameraOrientation();
    }
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        //停止录像
        if (mCamera != null) {
            mCamera .stopPreview();
            mCamera .release();
            mCamera = null;
        }
    }

};

surfaceCreated 主要是设置参数 预览等,surfaceChanged回调里面处理旋转拍照的情况 (这个后面再说),surfaceDestroyed 就是释放资源了。

写到这一步相机差不多就可以预览了,就还差个拍照方法

public void takePicture(final Camera.PictureCallback callback, TakePictureListener listener) {
    if (mCamera == null) return;
    mCamera .autoFocus(new Camera.AutoFocusCallback() {
        @Override
        public void onAutoFocus(boolean success, Camera camera) {
            if (mCamera != null) {
                mCamera.takePicture( null, null, callback) ;
            }
        }
    });

}

这里加了段拍照之前先对焦一次的逻辑。 ok,到这里核心代码差不多写完了。 下面说下其中的坑:

  1. 相机的权限 android.permission.CAMERA android 4.0 之后相机权限属于运行时权限,所以即使声明了权限也可能用户主动拒绝,导致Camera.open(); 失败 (所以这里还有个坑,打开相机失败是用户主动拒绝了权限,还是相机被占用或者其他原因失败 区分不出来)
  2. 相机预览时候图片变形 这里就是 Camera.Parameters parameters = mCamera .getParameters(); 相机参数设置原因了  只有当SufaceView的(当前控件)宽高比和相机预览设置的宽高比还有生成照片的宽高比一直时预览才不会变形 代码如下:

/**
     * 设置照相机参数
     */
   
private void setCameraParameters() {
        if (mCamera == null) return;
        Camera.Parameters parameters = mCamera.getParameters();
        // 选择合适的预览尺寸
        List<Camera.Size> sizeList = parameters.getSupportedPreviewSizes();
        Collections. sort(sizeList , new Comparator<Camera.Size>() {
            @Override
            public int compare(Camera.Size lhs, Camera.Size rhs) {
                return rhs. height - lhs.height ;
            }
        });
        Camera.Size tempSize = null;
        for (Camera.Size size : sizeList) {
            if (size.width * ScreenUtil. getScreenWidth(getContext()) == ScreenUtil.getScreenHeight(getContext()) * size. height) {
                tempSize = size;//  parameters.setPreviewSize(size.width, size.height);
                break;
            }
        }
        if (tempSize == null) {
            for (Camera.Size size : sizeList) {
                //小于100W像素
                if (size. width * 16 == 9 * size. height) {
                    tempSize = size ;//   parameters.setPictureSize(size.width, size.height);
                    break;
                }
            }
        }
        if (tempSize == null) {
            for (Camera.Size size : sizeList) {
                //小于100W像素
                if (size. width * 9 == 16 * size. height) {
                    tempSize = size ;//   parameters.setPictureSize(size.width, size.height);
                    break;
                }
            }
        }
        if (tempSize == null) {
            tempSize = sizeList.get(0) ;
        }
        parameters.setPreviewSize(tempSize.width , tempSize.height );
        tempSize = null;
        //设置生成的图片大小
        sizeList = parameters.getSupportedPictureSizes() ;
        Collections. sort(sizeList , new Comparator<Camera.Size>() {
            @Override
            public int compare(Camera.Size lhs, Camera.Size rhs) {
                return rhs. height - lhs.height ;
            }
        });
        if (sizeList.size() > 0) {
            for (Camera.Size size : sizeList) {
                //小于100W像素
                if (size. width * ScreenUtil.getScreenWidth (getContext()) == ScreenUtil.getScreenHeight(getContext()) * size. height) {
                    tempSize = size ;//   parameters.setPictureSize(size.width, size.height);
                    break;
                }
            }
        }
        if (tempSize == null) {
            for (Camera.Size size : sizeList) {
                //小于100W像素
                if (size. width * 16 == 9 * size. height) {
                    tempSize = size ;//   parameters.setPictureSize(size.width, size.height);
                    break;
                }
            }
        }
        if (tempSize == null) {
            for (Camera.Size size : sizeList) {
                //小于100W像素
                if (size. width * 9 == 16 * size. height) {
                    tempSize = size ;//   parameters.setPictureSize(size.width, size.height);
                    break;
                }
            }
        }
        if (tempSize == null) {
            tempSize = sizeList.get(0) ;
        }
        parameters.setPictureSize(tempSize.width , tempSize.height );
        if (parameters.getSupportedFocusModes().contains(Camera.Parameters. FOCUS_MODE_CONTINUOUS_PICTURE )) {
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE) ;
        }
  //       mCamera .cancelAutoFocus();//只有加上了这一句,才会自动对焦。 然并卵!

//        //设置图片格式
        parameters.setPictureFormat(ImageFormat. JPEG);
        parameters.setJpegQuality( 100);
        parameters.setJpegThumbnailQuality( 100);
        //自动聚焦模式
        parameters.setFocusMode(Camera.Parameters. FOCUS_MODE_AUTO);
        mCamera .setParameters(parameters);
        //开启屏幕朝向监听
        startOrientationChangeListener() ;

}

这里建议尺寸就和屏幕大小一样的 如果产品需求的预览范围比较小,就用其他view 去遮挡其他区域(这么做会埋下另外一个坑,但是基本所有android都能适配了)。

  1. 第三坑 预览时候手机横着或者旋转 发现预览的图片不对 。这里需要动态设置相机的预览参数,也就是前面SurfaceHolder.Callback里面surfaceChanged改变时候处理的updateCameraOrientation();代码实现如下:

/**
* 根据当前朝向修改保存图片的旋转角度
*/
private void updateCameraOrientation() {
    if (mCamera != null) {
        Camera.Parameters parameters = mCamera .getParameters();
        //rotation参数为 0、90、180、270。水平方向为0。
        int rotation = 90 + mOrientation == 360 ? 0 : 90 + mOrientation ;
        //前置摄像头需要对垂直方向做变换,否则照片是颠倒的
        if (mIsFrontCamera) {
            if (rotation == 90) rotation = 270;
            else if (rotation == 270) rotation = 90 ;
        }
        Log.e( "TAG", "rotation=" + rotation + "mOrientation=" + mOrientation);

parameters.setRotation(rotation) ;//生成的图片转90°
        //预览图片旋转90°
        mCamera .setDisplayOrientation(90) ;//预览转90°
        mCamera .setParameters(parameters);
    }

}

需要根据前置和后置摄像头来区分 ,还要监听屏幕朝向来设置方向 (主要用于横竖屏切换 如果没这个需求 可以不加)

/**
* 启动屏幕朝向改变监听函数 用于在屏幕横竖屏切换时改变保存的图片的方向
*/
private void startOrientationChangeListener() {
    OrientationEventListener mOrEventListener = new OrientationEventListener(getContext()) {
        @Override
        public void onOrientationChanged(int rotation) {

if (((rotation >= 0) && (rotation <= 45)) || (rotation > 315 )) {
                rotation = 0;
            } else if ((rotation > 45 ) && (rotation <= 135)) {
                rotation = 90;
            } else if ((rotation > 135 ) && (rotation <= 225)) {
                rotation = 180;
            } else if ((rotation > 225 ) && (rotation <= 315)) {
                rotation = 270;
            } else {
                rotation = 0;
            }
            if (rotation == mOrientation)
                return;
            mOrientation = rotation;
            updateCameraOrientation() ;
        }
    };
    mOrEventListener.enable() ;

}

  1. 三星手机拍照 旋转了90度问题 。 三星手机部分机型摄像头默认是横着的,解决方法是记录下拍照时候的角度,然后生成照片后比较下照片的长和宽,如果和预计的不一样就像左或右旋转90度(根据拍照时候的角度选择左右)。

到这里相机基本功能差不多就完成了,还有很多其他的坑没说到,但都是比较好解决了。还有就是对焦很缩放 闪光灯等功能 ,下篇文章再说吧。

时间: 2025-01-09 20:32:59

Android 相机开发中的坑的相关文章

Unity3d Android Http 开发中的坑(吐槽

在一般的U3D网络开发中,直接使用WWW类便足够正常使用,但我在发现使用WWW下载大文件时,会导致整个程序卡顿的情况(不清楚是否我个人电脑问题),所以干脆使用HttpWebRequest/HttpWebResponse + await/async 来代替WWW. U3D中还是在使用比较低的Mono版本,所以是不支持(await/async)的.准确来说是不支持Task<T>,而且还有些BUG(我自个能确定的就有两个),现在最希望就是U3D更新Mono版本,写代码就可以更爽了! 无奈之下,只有参

Android相机开发那些坑

版权声明:本文由王梓原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/168 来源:腾云阁 https://www.qcloud.com/community 最近我负责开发了一个跟Android相机有关的需求,新功能允许用户使用手机摄像头,快速拍摄特定尺寸(1:1或3:4)的照片,并支持在拍摄出的照片上做贴纸相关的操作.由于之前没有接触过Android相机开发,所以在整个开发过程中踩了不少坑,费了不少时间和精力.这篇文章总

Android实际开发中的bug总结与解决方法(一)

                                                                             Android实际开发中的bug总结与解决方法(一) Android开发中有很多bug,我们是完全可以在线下避免的,不要等到线上报的BUG的再去修复.下面是我在实际开发中遇到过的bug和解决方法. BUG 1: java.lang.RuntimeException: Unable to start activity ComponentInfo

Android相机开发详解(一)

Android相机开发详解(一) 请支持原创,尊重原创,转载请注明出处:http://blog.csdn.net/kangweijian(来自kangweijian的csdn博客) Android相机开发能够实现打开相机,前后摄像头切换,摄像预览,保存图片,浏览已拍照图片等相机功能. Android相机开发详解(一)主要实现打开相机,摄像预览,前后置摄像头切换,保存图片等四个功能. Android相机开发详解(二)主要实现翻页浏览相片,触控缩放浏览图片,删除图片,发送图片等四个功能. Andro

开源分享二(Android相机开发实战)

开源分享二(Android相机开发实战) 开源分享 一(StickerCamera + 仿微信多图选择) 前言 上篇博文给大家分享了两个非常实用的项目功能模块,不知道大伙感觉如何?有木有一种臭袜子味扑鼻,酸爽的赶脚!!!贱笑贱笑了~ ~ OK!不扯淡了,言归正传.本文将主要为大家介绍Android中自定义相机的开发,做Android应用的童鞋应该都知道,在应用中使用相机功能有两种方式: 调用Camera API 自定义相机 调用系统相机 由于需求不同,所以选择的方案固然也不同,至于第二种调用系统

Android应用开发中的风格和主题(style,themes)(转)

Android应用开发中的风格和主题(style,themes) 越来越多互联网企业都在Android平台上部署其客户端,为了提升用户体验,这些客户端都做得布局合理而且美观.......Android的Style设计就是提升用户体验的关键之一.Android上的Style分为了两个方面: Theme是针对窗体级别的,改变窗体样式: Style是针对窗体元素级别的,改变指定控件或者Layout的样式.      Android系统的themes.xml和style.xml(位于\base\core

Android应用开发中三种常见的图片压缩方法

Android应用开发中三种常见的图片压缩方法,分别是:质量压缩法.比例压缩法(根据路径获取图片并压缩)和比例压缩法(根据Bitmap图片压缩). 一.质量压缩法 private Bitmap compressImage(Bitmap image) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); image.compress(Bitmap.CompressFormat.JPEG, 100, baos);//质量压缩方法,这里

android游戏开发中图形绘制:Canvas和Paint的使用

android游戏开发中,使用android.graphics中的类来绘制2D向量图和文字. 一 画布Canvas 在Android中的绘图应该继承View组件,并重写它的onDraw(Canvas canvas)方法. Canvas代表指定View上的画布,常用方法如图: 二 画刷Paint Paint代表Canvas上的画刷,主要用于绘制风格,包括画刷颜色.画刷笔触粗细.填充风格等. 大体上可以分为两类,一类与图形绘制相关,一类与文本绘制相关. 常用方法如图: 三 路径Path Path表示

Android应用开发中对Bitmap的内存优化

在Android应用里,最耗费内存的就是图片资源.而且在Android系统中,读取位图Bitmap时,分给虚拟机中的图片的堆栈大小只有8M,如果超出了,就会出现OutOfMemory异常.所以,对于图片的内存优化,是Android应用开发中比较重要的内容. 1) 要及时回收Bitmap的内存 Bitmap类有一个方法recycle(),从方法名可以看出意思是回收.这里就有疑问了,Android系统有自己的垃圾回收机制,可以不定期的回收掉不使用的内存空间,当然也包括Bitmap的空间.那为什么还需