Camera使用指南(二)

接着上一篇接口介绍继续,这一片主要是看一下代码结构,本身自己缺乏这方面的能力,借着看zxing调用Camera组件的源码,也写了一个小demo,下面介绍一下代码中如何调用Camera进行拍照。最后会贴上demo地址。
目录:

  1. 功能介绍
  2. 根据功能设计类
  3. 流程分析

1. 功能介绍

这个demo主要实现了调用Camera组件进行拍照,并保存图片的功能。一下详细的功能拆分:

  1. 调用相机预览;
  2. 预览过程中相机自动对焦;
  3. 调用相机拍照;
  4. 保存图片;
  5. 退出Activity,释放相机。

2. 根据功能设计类

  1. 显示界面——CameraActivity,用于相机预览,根据上一篇。。。我们知道,相机预览需要使用到SurfaceView,所以,CameraActivity中会有一个SurfaceView变量。同时,我们需要监听SurfaceView中的Surface的生命周期,保证在其生命周期之内,调用Camera组件的接口setPreviewDisplay(holder)(设置显示预览界面的Surface),startPreview()(开启预览),stopPreview()(停止预览)进行相应的操作,所以CameraActivity还需要实现SurfaceHolder.Callback接口。
  2. 管理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)进行下一步处理。
  3. 预览回调类——PreviewCallback,需要实现接口Camera.PreviewCallback,当然,如果程序中不需要对预览数据进一步操作,就不用自定义这个回调接口了。如果需要对预览数据进行处理(比如扫描识别二维码,就是获取预览帧数据进行识别),就可以创建自己的预览回调类,实现函数onPreviewFrame(byte[] data, Camera camera),在这里获取data进行处理。
  4. 自动对焦的回调类——AutoFocusCallback,实现接口Camera.AutoFocusCallback,可以在onAutoFocus(boolean success, Camera camera)方法中获取对焦的结果。
  5. 拍照的回调类——PictureCallback,实现接口Camera.PictureCallback,然后在方法onPictureTaken(byte[] data, Camera camera)中获取拍照的图片数据,将其保存成图片。
  6. 辅助Activity,处理各个回调方法返回的结果——CaptureHandler,以上几个回调接口的结果会交有CaptureHandler进行处理,这样CameraActivity就可以只负责界面显示,CaptureHandler可以处理业务逻辑。

以下各个类之间的静态结构:


CameraDemo类图.png

3. 流程分析

为了帮助理解,我们看一下类之间的调用流程:


CameraDemo时序图.png

最后看一下代码中可能需要注意的几个地方吧。

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);
    }
}
  1. 首先,初始化SurfaceViewSurfaceView在这里可以当做一个普通View进行初始化。
  2. 获取SurfaceHolder,目的是判断Surface是否可用,如果可用,直接调用initCamera(surfaceHolder)初始化相机,如果不可用,需要通过SurfaceHolder.Callback监听Surface的生命周期,确保在surfaceCreated(SurfaceHolder holder)surfaceDestroyed(SurfaceHolder holder)之内,使用Surface,这里如果不清楚的,可以看上一篇 Camera使用指南(一)。这里的mHasSurfaceonCreate()中设置成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键返回的,所以这里我没有进行处理。这里有两个需要注意的地方,可能会用到:

  1. 多个调用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("相机被占用");
  1. 此外,连接相机是一个耗时操作,可以放到子线程中进行,这样的话,释放相机也最好放到子线程中。
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.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,478评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,825评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,482评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,726评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,633评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,018评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,168评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,320评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,264评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,288评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,995评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,587评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,909评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,284评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,862评论 2 339

推荐阅读更多精彩内容

  • 上一篇介绍了如何使用系统相机简单、快速的进行拍照,本篇将介绍如何使用框架提供的API直接控制摄像机硬件。 你还在为...
    Xiao_Mai阅读 7,120评论 4 18
  • 这篇文章本来上个月就要写的,后来一直没写,正好在新年写下,对之前的工作进行总结。 之前工作中需要自定义相机,网上看...
    zero_sr阅读 4,769评论 0 11
  • 不怕跌倒,所以飞翔 最近观看视频学习的时候,看见慕课网上又关于Camera实现自定义拍照的功能,特此写下笔记记录一...
    笔墨Android阅读 3,026评论 0 0
  • 现在的工作需要用到camera模块,所以打算分析Zxing中的camera实现,来了解android的camera...
    暴风雨1024阅读 3,865评论 1 5
  • 她 “你说我是穿裙子好还是穿裤子好?琪琪”女孩早早六点半就起来,她要去车站接他。这次是她们从相恋到六个月的第一次见...
    zpenGl阅读 215评论 0 0