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/ ...
随机推荐
- GitHub从注册到使用
GitHub是最流行的代码库,里面存储着丰富的优秀的开源代码,不仅如此,作为一款免费的代码存储利器也是很牛逼,支持各种编程语言,代码显示效果堪称完美,可以随时随地查看自己记录的笔记 GitHub的好处 ...
- springboot 的定时任务使用
定时任务在Spring Boot中的集成 在启动类中加入开启定时任务的注解: 在SpringBoot中使用定时任务相当的简单.首先,我们在启动类中加入@EnableScheduling来开启定时任务. ...
- Proxmox 安装 dsm 黑群 备忘
备忘:Proxmox 虚拟机使用 E1000网卡(用Virlo找不到引导),直通数据硬盘 . 使用的引导文件是 DS918+_6.21-23824-1.04b.img 虚拟机启动使用第三项EX ...
- book pile SGU - 271
有n本书从上到下摞在一起,有两种操作.ADD(C)表示把一本新书C放到这一摞书的最顶上,ROTATE表示将前K本书进行反转.在一系列操作后输出最后书的顺序 分析: 当时听别人讲这个题的时候很懵逼,后来 ...
- rsyslog收集nginx日志配置
rsyslog日志收集配置 rsyslog服务器收集各服务器的日志,并汇总,再由logstash处理 请查看上一篇文章 http://bbotte.blog.51cto.com/6205307/16 ...
- 在linux下设置定时任务
输入命令 crontab -l(列出当前有哪些定时任务)crontab -e(修改这些任务)然后编辑:添加定时任务(编辑命令是vi编辑器的,tips:i,insert;:wq,保存并退出)格式:* * ...
- Linux ls命令详解-乾颐堂CCIE
ls命令用法举例: 例一:列出/home文件夹下的所有文件和目录的详细资料: 1 ls -l -R /home 命令参数之前要有一短横线“-”, 上面的命令也可以这样写: 1 ls -lR /ho ...
- Python爬虫入门二之爬虫基础了解
1.什么是爬虫 爬虫,即网络爬虫,大家可以理解为在网络上爬行的一直蜘蛛,互联网就比作一张大网,而爬虫便是在这张网上爬来爬去的蜘蛛咯,如果它遇到资源,那么它就会抓取下来.想抓取什么?这个由你来控制它咯. ...
- C语言访问mysql数据库
mysql中新建的数据库为hyx,hyx中的表为my_schema,表中的数据为下图: 编写代码,访问表中的数据,测试代码如下: #include "stdafx.h" #incl ...
- YII2 rule exist unique
['mobile', 'exist', 'targetClass' => 'xmobile\modules\v1\models\BuyerList', 'message' => '当前用户 ...