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,在编码的路上坚挺下去。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值