前言

  在之前的博客https://www.cnblogs.com/guanxinjing/p/10940049.html 里,已经说明过如何使用Camera2实现拍照了。

  但是这个博客的讲解在Camera2控制上有点麻烦,因为这篇博客的思想是在页面进入后台后依然持有Camera与Camera会话,让下一次页面重新进入前台后使用原来的Camera会话重新打开预览与实现拍照。这样的方式优点是:

  1.响应快速,不会因为反复操作前后台导致多次初始化。

  2.在后台后Camera依然保持运行。

  但是这样的模式的缺点是:当App进入后台后,其他App调用了摄像头,你下次进入后就会无法获取摄像头了。并且还有一个延伸缺点是非常容易因为其他因素导致内存泄露,需要小心调试。

  所以,我们可以用另外一种简单直接的思想实现Camera2的功能,那就是在onStop后就清理摄像头持有,结束全部会话。在onStart后在重新获取持有摄像头,然后重新获得会话。这样的好处是:

  1.下次从后台进入前台能保证当前摄像头不会丢失,因为是重新获取的。

  2.Camera的释放会简单很多,直接在onStop里释放,不容易内存泄漏。

代码部分

import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Size;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.navigation.Navigation;

import net.yt.lib.permission.OnRequestPermissionCallback;
import net.yt.lib.permission.PermissionRequest;
import net.yt.lib.sdk.utils.ButtonDelayUtil;
import net.yt.lib.sdk.utils.ToastUtils;
import net.yt.whale.owner.yzhome.R;
import net.yt.whale.owner.yzhome.app.BaseFragment;
import net.yt.whale.owner.yzhome.databinding.HomeFragmentFaceTakePicturesBinding;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;

public class FaceTakePicturesFragment extends BaseFragment implements View.OnClickListener {
    private HomeFragmentFaceTakePicturesBinding mBinding;
    private CameraManager mCameraManager;
    private CameraDevice mCameraDevice;
    private CameraCaptureSession.StateCallback mSessionStateCallback;
    private CaptureRequest.Builder mCaptureRequest;
    private CameraDevice.StateCallback mCameraStateCallback;
    private CameraCaptureSession mCameraCaptureSession;
    private ImageReader mImageReader;
    private Surface mSurface;
    private SurfaceTexture mSurfaceTexture;
    private String mCurrentCameraId;
    private HandlerThread mHandlerThread;
    private Handler mChildHandler;

    @Override
    public void onAttach(@NonNull Context context) {
        super.onAttach(context);
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        mBinding = HomeFragmentFaceTakePicturesBinding.inflate(getLayoutInflater());
        return mBinding.getRoot();
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        mBinding.back.setOnClickListener(this);
        mBinding.takePictures.setOnClickListener(this);
        initListener();

    }

    @Override
    public void onStart() {
        super.onStart();
        initChildThread();
        if (mBinding.textureView.isAvailable()) {
            selectOpenCamera();
        }
    }

    @Override
    public void onStop() {
        super.onStop();
        if (mImageReader != null){
            mImageReader.close();
            mImageReader = null;
        }
        if (mCaptureRequest != null){
            mCaptureRequest.removeTarget(mSurface);
        }
        if (mCameraCaptureSession != null){
            try {
                mCameraCaptureSession.stopRepeating();
                mCameraCaptureSession.close();
            } catch (CameraAccessException e) {
                e.printStackTrace();
            }
        }
        if (mCameraDevice != null){
            mCameraDevice.close();
            mCameraDevice = null;
        }
        if (mChildHandler != null){
            mChildHandler.removeCallbacksAndMessages(null);
            mChildHandler = null;
        }
        if (mHandlerThread != null){
            mHandlerThread.quitSafely();
            mHandlerThread = null;
        }
    }

    @Override
    public void onResume() {
        super.onResume();

    }

    @Override
    public void onPause() {
        super.onPause();
    }

    @Override
    public void onClick(View v) {
        int id = v.getId();
        if (id == R.id.back){
            Navigation.findNavController(getView()).navigateUp();

        }else if (id == R.id.takePictures){
            if (ButtonDelayUtil.isFastClick()){
                takePicture();
            }
        }

    }

    private void initListener() {
        mBinding.textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
            @Override
            public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
                mSurfaceTexture = surface;
                selectOpenCamera();
            }

            @Override
            public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
            }

            @Override
            public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
                return false;
            }

            @Override
            public void onSurfaceTextureUpdated(SurfaceTexture surface) {

            }
        });
        //相机状态
        mCameraStateCallback = new CameraDevice.StateCallback() {
            @Override
            public void onOpened(@NonNull CameraDevice camera) {
                mCameraDevice = camera;
                try {
                    Size matchingSize = getMatchingSize();
                    initImageReader(matchingSize.getWidth(), matchingSize.getHeight());
                    mSurfaceTexture = mBinding.textureView.getSurfaceTexture();
                    mSurfaceTexture.setDefaultBufferSize(matchingSize.getWidth(), matchingSize.getHeight());
                    mSurface = new Surface(mSurfaceTexture);
                    mCaptureRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                    mCaptureRequest.addTarget(mSurface);
                    mCaptureRequest.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
                    mCameraDevice.createCaptureSession(Arrays.asList(mSurface, mImageReader.getSurface()), mSessionStateCallback, mChildHandler);
                } catch (CameraAccessException e) {
                    e.printStackTrace();
                    Navigation.findNavController(getView()).navigateUp();
                    ToastUtils.showShortToast("打开相机失败");
                }
            }

            @Override
            public void onDisconnected(@NonNull CameraDevice camera) {

            }

            @Override
            public void onError(@NonNull CameraDevice camera, int error) {
                ToastUtils.showShortToast("打开相机失败:" + error);
                Navigation.findNavController(getView()).navigateUp();

            }
        };
        //会话状态
        mSessionStateCallback = new CameraCaptureSession.StateCallback() {
            @Override
            public void onConfigured(@NonNull CameraCaptureSession session) {
                mCameraCaptureSession = session;
                try {
                    mCameraCaptureSession.setRepeatingRequest(mCaptureRequest.build(), null, mChildHandler);
                } catch (CameraAccessException e) {
                    e.printStackTrace();
                    ToastUtils.showShortToast("相机预览失败:" + e.getMessage());
                    Navigation.findNavController(getView()).navigateUp();

                }
            }

            @Override
            public void onConfigureFailed(@NonNull CameraCaptureSession session) {
                //配置失败
                try {
                    session.stopRepeating();
                } catch (CameraAccessException e) {
                    e.printStackTrace();
                    Navigation.findNavController(getView()).navigateUp();
                }
                Navigation.findNavController(getView()).navigateUp();

            }
        };
    }

    /**
     * 初始化子线程
     */
    private void initChildThread() {
        mHandlerThread = new HandlerThread("camera2");
        mHandlerThread.start();
        mChildHandler = new Handler(mHandlerThread.getLooper());
    }

    public void selectOpenCamera() {
        PermissionRequest request = new PermissionRequest.Builder(this)//权限
                .addPermission(Manifest.permission.CAMERA)
                .setCallback(new OnRequestPermissionCallback() {
                    @SuppressLint("MissingPermission")
                    @Override
                    public void onRequest(boolean granted) {
                        if (granted) {
                            mCameraManager = (CameraManager) getContext().getApplicationContext().getSystemService(Context.CAMERA_SERVICE);
                            try {
                                String[] cameraIdList = mCameraManager.getCameraIdList();
                                if (cameraIdList.length == 0) {
                                    ToastUtils.showShortToast("此设备没有摄像头");
                                    Navigation.findNavController(getView()).navigateUp();
                                    return;
                                }
                                for (String cameraId : cameraIdList) {
                                    CameraCharacteristics cameraCharacteristics = mCameraManager.getCameraCharacteristics(cameraId);
                                    Integer facing = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING);//获取这个摄像头的面向
                                    if (facing == CameraCharacteristics.LENS_FACING_FRONT) {
                                        mCurrentCameraId = cameraId;
                                    }
                                }
                                mCameraManager.openCamera(mCurrentCameraId, mCameraStateCallback, mChildHandler);
                            } catch (CameraAccessException e) {
                                e.printStackTrace();
                                ToastUtils.showShortToast("打开相机失败:" + e.getMessage());
                                Navigation.findNavController(getView()).navigateUp();
                            }
                        } else {
                            ToastUtils.showShortToast("未获取相机权限");
                            Navigation.findNavController(getView()).navigateUp();
                        }
                    }
                }).build();
        request.requestPermissions();
    }

    private Size getMatchingSize() {
        Size selectSize = null;
        try {
            CameraCharacteristics cameraCharacteristics = mCameraManager.getCameraCharacteristics(mCurrentCameraId);
            StreamConfigurationMap streamConfigurationMap = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
            Size[] sizes = streamConfigurationMap.getOutputSizes(ImageFormat.JPEG);
            DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
            int deviceWidth = displayMetrics.widthPixels;
            int deviceHeigh = displayMetrics.heightPixels;
            for (int j = 1; j < 41; j++) {
                for (int i = 0; i < sizes.length; i++) {
                    Size itemSize = sizes[i];
                    if (itemSize.getHeight() < (deviceWidth + j * 5) && itemSize.getHeight() > (deviceWidth - j * 5)) {
                        if (selectSize != null) {
                            if (Math.abs(deviceHeigh - itemSize.getWidth()) < Math.abs(deviceHeigh - selectSize.getWidth())) { //求绝对值算出最接近设备高度的尺寸
                                selectSize = itemSize;
                                continue;
                            }
                        } else {
                            selectSize = itemSize;
                        }
                    }
                }
                if (selectSize != null) {
                    return selectSize;
                }
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
        return selectSize;
    }

    /**
     * 初始化图片读取器
     */
    private void initImageReader(int width, int height) {
        //创建图片读取器,参数为分辨率宽度和高度/图片格式/需要缓存几张图片,我这里写的2意思是获取2张照片
        mImageReader = ImageReader.newInstance(width, height, ImageFormat.JPEG, 2);
        mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
            @Override
            public void onImageAvailable(ImageReader reader) {
                Image image = reader.acquireNextImage();
                File path = new File(getContext().getExternalCacheDir().getPath());
                File file = new File(path, "demo.jpg");//TODO 后续修改路径
                if (!path.exists()) {
                    path.mkdirs();
                }
                try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
                    ByteBuffer byteBuffer = image.getPlanes()[0].getBuffer();
                    byte[] bytes = new byte[byteBuffer.remaining()];
                    byteBuffer.get(bytes);
                    fileOutputStream.write(bytes);
                    fileOutputStream.flush();
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    image.close();
                }
            }
        }, mChildHandler);
    }

    private void takePicture() {
        CaptureRequest.Builder captureRequestBuilder = null;
        try {
            captureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            captureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);//自动对焦
            captureRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);//自动爆光
            captureRequestBuilder.set(CaptureRequest.JPEG_ORIENTATION, 270);
            Surface surface = mImageReader.getSurface();
            captureRequestBuilder.addTarget(surface);
            CaptureRequest request = captureRequestBuilder.build();
            mCameraCaptureSession.capture(request, null, mChildHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }
}