引言
一切源於在項目過程中的一個Bug:我的需求是在MainActivity 實現自動預覽,也可以點擊跳到簽到SignedActivity去實現拍照簽到,第一次進入界面的時候都是正常的,但是有時候返回來的時候預覽失敗,即從MainActivity跳轉到SignedActivity偶爾預覽失敗和從SignedActivity返回MainActivity偶爾失敗,都是報(CAMERA_IN_USE)ERRO=1的錯誤,奇怪的是的的確確做了完全釋放操作,加上以前用的更多的是Camera api 對於Camer2 的機制沒有完整去研究過,一下子懵了,於是乎先去找了Stack Overflow,查到一個解決方案是:"我棄用了新API,換回舊API",ORZ,找了其他的也沒有答案,可是我不服呀,我就把官方的文檔全部啃了一遍,於是乎便有了以下的理解,我想如果你不懂得怎么使用Camera2的話,這篇絕對值得你去閱讀,你會發現Camera2 並非像大多數說得那樣使用起來很復雜。
一、Camera2架構概述
全新的android.hardware.Camera2 。Android 5.0對拍照API進行了全新的設計,新增了全新設計的Camera 2 API,這些API不僅大幅提高了Android系統拍照的功能,還能支持RAW照片輸出,甚至允許程序調整相機的對焦模式、曝光模式、快門等。

如上圖所示,Camera2 API相比原來android.hardware.Camera API 在架構上有了很大的改變,雖然讓手機拍照功能更加強大,但同時也增加了開發復雜度了,從以下的Camera2 架構圖中所有參與類角色不能看出。

引入了管道的概念將安卓設備和攝像頭之間聯系起來, 系統向攝像頭發送 Capture 請求,而攝像頭會返回 CameraMetadata,這一切建立在一個叫作 CameraCaptureSession 的會話中。
二、Camera2架構主要的類角色說明
在Camera2 架構在核心參與類角色有:CameraManager、CameraDevice、CameraCharacteristics、CameraRequest與CameraRequest.Builder、CameraCaptureSession以及CaptureResult。
1、CameraManager
位於android.hardware.camera2.CameraManager下,也是Android 21(5.0)添加的,和其他系統服務一樣通過 Context.getSystemService(CameraManager.class ) 或者Context.getSystemService(Context.CAMERA_SERVICE) 來完成初始化,主要用於管理系統攝像頭:
-
通過getCameraIdList()方法獲取Android設備的攝像頭列表
-
getCameraCharacteristics(String cameraId)獲取攝像頭的詳細參數和支持的功能
-
** openCamera(String cameraId, CameraDevice.StateCallback callback, Handler handler)打開指定Id的攝像頭**
CameraManager manager = (CameraManager)context.getSystemService(Context.CAMERA_SERVICE);
2、CameraDevice
CameraDevice是Camera2中抽象出來的一個對象,直接與系統硬件攝像頭相聯系。因為不可能所有的攝像頭都會支持高級功能(即攝像頭功能可被分為limit 和full 兩個級別),當攝像頭處於limited 級別時候,此時Camera2和早期的Camera功能差不多,除此之外在Camera2架構中,CameraDevice還承擔其他兩項重要任務:
-
通過CameraDevice.StateCallback監聽攝像頭的狀態(主要包括onOpened、onClosed、onDisconnected、onErro四種狀態)
-
管理CameraCaptureSession,-通過方法createCaptureSession(List<Surface> outputs, CameraCaptureSession.StateCallback callback, Handler handler)方法和createReprocessableCaptureSession(InputConfiguration inputConfig, List<Surface> outputs, CameraCaptureSession.StateCallback callback, Handler handler)方法創建會話 (其中第三個參數: The handler on which the callback should be invoked, or null to use the current thread's looper.),通常會在CameraDevice.StateCallback中調用對應方法創建預覽會話。
-
管理CaptureRequest,主要包括通過createCaptureRequest(int templateType)創建捕獲請求,在需要預覽、拍照、再次預覽的時候都需要通過創建請求來完成。
3、CameraCaptureSession
正如前面所說,系統向攝像頭發送 Capture 請求,而攝像頭會返回 CameraMetadata,這一切都是在由對應的CameraDevice創建的CameraCaptureSession 會話完成,當程序需要預覽、拍照、再次預覽時,都需要先通過會話。(A configured capture session for a CameraDevice, used for capturing images from the camera or reprocessing images captured from the camera in the same session previously.A CameraCaptureSession is created by providing a set of target output surfaces to createCaptureSession, or by providing an InputConfiguration and a set of target output surfaces to createReprocessableCaptureSession for a reprocessable capture session. Once created, the session is active until a new session is created by the camera device, or the camera device is closed.)CameraCaptureSession一旦被創建,直到對應的CameraDevice關閉才會死掉。雖然CameraCaptureSession會話用於從攝像頭中捕獲圖像,但是只有同一個會話才能再次從同一攝像頭中捕獲圖像。另外,創建會話是一項耗時的異步操作,可能需要幾百毫秒,因為它需要配置相機設備的內部管道並分配內存緩沖區以將圖像發送到所需的目標,因而createCaptureSession和createReprocessableCaptureSession會將隨時可用的CameraCaptureSession發送到提供的監聽器的onConfigured回調中。如果無法完成配置,則觸發onConfigureFailed回調,並且會話將不會變為活動狀態。最后需要注意的是,如果攝像頭設備創建了一個新的會話,那么上一個會話是被關閉的,並且會回調與其關聯的onClosed,如果不處理好,當會話關閉之后再次調用會話的對應方法那么所有方法將會跑出IllegalStateException異常。關閉的會話清除任何重復的請求(和調用了stopRepeating()方法類似),但是在新創建的會話接管並重新配置攝像機設備之前,關閉的會話仍然會正常完成所有正在進行的捕獲請求。簡而言之,在Camera2中CameraCaptureSession承擔很重要的角色:
-
管理CameraCaptureSession.StateCallback狀態回調,用於接收有關CameraCaptureSession狀態的更新的回調對象,主要回調方法有兩個當CameraDevice 完成配置,對應的會話開始處理捕獲請求時觸發onConfigured(CameraCaptureSession session)方法,反之配置失敗時候觸發onConfigureFailed(CameraCaptureSession session)方法。
-
管理CameraCaptureSession.CaptureCallback捕獲回調,用於接收捕獲請求狀態的回調,當請求觸發捕獲已啟動時;捕獲完成時;在捕獲圖像時發生錯誤的情況下;都會觸發該回調對應的方法。
-
通過調用方法capture(CaptureRequest request, CameraCaptureSession.CaptureCallback listener, Handler handler)提交捕獲圖像請求(Submit a request for an image to be captured by the camera device.)即拍照,其中該請求定義了捕獲單個圖像的所有參數,包括傳感器,鏡頭,閃光燈和后處理參數,每一次請求的結果將產生一個CaptureResult,可以為一個或多個Surface生成新的幀,然后通過CaptureRequest.Builder的addTarget(Surface)方法附着到對應的Surface上顯示,而且這個參數Surface必須是會話創建時候的一個子集,會話一次可以處理多個常規和重新處理請求。但如果只有常規請求或重新處理請求在進行,則以先進先出的順序處理它們;如果兩者都在進行中則分別以各自的先進先出順序處理他們;然而,處理常規請求和重新處理請求的順序並不是特定的,換言之,一個常規請求在下一個常規請求提交前被處理,同理重新處理請求也一樣,但是一個常規請求不一定是在下一個重新處理請求提交之前被處理。通過capture方法提交的請求處理優先級比通過其他方式( setRepeatingRequest(CaptureRequest, CameraCaptureSession.CaptureCallback, Handler) 或者setRepeatingBurst(List, CameraCaptureSession.CaptureCallback, Handler))提交的請求的處理優先級高,一旦當前的repeat / repeatBurst處理完成,就會被處理。最后一點,所有CaptureSession可用於從相機捕獲圖像,但只有由createReprocessableCaptureSession創建的會話才可以提交重新處理捕獲請求,
將重新處理請求提交到常規捕獲會話將導致IllegalArgumentException。 -
通過調用方法setRepeatingRequest(CaptureRequest request, CameraCaptureSession.CaptureCallback listener, Handler handler)請求不斷重復捕獲圖像,即實現預覽
-
通過方法調用stopRepeating()實現停止捕獲圖像,即停止預覽。

4、CameraCharacteristics
描述Cameradevice屬性的對象,可以使用CameraManager通過getCameraCharacteristics(String cameraId)進行查詢。
5、CameraRequest和CameraRequest.Builder
CameraRequest代表了一次捕獲請求,而CameraRequest.Builder用於描述捕獲圖片的各種參數設置,包含捕獲硬件(傳感器,鏡頭,閃存),對焦模式、曝光模式,處理流水線,控制算法和輸出緩沖區的配置。,然后傳遞到對應的會話中進行設置,CameraRequest.Builder則負責生成CameraRequest對象。當程序調用setRepeatingRequest()方法進行預覽時,或調用capture()方法進行拍照時,都需要傳入CameraRequest參數。CameraRequest可以通過CameraRequest.Builder來進行初始化,通過調用createCaptureRequest來獲得。
6、CaptureResult
CaptureRequest描述是從圖像傳感器捕獲單個圖像的結果的子集的對象。(CaptureResults are produced by a CameraDevice after processing a CaptureRequest)當CaptureRequest被處理之后由CameraDevice生成。
7、Camera2 主要角色之間的聯系
CameraManager處於頂層管理位置負責檢測獲取所有攝像頭及其特性和傳入指定的CameraDevice.StateCallback回調打開指定攝像頭,CameraDevice是負責管理抽象對象,包括監聽Camera 的狀態回調CameraDevice.StateCallback、創建CameraCaptureSession和CameraRequest,CameraCaptureSession用於描述一次圖像捕獲操作,主要負責監聽自己會話的狀態回調CameraCaptureSession.StateCallback和CameraCaptureSession.CaptureCallback捕獲回調,還有發送處理CameraRequest;CameraRequest則可以看成是一個"JavaBean"的作用用於描述希望什么樣的配置來處理這次請求;最后三個回調用於監聽對應的狀態。
三、Camera2 使用步驟
- 初始化並啟動HandlerThread(因為創建會話是好時的操作不宜放在主線程)
private void startBackgroundThread() { LogUtil.showModelLog("攝像頭"+"啟動HandlerThread"); mBackgroundThread = new HandlerThread("CameraBackground"); mBackgroundThread.start(); mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); }
- 實現相關Surface的回調(是為了能在相關Surface可以用的時候自動開啟預覽)
private final TextureView.SurfaceTextureListener mSurfaceTextureListener = new TextureView.SurfaceTextureListener() { @Override public void onSurfaceTextureAvailable(SurfaceTexture texture, int width, int height) { LogUtil.showModelLog("攝像頭"+"當TextureView 可用時"); //3.在TextureView可用的時候嘗試打開攝像頭 openCamera(width, height); } @Override public void onSurfaceTextureSizeChanged(SurfaceTexture texture, int width, int height) { configureTransform(width, height); } @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture texture) { return true; } @Override public void onSurfaceTextureUpdated(SurfaceTexture texture) { } };
-
實現CameraDevice.StateCallback回調
-
初始化CameraManager對象,進行一些參數設置工作之后,使用CameraManager實例打開Camera
manager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);
-
當在CameraDevice.StateCallback回調中監聽到Camera成功被打開時,初始化CameraDevice並通過CameraDevice創建捕獲圖像請求並把對應的Surface附着到請求上,完成CameraRequest的創建。
-
實現CameraCaptureSession.StateCallback和CameraCaptureSession.CaptureCallback回調
private CameraCaptureSession.CaptureCallback mCaptureCallback = new CameraCaptureSession.CaptureCallback() { private void process(CaptureResult result) { switch (mState) { case STATE_PREVIEW: { LogUtil.showModelLog("攝像頭"+"在CameraCaptureSession.CaptureCallback捕獲回調處理CaptureResult,在process里預覽成功工作"); // We have nothing to do when the camera preview is working normally. break; } case STATE_WAITING_LOCK: { Integer afState = result.get(CaptureResult.CONTROL_AF_STATE); if (afState == null) { captureStillPicture(); } else if (CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED == afState || CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED == afState) { // CONTROL_AE_STATE can be null on some devices Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED) { mState = STATE_PICTURE_TAKEN; captureStillPicture(); } else { runPrecaptureSequence(); } } break; } case STATE_WAITING_PRECAPTURE: { // CONTROL_AE_STATE can be null on some devices Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); if (aeState == null || aeState == CaptureResult.CONTROL_AE_STATE_PRECAPTURE || aeState == CaptureRequest.CONTROL_AE_STATE_FLASH_REQUIRED) { mState = STATE_WAITING_NON_PRECAPTURE; } break; } case STATE_WAITING_NON_PRECAPTURE: { // CONTROL_AE_STATE can be null on some devices Integer aeState = result.get(CaptureResult.CONTROL_AE_STATE); if (aeState == null || aeState != CaptureResult.CONTROL_AE_STATE_PRECAPTURE) { mState = STATE_PICTURE_TAKEN; captureStillPicture(); } break; } default: break; } } @Override public void onCaptureProgressed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureResult partialResult) { process(partialResult); LogUtil.showDebugLog("onCaptureProgressed"); } @Override public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) { process(result); LogUtil.showModelLog("攝像頭"+"在CameraCaptureSession.CaptureCallback捕獲回調處理CaptureResult"); } };
- 再通過CameraDevice創建CameraCaptureSession,在會話配置完成並開始處理請求時候會觸發CameraCaptureSession.StateCallback的onConfiged方法,在完成請求的參數配置后發送CameraRequest
private void createCameraPreviewSession() { try { SurfaceTexture texture = cameraViewSigned.getSurfaceTexture(); assert texture != null; // We configure the size of default buffer to be the size of camera preview we want. texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); // This is the output Surface we need to start preview. Surface surface = new Surface(texture); LogUtil.showModelLog("攝像頭"+"通過調用CameraDevice.createCaptureRequest方法創建mPreviewRequestBuilder預覽請求,並把要附着的Surface通過addTarget 封到請求中"); // We set up a CaptureRequest.Builder with the output Surface. mPreviewRequestBuilder= mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); mPreviewRequestBuilder.addTarget(surface); LogUtil.showModelLog("攝像頭"+"通過調用CameraDevice.createCaptureSession方法並傳入CameraCaptureSession.StateCallbac創建預覽會話mCaptureSession"); // Here, we create a CameraCaptureSession for camera preview. mCameraDevice.createCaptureSession(Arrays.asList(surface, mImageReader.getSurface()), new CameraCaptureSession.StateCallback() { @Override public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { // The camera is already closed if (null == mCameraDevice) { return; } LogUtil.showModelLog("攝像頭"+"當預覽會話完成配置並開始處理請求時候,把閃光燈,模式等參數封裝到預覽請求"); // When the session is ready, we start displaying the preview. mCaptureSession = cameraCaptureSession; try { // Auto focus should be continuous for camera preview. mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE); // Flash is automatically enabled when necessary. setAutoFlash(mPreviewRequestBuilder); // Finally, we start displaying the camera preview. mPreviewRequest = mPreviewRequestBuilder.build(); LogUtil.showModelLog("攝像頭"+"接着把預覽請求設置到預覽會話mCaptureSession,傳入CameraCaptureSession.CaptureCallback捕獲回調並發出不斷捕獲圖像的請求"); mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mBackgroundHandler); } catch (CameraAccessException e) { e.printStackTrace(); } } @Override public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { showToast("拍照失敗"); } }, null ); } catch (CameraAccessException e) { e.printStackTrace(); } }
-
此時CameraCaptureSession.CaptureCallback監聽被激活,基本完成預覽流程
-
然后拍照的流程就很簡單了,本質上就是改變了CameraCaptureSession.CaptureCallback里狀態常量的值,然后再次發送請求,然后觸發了回調,此時狀態常量已經改變,就會執行不同的邏輯,太簡單了不想多說,最后附上一張Camera2 預覽的完整流程日志。

CameraManager處於頂層管理位置負責檢測檢測獲取所有攝像頭並設置輸出參數,傳入指定的CameraDevice.StateCallback回調,然后打開指定攝像頭,並觸發CameraDevice.StateCallback中的onOpened方法,並在onOpened方法里開始通過調用創建預覽會話,,CameraDevice負責創建請求CameraCharacteristics、CameraRequest與CameraRequest.Builder、CameraCaptureSession以及CaptureResult則可以看成是一個JavaBean的作用用於描述以什么樣的配置來處理這次請求。