Android 自定义Camera之SurfaceView的使用(6.0权限申请)

序言

由于前段时间在准备跳槽,所以一直没有更新。不过,从这个月开始,我会继续开始记录自己在android开发中遇到的一些坑,或写一些比较有意思的文章。希望大家继续关注。好了,开始切入正题。


概述

这段时间开始接触到Camera相关的东西,所以就打算自己写一个小demo来熟悉一下流程和要点。当然,本文使用SurfaceView来实现一个Camera,同时适配6.0权限(开始没6.0动态权限,后来因为身边很多都是6.0,所以简单的做了一下6.0权限),以及sd卡的读写,图片显示不全等一些相关的知识点。

相关知识的介绍

  • SurfaceView :使用场景界面迅速更新对帧率要求较高的情况。SurfaceView继承 View,SurfaceView和View最本质的区别在于,SurfaceView是在一个新起的单独线程中可以重新绘制画面,而View必须在UI的主线程中更新画面。因本文主要讲的是怎么使用,所以详细介绍可以看SurfaceView或者Google查看。

  • RxPermissions Github地址:本文使用了原生的6.0权限请求和RxPermissions。RxPermissions是一个6.0动态权限管理的一个library库,它的使用需要结合Rxjava一起,因为RxPermissions返回的是一个Observable,所以如果不准备使用Rxjava,可以去尝试一下其他的library。可以参考一下弘洋的6.0权限管理

  • 还有读写文件的基本使用方法以及一些图片的简单处理


实现

一 :主要逻辑在MainActivity,在onCreate的时候申请权限处理,在onResume的时候startPreview
开启预览,在onPause的时候releasePreview关闭预览并释放Camera(由于一直持有会出现oom)。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        frameLayout = (FrameLayout) findViewById(R.id.activity_main);
        btn_capture = (ImageView) findViewById(R.id.btn_capture);
        btn_capture.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                capture();
            }
        });
        /**
         * 使用系统API请求相机权限
         */
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            isCamera = false;
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, MY_PERMISSIONS_REQUEST_CAMERA);
        } else {
            isCamera = true;
            initCamera();
            initDefult();

        }
    }
    /**
     * 获得Camera,开启预览
     *
     */
    @Override
    protected void onResume() {
        super.onResume();
        if (isCamera == false) return;
        if (mCamera == null) {
            mCamera = getCamera();
            if (sHolder != null) {
                setStartPreview(mCamera, sHolder);
            }
        }
    }
    /**
     * 停止预览,销毁Camera
     */
    @Override
    protected void onPause() {
        super.onPause();
        releasePreview();
    }

二:initCamera()中主要是初始化Camera和SurfaceView,并且获得SurfaceHolder,然后SurfaceHolder添加回调,并调用setStartPreview,开启预览。

    /**
     * 初始化Camera相关
     */
    private void initCamera() {
        mCamera = getCamera();
        surface_camera = (SurfaceView) findViewById(R.id.surface_camera);
        frameLayout.bringChildToFront(surface_camera);
        frameLayout.bringChildToFront(btn_capture);
        sHolder = surface_camera.getHolder();
        sHolder.addCallback(this);
        surface_camera.setOnClickListener(this);
        setStartPreview(mCamera,sHolder);    //由于APP在第一次安装时,onResume不会执行,所以重新获得cemera权限以后重新start
    }

注:大家会看到,在onCeate和onResume都调用了 mCamera = getCamera(),原因是在于,当app第一次安装时,系统会依次执行Activity的生命周期,如果只在onResume中调用,会发现并没有使用相机。原因是在权限申请时,是另起了一个线程,所以获得Camera权限后,onResume已经执行完成。因此添加isCamera字段,来标记是否已经获取权限,同时在取得权限后,调用了 mCamera = getCamera()。

三:当添加了SurfaceHolder回调后,会重写三个方法:surfaceCreated(),surfaceChanged(),surfaceDestroyed()。分别是创建,变化和销毁。

@Override
    public void surfaceCreated(SurfaceHolder holder) {
        setStartPreview(mCamera, sHolder);
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        mCamera.stopPreview();
        setStartPreview(mCamera, sHolder);
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        releasePreview();
    }

四:接下来看最重要的setStartPreview()和 releasePreview()。这两个方法中,setStartPreview中主要是做一下初始化Preview的分辨率,调整一下预览的成像角度。releasePreview中主要是给setPreviewCallback置null,停止预览并释放Camera。

    /**
     * 开启Camera预览
     */
    private void setStartPreview(Camera camera, SurfaceHolder holder) {
        try {
            Camera.Parameters parameters = camera.getParameters();
            List<Camera.Size> size2 = parameters.getSupportedPreviewSizes();     //得到手机支持的预览分辨率
            parameters.setPreviewSize(size2.get(0).width,size2.get(0).height);
            camera.setPreviewDisplay(holder);//绑定holder
            camera.setDisplayOrientation(getPreviewDegree(MainActivity.this));//将系统Camera角度进行调整
            camera.startPreview();//开启预览
            camera.setParameters(parameters);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
     /**
     * 释放Camera
     */
    private void releasePreview() {
        if (mCamera == null) return;
        mCamera.setPreviewCallback(null);
        mCamera.stopPreview();//停止预览
        mCamera.release();
        mCamera = null;
    }

五:拍照和点击屏幕实现对焦。点击拍照前,会设置一下Picture相关的参数。当onAutoFocus返回true时,说明对焦成功,然后调用Camera的takePicture实现拍照。

     Camera.Parameters parameters = mCamera.getParameters();
        List<Camera.Size> supportedPictureSizes = parameters.getSupportedPictureSizes();
        parameters.setPictureFormat(ImageFormat.JPEG);//设置图片样式
        parameters.setPictureSize(supportedPictureSizes.get(0).width, supportedPictureSizes.get(0).height);//设置图片大小
        parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);//自动对焦
        mCamera.setParameters(parameters);
        mCamera.autoFocus(new Camera.AutoFocusCallback() {
            @Override
            public void onAutoFocus(boolean success, Camera camera) {
                if (success) {
                    mCamera.takePicture(null, null, pictureCallback);
                }
            }
        });

六:拍照成功后,使用RxPermissions申请写入sd权限。然后完成跳转到预览界面。其中返回的data是一个拍照完成后,没有压缩过完整的图片byte[]。

      //保存图片
                                String absolutePath = FileUtil.createIfNotExist(path);
                                FileUtil.writeBytes(path, data);
                                Intent intent = new Intent(MainActivity.this,ImageActivity.class);
                                intent.putExtra("path",absolutePath);
                                startActivity(intent);

总结

由于本人原来并没有涉及到相关模块,但是在刚接触的时候,感觉挺简单,就是按部就班的实现一些方法和生命周期,但是当一步步做下来的时候,发现其中涉及到的细节还是挺多。比如:
1.在我要设置setPreviewSize和setPictureSize时,我发现很容易导致程序崩溃,所以调用getSupportedPictureSizes,获取当前支持的各种分辨率,然后使用最高的分辨来设置。
2.由于本人没有6.0以上的测试机,所以很多问题难以定位。在添加6.0权限后,发现原有的逻辑需要重新思考,所以花费了一些时间和精力。
3.是大家经常会遇到的图片翻转或者角度问题。
4.由于安卓机型实在太多,所以还要考虑多种屏幕下的显示和预览问题。


源码

源码下载地址源码中注释写的很清楚,本文只是把关键代码贴出来,如有需要,欢迎大家下载。

如果大家在学习时有问题,欢迎大家随时联系或者留言,我看见后会第一时间回复并解决。最后,愿大家在小长假中玩得开心,祝愿你我gaygayup,在编码的路上坚挺下去。

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

推荐阅读更多精彩内容