Android知识要点整理(8)----控制相机

除了调用第三方APP进行拍照外,我们还可以自己使用系统API来控制相机设备进行拍照。这个相对来说比较复杂。下面只讲比较关键的步骤。

1.声明权限

控制照相机我们需要声明使用照相设备的权限。

<uses-feature android:name="android.hardware.camera" android:required="true"/> 
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />

我们声明feature的required属性为true,这样没有相机设备的手机就没法安装APP。若设置为false,我们就需要在代码中检测是否具有相机设备,若有的话则开启拍照功能,否则需要禁用该功能。判断方法如下:

if(getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)){
    photoBtn.setOnClickListener(this);
}else{
    photoBtn.setVisibility(View.GONE);
}

if(getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_AUTOFOCUS)){
            //开启自动聚焦模块
}

2.打开相机设备

打开相机设备就是获得相机的一个实例,它是IO操作,所以可能比较耗时。为了避免ANR,我们需要在onCreateonResume方法中另开一个线程去执行IO操作。相机设备可能会被其他APP占用,这时调用Camera.open()方法会抛出异常。安全地打开相机设备的代码如下:

@Override
protected void onResume() {
    super.onResume();
    //另开一个线程
    new Thread(new Runnable() {
        @Override
        public void run() {
            if(safeCameraOpen(0)){
                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        if(mPreview != null){
                        //设置给预览视图,mPreview 为SurfaceView的子类
                            mPreview.setCamera(mCamera);
                        }
                    }
                });
            }
        }
    }).start();
}

/** *打开指定的相机设备,id为相机序号,<=相机设备数量-1 **/
private boolean safeCameraOpen(int id) {
    boolean qOpened = false;
    try {
        //确保之前的实例已经被正确关闭并释放
        releaseCameraAndPreview();
        mCamera = Camera.open(id);
        qOpened = (mCamera != null);
        if(mCamera != null){
            mCamera.setDisplayOrientation(90);
        }
    } catch (Exception e) {
        Log.e(getString(R.string.app_name), "failed to open Camera");
        e.printStackTrace();
    }
    return qOpened;
}

private void releaseCameraAndPreview() {
    if(mPreview != null) {
        mPreview.setCamera(null);
    }
    if (mCamera != null) {
        mCamera.release();
        mCamera = null;
    }
}

上面的代码在onResume方法中安全的打开设备。同样重要的是,我们要在onPause方法中确保相机设备被正确的关闭和释放,这样就不会阻碍其他APP使用相机设备。

 @Override
 protected void onPause() {
    releaseCameraAndPreview();
    super.onPause();
}

3.关联相机和预览视图

要在拍照前预览效果,我们要构造预览视图,这个视图通常是继承自SurfaceView的自定义视图。SurfaceView能够非UI线程中绘制界面,因为预览效果需要频繁更新界面,使用它可以避免阻塞UI线程。下面是自定义的CameraPreview的完整代码:

package com.github.znacloud.multimediademo.view;

import android.content.Context;
import android.hardware.Camera;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import java.io.IOException;
import java.util.List;

/** * Created by Stephan on 2016/1/15. */
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback{
    private SurfaceHolder mHolder;
    private Camera mCamera;
    private List<Camera.Size> mSupportedPreviewSizes;

    public CameraPreview(Context context) {
        super(context);
        inits(context);
    }

    public CameraPreview(Context context, AttributeSet attrs) {
        super(context, attrs);
        inits(context);
    }

    public CameraPreview(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        inits(context);
    }

    private void inits(Context pContext){
        mHolder = getHolder();
        mHolder.addCallback(this);
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {

    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
        if(mCamera == null) return;
        Camera.Parameters parameters = mCamera.getParameters();
        //根据视图的尺寸设置预览图的尺寸
        parameters.setPreviewSize(width, height);
        mCamera.setParameters(parameters);
        requestLayout();

        // 必须调用startPreview里面更新视图
        mCamera.startPreview();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        if (mCamera != null) {
            // 视图销毁时停止预览
            mCamera.stopPreview();
        }
    }

    public void setCamera(Camera camera) {
        if (mCamera == camera) { return; }

        //设置新的相机实例之前确保旧的相机实例被正确的释放和关闭
        stopPreviewAndFreeCamera();

        mCamera = camera;

        if (mCamera != null) {
            List<Camera.Size> localSizes = mCamera.getParameters().getSupportedPreviewSizes();
            //TODO:根据设备支持的预览尺寸设置视图尺寸

            try {
                //关联到SurfaceView
                mCamera.setPreviewDisplay(mHolder);
            } catch (IOException e) {
                e.printStackTrace();
            }

            //每次更新了相机实例后都要调用此方法开启预览功能
            mCamera.startPreview();
        }
    }

    private void stopPreviewAndFreeCamera() {
        if (mCamera != null) {
            // 停止预览
            mCamera.stopPreview();

            // 释放设备,这样其他APP就可以继续使用相机设备
            mCamera.release();

            mCamera = null;
        }
    }
}

接下来就是在成功打开相机设备后将相机实例关联到预览视图。

if(mPreview != null){
    mPreview.setCamera(mCamera);
}
try {
    mCamera.setPreviewDisplay(mHolder);
} catch (IOException e) {
    e.printStackTrace();
}

4.拍摄照片

上面的工作做完了之后我们就可以正式拍摄照片了。使用Camera.takePicture()方法可以执行拍摄照片的动作,该方法可以传入多个回调函数。看API文档:
Android知识要点整理(8)----控制相机_第1张图片
我们现在只做最简单的拍照,实现代码如下:

 private static final int K_STATE_FROZEN = 0;
 private static final int K_STATE_PREVIEW = 1;
 private static final int K_STATE_BUSY = 2;

 ......

 mCaptureBtn = (Button)findViewById(R.id.btn_shutter);
 mCaptureBtn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if(mPreviewState == K_STATE_FROZEN){
            mCamera.startPreview();
            mPreviewState = K_STATE_PREVIEW;
        }else if(mPreviewState == K_STATE_PREVIEW){
            mCamera.takePicture(null,rawCallBack,null);
            mPreviewState = K_STATE_BUSY;
        }
    }
});

由于在拍照完成后相机会自动进入冻结状态,我们需要根据状态来重新启动预览状态。上面的代码使用了拍照按钮来响应两种不同的操作,若当前为预览状态,点击按钮将立即拍照,此后相机进入冻结状态,再次点击按钮就只是恢复预览状态,不执行拍照。拍照时我们只传了一个回调函数rawCallBack,该回调类定义如下:

private Camera.PictureCallback rawCallBack = new Camera.PictureCallback() {
    @Override
    public void onPictureTaken(byte[] data, Camera camera) {
        mPreviewState = K_STATE_FROZEN;
        //TODO:data中保存的是照片的原始数据,我们可以对其进行处理,保存为照片或者其他事情
    }
};

到此为止,我们已经控制相机拍摄一张完整的照片了。当然,想要界面更加友好,更易使用,我们需要添加一些细节,比如添加快门声,添加聚焦效果,添加人脸检测功能等等。这些功能,系统都有提供API,我们只需要实现响应的界面反馈出来就行了,这里就不详细介绍了。

你可能感兴趣的:(android)