- 自定义相机的开发过程
定制一个自定义相机应用,通常需要完成以下步骤,其流程图如图1所示:
- 检测并访问相机资源 检查手机是否存在相机资源,如果存在,请求访问相机资源。
- 创建预览类 创建继承自SurfaceView并实现SurfaceHolder接口的拍摄预览类。此类能够显示相机的实时预览图像。
- 建立预览布局 有了拍摄预览类,即可创建一个布局文件,将预览画面与设计好的用户界面控件融合在一起。
- 设置拍照监听器 给用户界面控件绑定监听器,使其能响应用户操作(如按下按钮), 开始拍照过程。
- 拍照并保存文件 将拍摄获得的图像转换成位图文件,最终输出保存成各种常用格式的图片。
- 释放相机资源 相机是一个共享资源,必须对其生命周期进行细心的管理。当相机使用完毕后,应用程序必须正确地将其释放,以免其它程序访问使用时,发生冲突。
1. SurfaceView预览图像、拍摄照片拉伸变形
说明这个问题之前,同样先说一下几个跟相机有关的尺寸。
- SurfaceView尺寸:即自定义相机应用中用于显示相机预览图像的View的尺寸,当它铺满全屏时就是屏幕的大小。这里SurfaceView显示的预览图像暂且称作手机预览图像。
- PreViewSize:相机硬件提供的预览帧数据尺寸。预览帧数据传递给SurfaceView,实现预览图像的显示。这里预览帧数据对应的预览图像暂且称作相机预览图像。
- PictureSize:相机硬件提供的拍摄帧数据尺寸。拍摄帧数据可以生成位图文件,最终保存成.jpg或者.png等格式的图片。这里拍摄帧数据对应的图像称作相机拍摄图像。手机预览图像是直接提供给用户看的图像,它由相机预览图像生成,拍摄照片的数据则来自于相机拍
下面说下我在开发过程中遇到的三种拉伸变形现象:
- 手机预览画面中物体被拉伸变形。
- 拍摄照片中物体被拉伸变形。
- 点击拍照瞬间,手机预览画面会停顿下,此时的图像是拉伸变形的,然后预览画面恢复后图像又正常了。
现象1的原因是SurfaceView和PreViewSize的长宽比率不一致。因为手机预览视图的图像是由相机预览图像根据SurfaceView大小缩放得来的,当长宽比不一致时必然会导致图像变形。
后两个现象的原因则是PreViewSize和 PictureSize的长宽比率不一致所致,查了相关的资料,发现其具体原因跟某些手机相机硬件的底层实现有关。
总之为了避免以上几种变形现象的发生,在开发时***将 SurfaceView、PreViewSize、PictureSize三个尺寸保证长宽比例一致。
具体实现可以先通过 Supported camera.getSupportedPreviewSizes()和camera.getSupportedPictureSizes()获得相机硬件支持的所有预览和拍摄尺寸,然后在里面筛选出和SurfaceView的长宽比一致并且大小合适的尺寸,通过camera.setPrameters 来更新设置。
2 Crash(可 rua 吃)的一些问题情况
1 运行时 异常:自动对焦失败
2 运行时 异常:取 图片 失败的
3 非法 参数异常
Crash(可 rua 吃)
前两个Crash的原因是:相机硬件在聚焦和拍照前,必须要保证已经连接到 surface,并且开启相机预览, surface有收到预览数据。
如果在还没有执行camera. set PreviewDisplay 或者未调用camera. startPreview之前,就调用camera.autofocus 或camera.takepicture, 就会出现这个运行时异常。
对应到自定义相机的代码中,要注意在拍照按钮事件响应中执行camera.autofocus 或camera.takepicture
前,一定要检验camera有没有设置预览 Surfaceview并开启了相机预览。
这里有个方法可以判断预览状态:Camera.setPreviewCallback是预览帧数据的回调函数,它会在SurfaceView收到相机的预览帧数据时被调用,因此在里面可以设置是否允许对焦和拍照的标志位。
还有一点要注意,camera.takePicture() 在执行过程中会执行camera.stopPreview 来获取拍摄帧数据,表现为预览画面卡住,而如果此时用户点击了按钮的话,也就是调用camera.takepicture, 也会出现上面的crash,因此在开发时,可能还需要屏蔽拍照按钮的连续点击。
第三个crash则涉及图像的裁剪,由于要支持1:1或者4:3尺寸镜头,所以会需要对预览视图进行裁剪,由于是竖屏应用,所以裁剪区域的坐标系跟相机传感器方向是成90度角的,表现在裁剪里就是,屏幕上的x方向,对应在拍摄图像上是高度方向,而屏幕上的y方向,对应到拍摄图像上则是宽度方向。因此在计算时要一定注意坐标系的转换以及越界保护。
3 综合的小问题集合
1、预览取景反转
这种情况,再低版本sdk中,必须设置为横屏模式[android: screenOrientation =" landscape "]
2、设置为横屏模式之后,UI界面怎么伪横竖屏
添加个 OrientationEventListener 根据获取不同方向,调整UI布局,对于一些比较特殊,不变调整的控件,可以采用播放动画的方式来实现。
3. 频繁点击屏幕应用崩溃
因为应用支持点击屏幕自动聚焦功能,但在某些机子上,用户频繁点击屏幕进行自动聚焦,应用发生了崩溃。究其原因是因为在某些ROM上,当上一次聚焦没有完成时,就进行下一次聚焦,就会发生崩溃。解决方案是通过设置标志位,只有在上一次聚焦完成后,才能进行下一次聚焦。
4. 部分机子拍照完后预览画面卡住了
- 部分机子,当点击拍照完成一张照片的拍摄后,后面就停止不动了。
- 出现这种现象是因为在拍照的时候,Camera会停止Preview,
- 拍照完成后,有的机子可以恢复回来重新Preview,有的则不会。
- 因此只需在拍照完成后,手动调用一次Camera的startPreview()方法即可。
5. 预览画面不失真展示
如果预览图片的分辨率比例和手机画面上展示拍摄画面的区域比例不一致的话,就会出现画面拉伸或者压缩的现象。为了解决这个问题,取得更好的用户体验。模块在布局的时候,对屏幕展示区域是动态计算的,以保证预览区域比例与图片的分辨率比例是一致的。
6. 部分机子拍摄照片分辨率不高
开发过程中碰到过这么一种情况,在部分机子上,明明已经聚焦,手机的分辨率也很高,但是拍出的照片分辨率却很小。究其原因,就是不同的手机ROM,获取的默认的照片分辨率是不同的。有的手机默认照片分辨率高,则照片就清晰;有的默认分辨率是最低的一档,则无论你手机分辨率多高,拍出来的照片还是很模糊的。解决方案就是需要显示设置拍照的图片分辨率:
parameters.setPreviewSize(cameraResolution.x, cameraResolution.y);parameters.setPictureSize(pictureResolution.x, pictureResolution.y);
parameters.setPreview Size(cameraResolution.x, cameraResolution.y);
parameters.setPictureSize(pictureResolution.x, pictureResolution.y);
4 相机中 ANR详解 和场景案例 ?
在Android系统中,应用程序的响应由Activity Manager及Window Manager两个系统服务所监控。通常情况下,应用常出现四类情况,系统将报ANR:
主线程被阻塞,输入事件5s没有响应,input事件5s内未处理完成导致ANR发生,主要为按键和触摸事件;
1 日志关键字:InputDispatching Timeout (Dispatching - ) (Timeout )
2 BroadcastReceiver在特定时间内未处理完成导致ANR发生(限制:前台广播10s;后台广播60s);
BroadcastReceiver是Android的消息组件,用来组件之间、应用之间的消息传递,生命周期太短也不能开子线程处理耗时任务,
耗时任务一般转交给IntentService或者JobIntentService去做。
日志关键字:Timeout of broadcast BroadcastRecord (braoadcast )
3 Service在特定的时间内未处理完成导致ANR发生。(限制:前台服务20s;后台服务200s);
日志关键字:Timeout executing service ( executing )
4 内容提供者,在10s内未处理完成导致ANR发生;
日志关键字:Timeout publishing content providers ( publishing )
- ANR的发生原因
经过大量ANR案例分析,以下问题产生的典型场景:
- 主线程被其他线程锁(占比57%):调用了thread的sleep()、wait()等方法,导致的主线程等待超时。
- 系统资源被占用(占比14%):
- 其他进程系统资源(CPU/RAM/IO)占用率高,导致该进程无法抢占到足够的系统资源。
- 主线程耗时工作导致线程卡死(占比9%):例如大量的数据库读写,耗时的网络情况,高强度的硬件计算等。
- 主线程频繁进行耗时的IO操作:如数据库读写
- 系统资源已耗尽(管道、CPU、IO)
- 如何避免ANR
1):UI线程尽量只做跟UI相关的工作
2):耗时的工作(比如数据库操作,I/O,连接网络或者别的有可能阻碍UI线程的操作)把它放入单独的线程处理
3):尽量用Handler来处理UIthread和别的thread之间的交互
- 分析ANR问题方法
1 查看events_log
查看mobilelog(mei bao o)文件夹下的events_log,从日志中搜索关键字:am_anr,找到出现ANR的时间点、进程PID、ANR类型。
如日志:
07-20 15:36:36.472 1000 1520 1597 I am_anr : [0,1480,com.xxxx.moblie,952680005,Input dispatching timed out (AppWindowToken{da8f666 token=Token{5501f51 ActivityRecord{15c5c78 u0 com.xxxx.moblie/.ui.MainActivity t3862}}}, Waiting because no window has focus but there is a focused application that may eventually add a window when it finishes starting up.)]
从log中我们可以看出: 应用com.xxxx.moblie 在07-20 15:36:36.472时间,发生了一次KeyDispatchTimeout类型的ANR,它的进程号是1480. 把关键的信息整理一下:
1 ANR时间:07-20 15:36:36.472
2 进程pid:1480
3 进程名:com.xxxx.moblie
4 ANR类型:Input dispatching timed out
5 events_log 中的关键字 am_anr
我们已经知道了发生KeyDispatchTimeout的ANR是因为 input事件在5秒内没有处理完成,也就是主线程被阻塞;那么在这个时间07-20 15:36:36.472 的前5秒,也就是(15:36:30 ~15:36:31)时间段左右程序到底做了什么事情?这个简单,因为我们已经知道pid了,再搜索一下pid = 1480的日志.这些日志表示该进程所运行的轨迹,
关键的日志如下:
07-20 15:36:29.749 10102 1480 1737 D moblie-Application: [Thread:17329] receive an intent from server, action=com.ttt.push.RECEIVE_MESSAGE
07-20 15:36:30.136 10102 1480 1737 D moblie-Application: receiving an empty message, drop
07-20 15:36:35.791 10102 1480 1766 I Adreno : QUALCOMM build : 9c9b012, I92eb381bc9
07-20 15:36:35.791 10102 1480 1766 I Adreno : Build Date : 12/31/17
07-20 15:36:35.791 10102 1480 1766 I Adreno : OpenGL ES Shader Compiler Version: EV031.22.00.01
07-20 15:36:35.791 10102 1480 1766 I Adreno : Local Branch :
07-20 15:36:35.791 10102 1480 1766 I Adreno : Remote Branch : refs/tags/AU_LINUX_ANDROID_LA.UM.6.4.R1.08.00.00.309.049
07-20 15:36:35.791 10102 1480 1766 I Adreno : Remote Branch : NONE
07-20 15:36:35.791 10102 1480 1766 I Adreno : Reconstruct Branch : NOTHING
07-20 15:36:35.826 10102 1480 1766 I vndksupport: sphal namespace is not configured for this process. Loading /vendor/lib64/hw/gralloc.msm8998.so from the current namespace instead.
07-20 15:36:36.682 10102 1480 1480 W ViewRootImpl[MainActivity]: Cancelling event due to no window focus: KeyEvent { action=ACTION_UP, keyCode=KEYCODE_PERIOD, scanCode=0, metaState=0, flags=0x28, repeatCount=0, eventTime=16099429, downTime=16099429, deviceId=-1, source=0x101 }
我们再看看CPU的信息,。搜索关键字关键字: ANR IN 看看cpu中的信息是否ok。也是排查anr情况的一种(这个时候里面会有一个百分比的数值,看这个数值)
2 traces.txt 日志分析 traces
接着查看traces.txt,搜索关键字main 找到java的堆栈信息定位代码位置,最后查看源码,分析与解决问题
总结一下这分析流程:
1 首先我们在events_log中搜索am_anr,找到出现ANR的时间点、进程PID、ANR类型、
2 然后再找搜索PID,找前5秒左右的日志。过滤ANR IN 查看CPU信息,
关注系统资源信息,包括ANR发生前后的CPU,内存,IO等系统资源的使用情况。
3 接着查看traces.txt,找到java的堆栈信息定位代码位置,最后查看源码,分析与解决问题。
查看主线程状态,关注主线程是否存在耗时、死锁、等锁等问题,判断该ANR是App导致还是系统导致的
ANR问题原因可以分为两大类:一是系统资源不足导致, 二是自身代码逻辑导致,
ROM是指“只读存储器”,信息已固化在存储器中,只可读出,但无法改写。ROM的特点是把信息写入存储器以后能长期保存,不会因电源断电而丢失信息;计算机在运行过程中,只能读出只读存储器中的信息,不能再写入信息。