美文网首页Android技术分享交流区Android知识程序员
Android 自定义Camera之SurfaceView的使用

Android 自定义Camera之SurfaceView的使用

作者: AFinalDream | 来源:发表于2017-04-02 20:43 被阅读560次

    序言

    由于前段时间在准备跳槽,所以一直没有更新。不过,从这个月开始,我会继续开始记录自己在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,在编码的路上坚挺下去。

    相关文章

      网友评论

      • 梧叶已秋声:感谢,很有帮助,正好需要一个android6.0以上权限camera管理的~
        AFinalDream:@asgrfsd 没事 有疑问可以联系

      本文标题:Android 自定义Camera之SurfaceView的使用

      本文链接:https://www.haomeiwen.com/subject/mjagottx.html