前言
由前面的幾篇博客可以知道,openCamera,createCaptureSession,setRepeatingRequest,capture是非常重要的過程,如果其中一個環節出了問題時該如何分析呢,這里我們首先從打開相機流程時,打開相機失敗了的情況進行講述說明。
注:
以下是需要說明的系統源碼(源碼皆來自谷歌Android12源碼,如需要下載對應源碼,可根據我之前的博客進行下載。
下載鏈接為:淺談Android12系統源碼下載
APP
在相機問題排查中,優先從 “上層”(應用層、框架層 API 使用)開始分析,是基于問題發生概率、排查成本、開發者可控性和邏輯遞進關系的實踐原則。
以下是簡單的示例代碼,具體使用請按api使用規范使用
// 打開相機private void openCamera() {if (mCameraId == null || mCameraManager == null) {return;}try {// 打開相機mCameraManager.openCamera(mCameraId, mStateCallback, mBackgroundHandler);} catch (CameraAccessException e) {e.printStackTrace();runOnUiThread(() -> Toast.makeText(this, "打開相機失敗", Toast.LENGTH_SHORT).show());}}// 相機設備狀態回調private final CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {@Overridepublic void onOpened(@NonNull CameraDevice camera) {// 相機打開成功,保存CameraDevice實例并創建預覽會話mCameraDevice = camera;// 相機連接成功后開始配流操作createCaptureSession();}@Overridepublic void onDisconnected(@NonNull CameraDevice camera) {// 相機斷連記得釋放資源camera.close();mCameraDevice = null;}@Overridepublic void onError(@NonNull CameraDevice camera, int error) {camera.close();// 這里的回調就表示相機打開失敗了,可以新增一個提示,告訴用戶無法連接相機mCameraDevice = null;runOnUiThread(() -> Toast.makeText(Camera2PreviewActivity.this,"相機打開失敗: " + error, Toast.LENGTH_SHORT).show());}};
可以看到如果連接失敗了,上層能通過回調很快就知道了連接失敗的結果,可以加一下打印如果出現問題時可以快速分析定位是否連接過程出現問題了,甚至還可以根據error對應具體發生了什么問題,以下皆為示例代碼
public class CameraErrorHandler {private static final String TAG = "CameraErrorHandler";// CameraDevice.StateCallback 實現,包含錯誤處理public CameraDevice.StateCallback getStateCallback() {return new CameraDevice.StateCallback() {@Overridepublic void onOpened(@NonNull CameraDevice camera) {Log.d(TAG, "相機打開成功");// 相機打開后的初始化操作(如創建會話)}@Overridepublic void onDisconnected(@NonNull CameraDevice camera) {Log.w(TAG, "相機連接斷開,關閉相機");camera.close();}@Overridepublic void onError(@NonNull CameraDevice camera, int error) {Log.e(TAG, "相機錯誤,錯誤碼: " + error);// 解析錯誤原因String errorMessage = getErrorMessage(error);Log.e(TAG, "錯誤原因: " + errorMessage);// 無論何種錯誤,都需要關閉相機釋放資源camera.close();// 可根據錯誤類型執行不同的恢復策略handleErrorByType(error);}};}// 解析錯誤碼對應的具體原因private String getErrorMessage(int error) {switch (error) {case CameraDevice.StateCallback.ERROR_CAMERA_IN_USE:return "相機正在被其他應用使用(已被占用)";case CameraDevice.StateCallback.ERROR_MAX_CAMERAS_IN_USE:return "已達到系統同時打開相機的最大數量";case CameraDevice.StateCallback.ERROR_CAMERA_DISABLED:return "相機被系統禁用(如設備管理策略限制)";case CameraDevice.StateCallback.ERROR_CAMERA_DEVICE:return "相機硬件設備發生故障";case CameraDevice.StateCallback.ERROR_CAMERA_SERVICE:return "相機服務(cameraserver)崩潰或未響應";case CameraDevice.StateCallback.ERROR_PERMISSION_DENIED:return "沒有相機權限(用戶未授權)";case CameraDevice.StateCallback.ERROR_TIMED_OUT:return "操作超時(如打開相機超時)";default:return "未知錯誤(錯誤碼: " + error + "),可能是設備特定錯誤";}}// 根據錯誤類型執行不同的處理策略private void handleErrorByType(int error) {switch (error) {case CameraDevice.StateCallback.ERROR_CAMERA_IN_USE:// 相機被占用:提示用戶關閉其他應用showUserMessage("相機正在被使用,請關閉其他應用后重試");break;case CameraDevice.StateCallback.ERROR_PERMISSION_DENIED:// 權限被拒:引導用戶去設置頁開啟權限showUserMessage("請授予相機權限以使用相機功能");navigateToPermissionSettings();break;case CameraDevice.StateCallback.ERROR_CAMERA_DISABLED:// 相機被禁用:提示用戶檢查設備設置showUserMessage("相機已被系統禁用,請在設置中啟用");break;case CameraDevice.StateCallback.ERROR_CAMERA_DEVICE:case CameraDevice.StateCallback.ERROR_CAMERA_SERVICE:// 硬件或服務錯誤:嘗試重啟相機showUserMessage("相機服務異常,正在嘗試恢復...");retryOpenCamera();break;default:// 其他錯誤:通用提示showUserMessage("相機使用失敗,請稍后重試");}}// 以下為輔助方法(實際使用時需根據應用場景實現)private void showUserMessage(String message) {// 顯示Toast或Snackbar等用戶提示Log.d(TAG, "用戶提示: " + message);}private void navigateToPermissionSettings() {// 跳轉到應用權限設置頁}private void retryOpenCamera() {// 重試打開相機的邏輯}
}
為啥會回調onError繼續往下看源碼
java framework
我們知道打開相機會調用到下面的函數
frameworks/base/core/java/android/hardware/camera2/CameraManager.java
private CameraDevice openCameraDeviceUserAsync(String cameraId,CameraDevice.StateCallback callback, Executor executor, final int uid,final int oomScoreOffset) throws CameraAccessException {try {ICameraService cameraService = CameraManagerGlobal.get().getCameraService();if (cameraService == null) {throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED,"Camera service is currently unavailable");}cameraUser = cameraService.connectDevice(callbacks, cameraId,mContext.getOpPackageName(), mContext.getAttributionTag(), uid,oomScoreOffset, mContext.getApplicationInfo().targetSdkVersion);} catch (ServiceSpecificException e) {if (e.errorCode == ICameraService.ERROR_DEPRECATED_HAL) {throw new AssertionError("Should've gone down the shim path");} else if (e.errorCode == ICameraService.ERROR_CAMERA_IN_USE ||e.errorCode == ICameraService.ERROR_MAX_CAMERAS_IN_USE ||e.errorCode == ICameraService.ERROR_DISABLED ||e.errorCode == ICameraService.ERROR_DISCONNECTED ||e.errorCode == ICameraService.ERROR_INVALID_OPERATION) {// Received one of the known connection errors// The remote camera device cannot be connected to, so// set the local camera to the startup error state//這里回調onError或者onDisconnecteddeviceImpl.setRemoteFailure(e);}} catch (RemoteException e) {// Camera service died - act as if it's a CAMERA_DISCONNECTED caseServiceSpecificException sse = new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED,"Camera service is currently unavailable");//這里回調onError或者onDisconnecteddeviceImpl.setRemoteFailure(sse);throwAsPublicException(sse);}// TODO: factor out callback to be non-nested, then move setter to constructor// For now, calling setRemoteDevice will fire initial// onOpened/onUnconfigured callbacks.// This function call may post onDisconnected and throw CAMERA_DISCONNECTED if// cameraUser dies during setup.//這里會回調onOpeneddeviceImpl.setRemoteDevice(cameraUser);device = deviceImpl;}return device;}
直接看deviceImpl.setRemoteFailure做了什么
frameworks/base/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
public void setRemoteFailure(final ServiceSpecificException failure) {int failureCode = StateCallback.ERROR_CAMERA_DEVICE;boolean failureIsError = true;switch (failure.errorCode) {case ICameraService.ERROR_CAMERA_IN_USE:failureCode = StateCallback.ERROR_CAMERA_IN_USE;break;case ICameraService.ERROR_MAX_CAMERAS_IN_USE:failureCode = StateCallback.ERROR_MAX_CAMERAS_IN_USE;break;case ICameraService.ERROR_DISABLED:failureCode = StateCallback.ERROR_CAMERA_DISABLED;break;case ICameraService.ERROR_DISCONNECTED:failureIsError = false;break;case ICameraService.ERROR_INVALID_OPERATION:failureCode = StateCallback.ERROR_CAMERA_DEVICE;break;default:Log.e(TAG, "Unexpected failure in opening camera device: " + failure.errorCode +failure.getMessage());break;}final int code = failureCode;final boolean isError = failureIsError;synchronized(mInterfaceLock) {mInError = true;mDeviceExecutor.execute(new Runnable() {@Overridepublic void run() {if (isError) {mDeviceCallback.onError(CameraDeviceImpl.this, code);} else {mDeviceCallback.onDisconnected(CameraDeviceImpl.this);}}});}}
ok,這里就回調到app了,app就能知道是什么原因導致的open失敗了
native service
這里是這邊文章的核心內容,已在關鍵位置加了注釋,后續根據注釋一一說明
frameworks/av/services/camera/libcameraservice/CameraService.cpp
template<class CALLBACK, class CLIENT>
Status CameraService::connectHelper(const sp<CALLBACK>& cameraCb, const String8& cameraId,int api1CameraId, const String16& clientPackageName,const std::optional<String16>& clientFeatureId, int clientUid, int clientPid,apiLevel effectiveApiLevel, bool shimUpdateOnly, int oomScoreOffset, int targetSdkVersion,/*out*/sp<CLIENT>& device) {binder::Status ret = binder::Status::ok();String8 clientName8(clientPackageName);int originalClientPid = 0;ALOGI("CameraService::connect call (PID %d \"%s\", camera ID %s) and ""Camera API version %d", clientPid, clientName8.string(), cameraId.string(),static_cast<int>(effectiveApiLevel));nsecs_t openTimeNs = systemTime();sp<CLIENT> client = nullptr;int facing = -1;int orientation = 0;bool isNdk = (clientPackageName.size() == 0);{// 注釋一// Acquire mServiceLock and prevent other clients from connectingstd::unique_ptr<AutoConditionLock> lock =AutoConditionLock::waitAndAcquire(mServiceLockWrapper, DEFAULT_CONNECT_TIMEOUT_NS);if (lock == nullptr) {ALOGE("CameraService::connect (PID %d) rejected (too many other clients connecting).", clientPid);return STATUS_ERROR_FMT(ERROR_MAX_CAMERAS_IN_USE,"Cannot open camera %s for \"%s\" (PID %d): Too many other clients connecting",cameraId.string(), clientName8.string(), clientPid);}//注釋二// Enforce client permissions and do basic validity checksif(!(ret = validateConnectLocked(cameraId, clientName8,/*inout*/clientUid, /*inout*/clientPid, /*out*/originalClientPid)).isOk()) {return ret;}// Check the shim parameters after acquiring lock, if they have already been updated and// we were doing a shim update, return immediatelyif (shimUpdateOnly) {auto cameraState = getCameraState(cameraId);if (cameraState != nullptr) {if (!cameraState->getShimParams().isEmpty()) return ret;}}status_t err;sp<BasicClient> clientTmp = nullptr;std::shared_ptr<resource_policy::ClientDescriptor<String8, sp<BasicClient>>> partial;//注釋三if ((err = handleEvictionsLocked(cameraId, originalClientPid, effectiveApiLevel,IInterface::asBinder(cameraCb), clientName8, oomScoreOffset, /*out*/&clientTmp,/*out*/&partial)) != NO_ERROR) {switch (err) {case -ENODEV:return STATUS_ERROR_FMT(ERROR_DISCONNECTED,"No camera device with ID \"%s\" currently available",cameraId.string());case -EBUSY:return STATUS_ERROR_FMT(ERROR_CAMERA_IN_USE,"Higher-priority client using camera, ID \"%s\" currently unavailable",cameraId.string());case -EUSERS:return STATUS_ERROR_FMT(ERROR_MAX_CAMERAS_IN_USE,"Too many cameras already open, cannot open camera \"%s\"",cameraId.string());default:return STATUS_ERROR_FMT(ERROR_INVALID_OPERATION,"Unexpected error %s (%d) opening camera \"%s\"",strerror(-err), err, cameraId.string());}}if (clientTmp.get() != nullptr) {// Handle special case for API1 MediaRecorder where the existing client is returneddevice = static_cast<CLIENT*>(clientTmp.get());return ret;}// give flashlight a chance to close devices if necessary.mFlashlight->prepareDeviceOpen(cameraId);int deviceVersion = getDeviceVersion(cameraId, /*out*/&facing, /*out*/&orientation);if (facing == -1) {ALOGE("%s: Unable to get camera device \"%s\" facing", __FUNCTION__, cameraId.string());return STATUS_ERROR_FMT(ERROR_INVALID_OPERATION,"Unable to get camera device \"%s\" facing", cameraId.string());}sp<BasicClient> tmp = nullptr;bool overrideForPerfClass = SessionConfigurationUtils::targetPerfClassPrimaryCamera(mPerfClassPrimaryCameraIds, cameraId.string(), targetSdkVersion);if(!(ret = makeClient(this, cameraCb, clientPackageName, clientFeatureId,cameraId, api1CameraId, facing, orientation,clientPid, clientUid, getpid(),deviceVersion, effectiveApiLevel, overrideForPerfClass,/*out*/&tmp)).isOk()) {return ret;}client = static_cast<CLIENT*>(tmp.get());LOG_ALWAYS_FATAL_IF(client.get() == nullptr, "%s: CameraService in invalid state",__FUNCTION__)//注釋四err = client->initialize(mCameraProviderManager, mMonitorTags);if (err != OK) {ALOGE("%s: Could not initialize client from HAL.", __FUNCTION__);// Errors could be from the HAL module open call or from AppOpsManagerswitch(err) {case BAD_VALUE:return STATUS_ERROR_FMT(ERROR_ILLEGAL_ARGUMENT,"Illegal argument to HAL module for camera \"%s\"", cameraId.string());case -EBUSY:return STATUS_ERROR_FMT(ERROR_CAMERA_IN_USE,"Camera \"%s\" is already open", cameraId.string());case -EUSERS:return STATUS_ERROR_FMT(ERROR_MAX_CAMERAS_IN_USE,"Too many cameras already open, cannot open camera \"%s\"",cameraId.string());case PERMISSION_DENIED:return STATUS_ERROR_FMT(ERROR_PERMISSION_DENIED,"No permission to open camera \"%s\"", cameraId.string());case -EACCES:return STATUS_ERROR_FMT(ERROR_DISABLED,"Camera \"%s\" disabled by policy", cameraId.string());case -ENODEV:default:return STATUS_ERROR_FMT(ERROR_INVALID_OPERATION,"Failed to initialize camera \"%s\": %s (%d)", cameraId.string(),strerror(-err), err);}}// Update shim paremeters for legacy clientsif (effectiveApiLevel == API_1) {// Assume we have always received a Client subclass for API1sp<Client> shimClient = reinterpret_cast<Client*>(client.get());String8 rawParams = shimClient->getParameters();CameraParameters params(rawParams);auto cameraState = getCameraState(cameraId);if (cameraState != nullptr) {cameraState->setShimParams(params);} else {ALOGE("%s: Cannot update shim parameters for camera %s, no such device exists.",__FUNCTION__, cameraId.string());}}// Set rotate-and-crop override behaviorif (mOverrideRotateAndCropMode != ANDROID_SCALER_ROTATE_AND_CROP_AUTO) {client->setRotateAndCropOverride(mOverrideRotateAndCropMode);} else if (CameraServiceProxyWrapper::isRotateAndCropOverrideNeeded(clientPackageName,orientation, facing)) {client->setRotateAndCropOverride(ANDROID_SCALER_ROTATE_AND_CROP_90);}// Set camera muting behaviorbool isCameraPrivacyEnabled =mSensorPrivacyPolicy->isCameraPrivacyEnabled(multiuser_get_user_id(clientUid));if (client->supportsCameraMute()) {client->setCameraMute(mOverrideCameraMuteMode || isCameraPrivacyEnabled);} else if (isCameraPrivacyEnabled) {// no camera mute supported, but privacy is on! => disconnectALOGI("Camera mute not supported for package: %s, camera id: %s",String8(client->getPackageName()).string(), cameraId.string());// Do not hold mServiceLock while disconnecting clients, but// retain the condition blocking other clients from connecting// in mServiceLockWrapper if held.mServiceLock.unlock();// Clear caller identity temporarily so client disconnect PID// checks work correctlyint64_t token = CameraThreadState::clearCallingIdentity();// Note AppOp to trigger the "Unblock" dialogclient->noteAppOp();client->disconnect();CameraThreadState::restoreCallingIdentity(token);// Reacquire mServiceLockmServiceLock.lock();return STATUS_ERROR_FMT(ERROR_DISABLED,"Camera \"%s\" disabled due to camera mute", cameraId.string());}if (shimUpdateOnly) {// If only updating legacy shim parameters, immediately disconnect clientmServiceLock.unlock();client->disconnect();mServiceLock.lock();} else {// Otherwise, add client to active clients listfinishConnectLocked(client, partial, oomScoreOffset);}client->setImageDumpMask(mImageDumpMask);} // lock is destroyed, allow further connect calls// Important: release the mutex here so the client can call back into the service from its// destructor (can be at the end of the call)device = client;int32_t openLatencyMs = ns2ms(systemTime() - openTimeNs);CameraServiceProxyWrapper::logOpen(cameraId, facing, clientPackageName,effectiveApiLevel, isNdk, openLatencyMs);return ret;
}
注釋一
// Acquire mServiceLock and prevent other clients from connectingstd::unique_ptr<AutoConditionLock> lock =AutoConditionLock::waitAndAcquire(mServiceLockWrapper, DEFAULT_CONNECT_TIMEOUT_NS);if (lock == nullptr) {ALOGE("CameraService::connect (PID %d) rejected (too many other clients connecting).", clientPid);return STATUS_ERROR_FMT(ERROR_MAX_CAMERAS_IN_USE,"Cannot open camera %s for \"%s\" (PID %d): Too many other clients connecting",
mServiceLock 是相機服務的一個核心鎖,用于控制多個客戶端同時連接相機服務的并發訪問。
AutoConditionLock::waitAndAcquire 是一個封裝的鎖操作:
嘗試獲取鎖,如果當前鎖被其他客戶端占用,會進入等待狀態。
等待超時時間由 DEFAULT_CONNECT_TIMEOUT_NS 定義(通常是一個固定的納秒值,如幾秒鐘)。
成功獲取鎖后,返回一個 lock 對象(用于后續自動釋放鎖);失敗則返回 nullptr。
如果 lock 為 nullptr,表示在超時時間內未能獲取到鎖,通常原因是同時有太多客戶端在嘗試連接相機服務,導致當前客戶端等待超時。
此時會打印錯誤日志(ALOGE),并返回 ERROR_MAX_CAMERAS_IN_USE 錯誤,告知客戶端連接失敗。
核心作用
這段代碼的本質是限制相機服務的并發連接數,相機服務作為系統核心服務,需要處理多個客戶端的相機請求,但同時連接的客戶端數量過多可能導致資源耗盡、響應緩慢等問題。
通過 mServiceLock 控制并發,確保同一時間只有有限數量的客戶端能執行連接邏輯,避免服務過載。
當并發連接數超過系統承載能力(導致當前客戶端超時無法獲取鎖)時,拒絕新連接并返回錯誤,保護系統穩定性。
注釋二
//注釋二// Enforce client permissions and do basic validity checksif(!(ret = validateConnectLocked(cameraId, clientName8,/*inout*/clientUid, /*inout*/clientPid, /*out*/originalClientPid)).isOk()) {return ret;}
該函數內部會執行一系列關鍵檢查(具體邏輯依賴 Android 版本,但通常包括):
權限驗證:
檢查客戶端是否擁有訪問相機的權限(如 CAMERA 權限)。
對于特殊相機(如系統相機、受限制的硬件相機),檢查是否有額外的權限或簽名驗證(如系統應用簽名)。
參數有效性檢查:
驗證 cameraId 是否存在(是否為系統中實際可用的相機 ID)。
檢查 clientUid/clientPid 的合法性(如是否為有效的進程 ID,是否屬于當前系統中的活躍進程)。
客戶端身份確認:
確認客戶端的真實身份(避免權限偽造),例如通過進程 ID 追溯應用的包名和簽名。
在跨進程場景(如通過 binder 調用)中,修正并記錄原始客戶端的進程 ID(通過 originalClientPid 輸出)。
注釋三
//注釋三if ((err = handleEvictionsLocked(cameraId, originalClientPid, effectiveApiLevel,IInterface::asBinder(cameraCb), clientName8, oomScoreOffset, /*out*/&clientTmp,/*out*/&partial)) != NO_ERROR) {switch (err) {case -ENODEV:return STATUS_ERROR_FMT(ERROR_DISCONNECTED,"No camera device with ID \"%s\" currently available",cameraId.string());case -EBUSY:return STATUS_ERROR_FMT(ERROR_CAMERA_IN_USE,"Higher-priority client using camera, ID \"%s\" currently unavailable",cameraId.string());case -EUSERS:return STATUS_ERROR_FMT(ERROR_MAX_CAMERAS_IN_USE,"Too many cameras already open, cannot open camera \"%s\"",cameraId.string());default:return STATUS_ERROR_FMT(ERROR_INVALID_OPERATION,"Unexpected error %s (%d) opening camera \"%s\"",strerror(-err), err, cameraId.string());}}
這段代碼的核心是調用 handleEvictionsLocked() 函數,并根據其返回的錯誤碼(err)進行不同的錯誤處理,該函數是相機服務內部的關鍵邏輯,作用是檢查當前相機設備(cameraId)的占用情況
處理「資源驅逐」,當新客戶端請求打開相機時,如果相機已被占用或系統資源不足,可能需要終止低優先級的現有客戶端,為新客戶端騰出資源。
參數包括相機 ID、客戶端進程 ID、API 版本、回調接口、客戶端名稱等,用于標識和評估新客戶端的優先級
函數返回值 err 表示操作結果:NO_ERROR 表示成功(可能驅逐了低優先級客戶端),非零值表示失敗。
根據 handleEvictionsLocked() 返回的錯誤碼,返回對應的相機服務錯誤狀態:
-ENODEV:相機設備不存在或不可用(如硬件故障、被移除),返回 ERROR_DISCONNECTED 錯誤。
-EBUSY:相機已被更高優先級的客戶端占用(無法驅逐),返回 ERROR_CAMERA_IN_USE 錯誤。
-EUSERS:系統中已打開的相機數量達到上限,返回 ERROR_MAX_CAMERAS_IN_USE 錯誤。
默認情況:其他未預期的錯誤,返回 ERROR_INVALID_OPERATION 并附帶錯誤詳情。
特別需要注意的是,為什么驅逐解決不了 EUSERS問題?
那是因為驅逐只能釋放某個特定相機的占用權(讓新客戶端使用同一個相機),但無法增加「系統允許同時打開的相機總數」。
例如:系統最多允許打開 2 個相機,當前已打開相機 A 和 B。此時新客戶端請求打開相機 C,即使 A 和 B 的客戶端優先級很低,驅逐它們只能釋放 A 或 B 的使用權,但新客戶端要打開的是 C(第三個相機),總數量仍然超過上限,因此必然觸發 EUSERS。
注釋四
err = client->initialize(mCameraProviderManager, mMonitorTags);if (err != OK) {ALOGE("%s: Could not initialize client from HAL.", __FUNCTION__);// Errors could be from the HAL module open call or from AppOpsManagerswitch(err) {case BAD_VALUE:return STATUS_ERROR_FMT(ERROR_ILLEGAL_ARGUMENT,"Illegal argument to HAL module for camera \"%s\"", cameraId.string());case -EBUSY:return STATUS_ERROR_FMT(ERROR_CAMERA_IN_USE,"Camera \"%s\" is already open", cameraId.string());case -EUSERS:return STATUS_ERROR_FMT(ERROR_MAX_CAMERAS_IN_USE,"Too many cameras already open, cannot open camera \"%s\"",cameraId.string());case PERMISSION_DENIED:return STATUS_ERROR_FMT(ERROR_PERMISSION_DENIED,"No permission to open camera \"%s\"", cameraId.string());case -EACCES:return STATUS_ERROR_FMT(ERROR_DISABLED,"Camera \"%s\" disabled by policy", cameraId.string());case -ENODEV:default:return STATUS_ERROR_FMT(ERROR_INVALID_OPERATION,"Failed to initialize camera \"%s\": %s (%d)", cameraId.string(),strerror(-err), err);}}
由前面文章可以知道,這里是連接hal去打開相機的流程,需要注意這個階段也會有失敗的可能
可以看到上面有很多條件會導致openCamera的過程失敗直接返回,可以在相對應的位置加特定的日志以方便獲取打開相機失敗的具體原因,可以歸于上層原因,還是系統資源原因,還是底層原因,并且由于在前期相機適配過程中,相機打開失敗的問題還是很常見的,所以很有必要去熟悉connectHelper的流程。
底層
底層這里不討論
總結
還有特別重要的預覽也需要先說明下,比如預覽卡頓問題,也是可以分為上層,中間層,底層去分析,上層主要是監聽onCaptureCompleted,onCaptureFailed回調,中間層的話主要看processCaptureResult函數,底層的話主要看是否有出幀打印,這是一個分析方向,也是一個引子,就先這樣吧,后面有空再重新排一下版。