Android Camera开发经验总结以及踩过的那些坑
写在开头
需求方:上传试卷的时候,用户自己拍的照片有很多问题。如:不清晰、图片歪了、错误图片等。我们要是能够对拍摄照片进行识别处理就好了,能够裁切矫正就更好了,最好可以像二维码扫描一样,直接识别处理~
开发:满足你!
一、整体框架逻辑
试卷扫描模块,最核心的逻辑就是数据采集、解码识别、图片裁切,再加上对识别结果和裁切结果的处理,就构成了整个模块的主逻辑(感谢多媒体同事对图片识别与处理提供库的支持)。整个逻辑的实现如下图所示:

在模块中,除了UI线程,还开启了一个Deocde线程,用来处理图片的解码识别和裁切。这么做的原因是因为对于图片数据的处理,是比较耗时的,如果在UI线程处理,会有ANR的风险。同时采用这种处理方式,整个模块的流畅性也更加好,且模块的结构更加清晰。
那么线程之间是如何交互的呢?这里模块中是采用了最常用的Handler消息传递机制。因为通过Handler的Message可以在线程间传递较大的图片数据(注意如果在Intent的Bundle中传递较大的数据,会崩溃报错)。请看下面这段代码:
@Override
public void run() {
Looper.prepare();
handler = new DecodeHandler(activity);
handlerInitLatch.countDown();
Looper.loop();
}
上面这个方法是DecodeThread的run方法,在方法中,我们初始化了当前线程对应的Handler对象DecodeHandler。而DecodeHandler初始化是需要传入当前主线程的上下文activity,通过activity我们可以拿到主线程的Handler对象。这样的话主线程和解码线程就建立了联系,它们之间就可以方便得进行消息传递了。最终实现的模块采集界面如下所示:

二、模块开发相关实现
整个扫码拍照模块的逻辑比较琐碎,就不一一说明了。以下是整理的几个开发中比较关键的点和Camera硬件开发一些经验,在这里做记录,避免以后重复造轮子。
闪光灯设置
- 开启闪光灯
public void turnOnFlash(){
if(camera != null){
try {
Camera.Parameters parameters = camera.getParameters();
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
camera.setParameters(parameters);
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 关闭闪光灯
public void turnOffFlash(){
if(camera != null){
try {
Camera.Parameters parameters = camera.getParameters();
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
camera.setParameters(parameters);
} catch (Exception e) {
e.printStackTrace();
}
}
}
预览图片分辨率选择
预览图片的分辨率选择逻辑是:有1920*1080则选之,否则选硬件支持的最大的分辨率,且满足图片比例为16:9
private static Point findBestPreviewSizeValue(List<Camera.Size> sizeList, Point screenResolution) {
int bestX = 0;
int bestY = 0;
int size = 0;
for(int i = 0; i < sizeList.size(); i ++){
// 如果有符合的分辨率,则直接返回
if(sizeList.get(i).width == DEFAULT_WIDTH && sizeList.get(i).height == DEFAULT_HEIGHT){
Log.d(TAG, "get default preview size!!!");
return new Point(DEFAULT_WIDTH, DEFAULT_HEIGHT);
}
int newX = sizeList.get(i).width;
int newY = sizeList.get(i).height;
int newSize = Math.abs(newX * newX) + Math.abs(newY * newY);
float ratio = (float)newY / (float)newX;
Log.d(TAG, newX + ":" + newY + ":" + ratio);
if (newSize >= size && ratio != 0.75) { // 确保图片是16:9的
bestX = newX;
bestY = newY;
size = newSize;
} else if (newSize < size) {
continue;
}
}
if (bestX > 0 && bestY > 0) {
return new Point(bestX, bestY);
}
return null;
}
拍照图片分辨率选择
在硬件支持的拍照图片分辨率列表中,拍照图片分辨率选择逻辑:
- 有1920*1080则选之
- 选择大于屏幕分辨率且图片比例为16:9的
- 选择图片分辨率尽可能大且图片比例为16:9的
private static Point findBestPictureSizeValue(List<Camera.Size> sizeList, Point screenResolution){
List<Camera.Size> tempList = new ArrayList<>();
for(int i = 0; i < sizeList.size(); i ++){
// 如果有符合的分辨率,则直接返回
if(sizeList.get(i).width == DEFAULT_WIDTH && sizeList.get(i).height == DEFAULT_HEIGHT){
Log.d(TAG, "get default picture size!!!");
return new Point(DEFAULT_WIDTH, DEFAULT_HEIGHT);
}
if(sizeList.get(i).width >= screenResolution.x && sizeList.get(i).height >= screenResolution.y){
tempList.add(sizeList.get(i));
}
}
int bestX = 0;
int bestY = 0;
int diff = Integer.MAX_VALUE;
if(tempList != null && tempList.size() > 0){
for(int i = 0; i < tempList.size(); i ++){
int newDiff = Math.abs(tempList.get(i).width - screenResolution.x) + Math.abs(tempList.get(i).height - screenResolution.y);
float ratio = (float)tempList.get(i).height / tempList.get(i).width;
Log.d(TAG, "ratio = " + ratio);
if(newDiff < diff && ratio != 0.75){ // 确保图片是16:9的
bestX = tempList.get(i).width;
bestY = tempList.get(i).height;
diff = newDiff;
}
}
}
if (bestX > 0 && bestY > 0) {
return new Point(bestX, bestY);
}else {
return findMaxPictureSizeValue(sizeList);
}
}
预览模式循环自动对焦
预览模式时,支持自动对焦。当前处理逻辑是在AutoFocusCallback的回调方法onAutoFocus中,延迟发送Message信息。这样在上一次聚焦完成后,固定时间的延迟后会发送下一次的自动聚焦消息,如此达到循环聚焦的目的。
@Override
public void onAutoFocus(boolean success, Camera camera) {
Log.d(TAG, "onAutoFocus");
PaperScanConstant.isAutoFocusSuccess = true;
if (autoFocusHandler != null) {
Message message = autoFocusHandler.obtainMessage(autoFocusMessage, success);
autoFocusHandler.sendMessageDelayed(message, AUTOFOCUS_INTERVAL_MS);
autoFocusHandler = null;
} else {
Log.d(TAG, "Got auto-focus callback, but no handler for it");
}
}
预览画面不失真展示
如果预览图片的分辨率比例和手机画面上展示拍摄画面的区域比例不一致的话,就会出现画面拉伸或者压缩的现象。为了解决这个问题,取得更好的用户体验。模块在布局的时候,对屏幕展示区域是动态计算的,以保证预览区域比例与图片的分辨率比例是一致的。
三、模块开发中的那些坑
扫码模块开发,因为是跟手机硬件Camera打交道,基于目前市场中Android手机众多的型号和搭载的五花八门的ROM,没坑那是不可能的!!!下面是本模块开发过程中的相关坑。
部分机子拍摄照片分辨率不高
开发过程中碰到过这么一种情况,在部分机子上,明明已经聚焦,手机的分辨率也很高,但是拍出的照片分辨率却很小。究其原因,就是不同的手机ROM,获取的默认的照片分辨率是不同的。有的手机默认照片分辨率高,则照片就清晰;有的默认分辨率是最低的一档,则无论你手机分辨率多高,拍出来的照片还是很模糊的。解决方案就是需要显示设置拍照的图片分辨率:
parameters.setPreviewSize(cameraResolution.x, cameraResolution.y);
parameters.setPictureSize(pictureResolution.x, pictureResolution.y);
部分机子拍摄照片发生了旋转
还是由于Android手机碎片化的问题,每个手机默认拍照的旋转角度是不一样的。刚开始模块中是按照默认旋转90度处理,在大多数机子上是没有问题的。但是在碰到Nexus 5X的时候就出问题了,图片上下导致了。查阅了相关资料,Google官方提供了下面的方法,解决了这个问题。
public void setCameraDisplayOrientation(int cameraId, android.hardware.Camera camera) {
android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
android.hardware.Camera.getCameraInfo(cameraId, info);
int rotation = BaseApplication.getInstance().getCurrentActivity().getWindowManager().getDefaultDisplay()
.getRotation();
int degrees = 0;
switch (rotation) {
case Surface.ROTATION_0: degrees = 0; break;
case Surface.ROTATION_90: degrees = 90; break;
case Surface.ROTATION_180: degrees = 180; break;
case Surface.ROTATION_270: degrees = 270; break;
}
int result;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360; // compensate the mirror
} else { // back-facing
result = (info.orientation - degrees + 360) % 360;
}
// 记录本机子相机的旋转角度
PaperScanConstant.cameraRotation = result;
camera.setDisplayOrientation(result);
}
private int findFrontFacingCameraID() {
int cameraId = -1;
// Search for the back facing camera
int numberOfCameras = Camera.getNumberOfCameras();
for (int i = 0; i < numberOfCameras; i++) {
Camera.CameraInfo info = new Camera.CameraInfo();
Camera.getCameraInfo(i, info);
if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
Log.d(TAG, "Camera found");
cameraId = i;
break;
}
}
return cameraId;
}
频繁点击屏幕应用崩溃
因为应用支持点击屏幕自动聚焦功能,但在某些机子上,用户频繁点击屏幕进行自动聚焦,应用发生了崩溃。究其原因是因为在某些ROM上,当上一次聚焦没有完成时,就进行下一次聚焦,就会发生崩溃。解决方案是通过设置标志位,只有在上一次聚焦完成后,才能进行下一次聚焦。
第三方ROM禁止了应用的摄像头权限
有些第三方ROM会有自己的权限管理机制,当应用的摄像头权限被禁止了,进入扫码页,会发生崩溃。这样的交互体验肯定不是很好,交互要求这边权限被禁止以后,还是需要有一个温和的提示,提醒用户去设置页面重新赋予应用摄像头权限。但是系统也没有提供接口说当前应用这个权限被禁止了。因此模块中采用了一个折中的方案,监狱应用没有摄像头权限时候,开启摄像头会崩溃。因此我们捕获开启Camera的异常,在捕获异常时候弹框提醒用户去开启权限。
try {
CameraManager.get().openDriver(surfaceHolder);
} catch (Throwable tr){
showOpenCameraErrorDialog();
return;
}
Pad进入扫码页应用崩溃
实际上线时候,发现用户使用pad的话,一进入扫码页面就崩溃。因为我们应用首次进入扫码页面默认是开启设备闪光灯的。但是pad没有闪光灯,因此就崩溃了。刚开始用如下方式检测设备是否支持闪光灯:
getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH)
但是失败了。原因是好多pad的ROM是从手机ROM改过去的,有可能改得不是那么彻底。所以在Pad上调用如上代码进行判断时,还是会返回true。这是只能求助于try catch了。就是在开关闪光灯的时候进行异常捕获,这样在Pad上开关闪光灯崩溃问题就解决了。
部分机子拍照后闪光灯自动关闭
部分机子,在闪光灯开启的状态下,点击拍照按钮,闪光灯关闭了。目前没有找到原因,只能在模块中加了特殊处理。针对当前有此问题的手机,拍照完后主动再去开关一次闪光灯,这样拍照完成后,闪光灯还是可以亮着。只是在拍照的过程中,会出现闪光灯闪烁的情况。
部分机子拍照完后预览画面卡住了
部分机子,当点击拍照完成一张照片的拍摄后,后面就停止不动了。出现这种现象是因为在拍照的时候,Camera会停止Preview,拍照完成后,有的机子可以恢复回来重新Preview,有的则不会。因此只需在拍照完成后,手动调用一次Camera的startPreview()方法即可。
本文来自网易云社区,经作者郑睿授权发布。
原文地址:Android Camera开发经验总结以及踩过的那些坑
更多网易研发、产品、运营经验分享请访问网易云社区。
Android Camera开发经验总结以及踩过的那些坑的更多相关文章
- React-Native android在windows下的踩坑记
坑很多,跳之前做好准备.没有VPN的同学请浏览完本文后慎行. 你需要先安装最新版本的node.js(我最后使用的是v4.1.2),前往官网下载>> 注:我win7已经安装过Visual ...
- 【Android】Android Camera原始帧格式转换 —— 获取Camera图像(一)
概述: 做过Android Camera图像采集和处理的朋友们应该都知道,Android手机相机采集的原始帧(RawFrame)默认是横屏格式的,而官方API有没有提供一个设置Camera采集图像的 ...
- android camera setMeteringArea详解
摘要: 本文为作者原创,未经允许不得转载:原文由作者发表在博客园:http://www.cnblogs.com/panxiaochun/p/5802814.html setMeteringArea() ...
- Android — Camera聚焦流程
原文 http://www.cnphp6.com/archives/65098 主题 Android Camera.java autoFocus()聚焦回调函数 @Override public v ...
- android camera setParameters failed 类问题分析总结
在 monkey test 测试中出现了一例 RuntimeException ,即 setParameters failed. LOG显示为:09-01 18:47:17.348 15656 156 ...
- Android Camera 相机程序编写
Android Camera 相机程序编写 要自己写一个相机应用直接使用相机硬件,首先应用需要一个权限设置,在AndroidManifest.xml中加上使用设备相机的权限: <uses-per ...
- Android Camera 使用小结
Android手机关于Camera的使用,一是拍照,二是摄像,由于Android提供了强大的组件功能,为此对于在Android手机系统上进行Camera的开发,我们可以使用两类方法:一是借助Inten ...
- Android Camera拍照 压缩
http://www.linuxidc.com/Linux/2014-12/110924.htm package com.klp.demo_025; import java.io.ByteArrayI ...
- Android Camera 流程梳理
毕业已经快两年了,一直没有写博客的习惯,这是第一篇,以后要慢慢养成这个习惯.毕业之后一直在做相机,先简单的梳理下Android Camera的流程. Android Camera 是一个client/ ...
随机推荐
- springboot整合最新版dubbo以及dubbo-admin的安装
一.安装前准备 由于dubbo被阿里捐献给了apache,这次安装admin时,参考网上的资料,地址还是停留在之前的链接,踩了不少坑,这里记录下. dubbo-admin下载地址: 地址一:https ...
- 利用maven实现差异化配置
回顾过去 生产环境,测试环境,开发环境在不同的环境下会有各种各样的配置,比如数据库链接地址,账户名,密码等等.不同环境下都需要配置,但是配置却又不同.以前分享过一篇文章,介绍了我之前A公司的差异化配置 ...
- UNITY 内存问题资料收集
1,https://blog.csdn.net/wetest_tencent/article/details/52130703 2,http://blog.51cto.com/13638120/208 ...
- ELK 日志管理系统,再次尝试记录
简介: 第二次尝试 ELK 记录... 工作流程: 1.客户端的 Logstash 将日志信息采集到之后传输给 Redis 做消息队列 2.然后服务端的 Logstash 将日志从 Redis 中取出 ...
- Keepalived 角色选举
简介: 1.在 Keepalived 集群中,其实并没有严格意思上的主.备节点,虽然可以在 keepalived.conf 中定义 state 选项为 MASTER 状态,但是这并不意味着此节点就一直 ...
- 在struts2.3.4.1中使用注解、反射、拦截器实现基于方法的权限控制
权限控制是每一个系统都应该有的一个功能,有些只需要简单控制一下就可以了,然而有些却需要进行更加深入和细致的权限控制,尤其是对于一些MIS类系统,基于方法的权限控制就更加重要了. 用反射和自定义注解来实 ...
- Unity几个有用的游戏运动特效
本文摘要 本文主要记录了我在开发格斗游戏时用到的几个运动特效,可以方便地表现武器挥动.运动模糊和其他一些特效.灵活使用可以大幅提升格斗游戏的视觉效果和感染力.有关Unity的其他话题也可以查阅我的其他 ...
- 274. H-Index论文引用量
[抄题]: Given an array of citations (each citation is a non-negative integer) of a researcher, write a ...
- Maven详解【面试+工作】 各种安装 没用
1 Maven介绍1.1 项目开发中遇到的问题 1.都是同样的代码,为什么在我的机器上可以编译执行,而在他的机器上就不行? 2.为什么在我的机器上可以正常打包,而配置管理员却打不出来? 3.项目组加入 ...
- OpenCV之设计模式
参考资料: http://hahack.com/codes/opencv-and-mvc/ http://blog.csdn.net/yzhang6_10/article/details/508084 ...