1. 程式人生 > 實用技巧 >Android開發 Camera2拍照功能實現(另一種思路的實現)

Android開發 Camera2拍照功能實現(另一種思路的實現)

前言

  在之前的部落格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(); } } }