前言
在之前的博客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(); } } }