接着上一篇接口介绍继续,这一片主要是看一下代码结构,本身自己缺乏这方面的能力,借着看zxing调用Camera组件的源码,也写了一个小demo,下面介绍一下代码中如何调用Camera进行拍照。最后会贴上demo地址。
目录:
- 功能介绍
- 根据功能设计类
- 流程分析
1. 功能介绍
这个demo主要实现了调用Camera组件进行拍照,并保存图片的功能。一下详细的功能拆分:
- 调用相机预览;
- 预览过程中相机自动对焦;
- 调用相机拍照;
- 保存图片;
- 退出Activity,释放相机。
2. 根据功能设计类
-
显示界面——CameraActivity,用于相机预览,根据上一篇。。。我们知道,相机预览需要使用到SurfaceView,所以,CameraActivity中会有一个SurfaceView变量。同时,我们需要监听SurfaceView中的Surface的生命周期,保证在其生命周期之内,调用Camera组件的接口
setPreviewDisplay(holder)
(设置显示预览界面的Surface),startPreview()
(开启预览),stopPreview()
(停止预览)进行相应的操作,所以CameraActivity还需要实现SurfaceHolder.Callback接口。 -
管理Camera的各个接口——CameraManager,上一篇。。。介绍过调用Camera的接口,CameraManager需要对Camera接口进行封装,提供给Activity使用,主要包括一下成员函数:
openDriver(SurfaceHolder holder)
:连接相机,并设置相机基础参数,保证预览画面正常。
closeDriver()
:释放相机。
setFlashLight(boolean open)
:打开或关闭闪关灯。
startPreview()
:开始预览,告诉相机硬件将预览帧数据显示到屏幕上。
stopPreview()
:停止预览,告诉相机硬件停止绘制预览帧数据。
requestPreviewFrame(Handler handler, int message)
:获取下一次预览帧数据,为什么说“下一次”呢,因为预览数据是在不断更新的,这样才能保证我们在移动手机的时候,屏幕能显示你移动之后画面。这里调用的是mCamera.setOneShotPreviewCallback(PreviewCallback)
,这里只是设置了一个回调接口PreviewCallback,当我们调用这个方法设置回调接口之后,下一次预览帧数据显示到屏幕上的时候,就会回调PreviewCallback接口的onPreviewFrame(byte[] data, Camera camera)方法,我们会获取到一个byte[]类型的数据,这就是预览的帧数据。为什么会传入Handler和Message呢,就是为了在回调接口被调用的时候,用这里传入的Handler处理byte[]数据。
requestAutoFocus(Handler handler, int message)
:自动对焦,这里需要调用Camera的autoFocus(AutoFocusCallback cb)
方法,该方法会设置自动对焦的回调接口AutoFocusCallback,然后调用native_autoFocus()
,告诉相机硬件自动对焦。底层硬件对焦之后,会回调AutoFocusCallback
接口的onAutoFocus(boolean success, Camera camera)
方法,返回对焦知否成功,这时传入Handler就可以针对对焦状态进行下一步操作。需要注意的是,调用autoFocus(AutoFocusCallback cb)
方法才会自动对焦一次,但是我们经常会移动手机,改变物体和摄像头之间的距离,所以需要隔一段时间请求一次自动对焦,这样才能保证相机一直都能自动对焦。当然,也可以提供手动对角的方法。
requestCapture(Handler handler, int message)
:告诉相机硬件,要拍照,这里会调用takePicture(ShutterCallback shutter, PictureCallback raw,PictureCallback jpeg)
方法,ShutterCallback接口会在拍照的瞬间回调,可以在这里设置拍照的声音,第二个参数PictureCallback接口的回调方法,会返回未压缩处理的原生数据byte[],可以在这里对照片的原生数据进行压缩处理,转换成我们需要的格式,第三个参数也是PictureCallback类型,这里的回调接口返回的数据也是byte[]类型的,但是将原生的数据压缩成了jepg格式。这里我们主要关注第三个参数,传入的Handler和Message可以在PictureCallback的回调方法onPictureTaken(byte[] data, Camera camera)
进行下一步处理。 -
预览回调类——PreviewCallback,需要实现接口
Camera.PreviewCallback
,当然,如果程序中不需要对预览数据进一步操作,就不用自定义这个回调接口了。如果需要对预览数据进行处理(比如扫描识别二维码,就是获取预览帧数据进行识别),就可以创建自己的预览回调类,实现函数onPreviewFrame(byte[] data, Camera camera)
,在这里获取data进行处理。 -
自动对焦的回调类——AutoFocusCallback,实现接口
Camera.AutoFocusCallback
,可以在onAutoFocus(boolean success, Camera camera)
方法中获取对焦的结果。 -
拍照的回调类——PictureCallback,实现接口
Camera.PictureCallback
,然后在方法onPictureTaken(byte[] data, Camera camera)
中获取拍照的图片数据,将其保存成图片。 -
辅助Activity,处理各个回调方法返回的结果——CaptureHandler,以上几个回调接口的结果会交有CaptureHandler进行处理,这样
CameraActivity
就可以只负责界面显示,CaptureHandler
可以处理业务逻辑。
以下各个类之间的静态结构:
3. 流程分析
为了帮助理解,我们看一下类之间的调用流程:
最后看一下代码中可能需要注意的几个地方吧。
1. 在CameraActivity的onResume中初始化相机。
protected void onResume() {
super.onResume();
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
//初始化Camera
initCamera();
} else {
ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.CAMERA }, REQUEST_PERMISSIONS);
}
}
这里主要是,申请权限,然后初始化相机。下面看一下initCamera()
.
private void initCamera() {
//1:初始化SurfaceView
if (mSurfaceView == null) {
mSurfaceView = new SurfaceView(this);
mSurfaceView.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
mContainer.addView(mSurfaceView, 0);
}
//2.获取SurfaceHolder,判断Surface是否可用
SurfaceHolder surfaceHolder = mSurfaceView.getHolder();
Surface surface = surfaceHolder.getSurface();
boolean isValid = surface == null ? false:surface.isValid();
if (mHasSurface && isValid) {
//2.1 SurfaceV可用,直接调用initCamera(surfaceHolder)初始化相机
initCamera(surfaceHolder);
} else {
//2.2 Surface不可用,通过SurfaceHolder.Callback监听Surface的生命周期
surfaceHolder.addCallback(this);
//设置Surface的type,该参数表示,由Camera为Surface提供帧数据。
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
}
- 首先,初始化
SurfaceView
,SurfaceView
在这里可以当做一个普通View
进行初始化。 - 获取
SurfaceHolder
,目的是判断Surface
是否可用,如果可用,直接调用initCamera(surfaceHolder)
初始化相机,如果不可用,需要通过SurfaceHolder.Callback
监听Surface
的生命周期,确保在surfaceCreated(SurfaceHolder holder)
和surfaceDestroyed(SurfaceHolder holder)
之内,使用Surface
,这里如果不清楚的,可以看上一篇 Camera使用指南(一)。这里的mHasSurface
在onCreate()
中设置成false,在surfaceCreated(SurfaceHolder holder)中设置为true,在surfaceDestroyed(SurfaceHolder holder)
设置成false,这样就不需要用isValid
来判断了,这里其实比较多余。
这里看一下initCamera(surfaceHolder)
:
private void initCamera(SurfaceHolder surfaceHolder) {
try {
//1. 连接相机
if (!CameraManager.get().openDriver(surfaceHolder)) {
Log.d(TAG, "Camera被占用");
}
} catch (IOException e) {
e.printStackTrace();
}
if (mCaptureHandler == null) {
mCaptureHandler = new CaptureHandler(this);
}
}
这里主要步骤就是调用CameraManager连接相机,一般情况下不会有什么问题,但是在多个调用相机的应用切换的时候,就很有可能会出问题,但是你的应用里可能也不会出现多个调用相机应用切换的时候,比较有的时候,切换是需要先按home键或者back键返回的,所以这里我没有进行处理。这里有两个需要注意的地方,可能会用到:
-
多个调用Camera的进程切换,争抢Camera,导致应用预览失败,预览界面黑屏/定屏。
这种情况可以在调用openDriver(surfaceHolder)
之后,判断结果,结果为false,一般就是openDriver()
内部发生了异常,这时,可以等待2S左右,类似这样:
long oldTime = System.currentTimeMillis();
boolean isRunning = !CameraManager.get().openDriver(surfaceHolder);
while (isRunning && (System.currentTimeMillis() - oldTime) < 2500) {
try {
Thread.sleep(50);
} catch (Exception e) {
}
isRunning = !CameraManager.get().openDriver(surfaceHolder);
}
if (isRunning)
showToast("相机被占用");
- 此外,连接相机是一个耗时操作,可以放到子线程中进行,这样的话,释放相机也最好放到子线程中。
2. 在CameraActivity的onPause中释放相机。
protected void onPause() {
super.onPause();
//断开Camera连接
if (mCaptureHandler != null) {
try {
//停止预览
mCaptureHandler.quitSynchronously();
mCaptureHandler = null;//下次进入重新初始化
//释放相机
CameraManager.get().closeDriver();
} catch (Exception e) {
//关闭摄像头失败情况下,最好退出Activity,否则下次初始化相机的时候,会提示相机占用
finish();
}
}
}
其它的内容,可以看一下demo,地址如下,CameraDemo.