上一章《Camera2 概覽》裏咱們介紹了一些 Camera2 的基礎知識,可是並無涉及太多的 API,從本章開始咱們會開發一個具備完整相機功能的應用程序,而且將相機知識分紅多個篇章進行介紹,而本章所要介紹的就是相機的開啓流程。html
閱讀本章以後,你將學會如下幾個知識點:java
你能夠在 https://github.com/darylgo/Camera2Sample 下載相關的源碼,而且切換到 Tutorial2 標籤下。android
正如前所說的,咱們會開發一個具備完整相機功能的應用程序,因此第一步要作的就是建立一個相機項目,這裏我用 AS 建立了一個叫 Camera2Sample 的項目,而且有一個 Activity 叫 MainActivity。咱們使用的開發語言是 Kotlin,因此若是你對 Kotlin 還不熟悉的話,建議你先去學習下 Kotlin 的基礎知識。git
爲了下降源碼的閱讀難度,我不打算引入任何的第三方庫,不去關注性能問題,也不進行任何模式上的設計,大部分的代碼我都會寫在這個 MainActivity 裏面,全部的功能的實現都儘量簡化,讓閱讀者能夠只關注重點。github
在使用相機 API 以前,必須在 AndroidManifest.xml 註冊相機權限 android.permission.CAMERA,聲明咱們開發的應用程序須要相機權限,另外若是你有保存照片的操做,那麼讀寫 SD 卡的權限也是必須的:數組
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.darylgo.camera.sample"> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> </manifest>
須要注意的是 6.0 以上的系統須要咱們在程序運行的時候進行動態權限申請,因此咱們須要在程序啓動的時候去檢查權限,有任何一個必要的權限被用戶拒絕時,咱們就彈窗提示用戶程序由於權限被拒絕而沒法正常工做:ide
class MainActivity : AppCompatActivity() { companion object { private const val REQUEST_PERMISSION_CODE: Int = 1 private val REQUIRED_PERMISSIONS: Array<String> = arrayOf( android.Manifest.permission.CAMERA, android.Manifest.permission.WRITE_EXTERNAL_STORAGE ) } /** * 判斷咱們須要的權限是否被授予,只要有一個沒有受權,咱們都會返回 false,而且進行權限申請操做。 * * @return true 權限都被受權 */ private fun checkRequiredPermissions(): Boolean { val deniedPermissions = mutableListOf<String>() for (permission in REQUIRED_PERMISSIONS) { if (ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_DENIED) { deniedPermissions.add(permission) } } if (deniedPermissions.isEmpty().not()) { requestPermissions(deniedPermissions.toTypedArray(), REQUEST_PERMISSION_CODE) } return deniedPermissions.isEmpty() } }
你必定不但願用戶在一臺沒有任何相機的手機上安裝你的相機應用程序吧,由於那樣作是沒有意義的。因此接下來要作的就是在 AndroidManifest.xml 中配置一些程序運行時必要的相機特性,若是這些特性不支持,那麼用戶在安裝 apk 的時候就會由於條件不符合而沒法安裝。性能
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.darylgo.camera.sample"> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-feature android:name="android.hardware.camera" android:required="true" /> </manifest>
咱們經過 <uses-feature> 標籤聲明瞭咱們的應用程序必須在具備相機的手機上才能運行。另外你還能夠配置更多的特性要求,例如必須支持自動對焦的相機才能運行你的應用程序,更多的特性能夠在 官方文檔 上查詢。學習
CameraManager 是一個負責查詢和創建相機鏈接的系統服務,能夠說 CameraManager 是 Camera2 使用流程的起點,因此首先咱們要經過 getSystemService() 獲取 CameraManager 實例:ui
private val cameraManager: CameraManager by lazy { getSystemService(CameraManager::class.java) }
接下來咱們要獲取全部可用的相機 ID 列表,這個 ID 列表的長度也表明有多少個相機可使用。使用的 API 是 CameraManager.getCameraIdList(),它會返回一個包含全部可用相機 ID 的字符串數組:
val cameraIdList = cameraManager.cameraIdList
注意:Kotlin 會將不少 Java API 的 getter 直接轉換成 Kotlin 的 property 語法,因此你會看到 getCameraIdList() 被轉換成了 cameraIdList,後續會有不少相似的轉換,這裏提早說明下,避免誤解。
CameraCharacteristics 是相機信息的提供者,經過它咱們能夠獲取全部相機信息,這裏咱們須要根據攝像頭的方向篩選出前置和後置攝像頭,而且要求相機的 Hardware Level 必須是 FULL 及以上,因此首先咱們要獲取全部相機的 CameraCharacteristics 實例,涉及的 API 是 CameraManager.getCameraCharacteristics(),它會根據你指定的相機 ID 返回對應的相機信息:
/** * 判斷相機的 Hardware Level 是否大於等於指定的 Level。 */ fun CameraCharacteristics.isHardwareLevelSupported(requiredLevel: Int): Boolean { val sortedLevels = intArrayOf( CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY, CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED, CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL, CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3 ) val deviceLevel = this[CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL] if (requiredLevel == deviceLevel) { return true } for (sortedLevel in sortedLevels) { if (requiredLevel == sortedLevel) { return true } else if (deviceLevel == sortedLevel) { return false } } return false }
// 遍歷全部可用的攝像頭 ID,只取出其中的前置和後置攝像頭信息。 val cameraIdList = cameraManager.cameraIdList cameraIdList.forEach { cameraId -> val cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId) if (cameraCharacteristics.isHardwareLevelSupported(REQUIRED_SUPPORTED_HARDWARE_LEVEL)) { if (cameraCharacteristics[CameraCharacteristics.LENS_FACING] == CameraCharacteristics.LENS_FACING_FRONT) { frontCameraId = cameraId frontCameraCharacteristics = cameraCharacteristics } else if (cameraCharacteristics[CameraCharacteristics.LENS_FACING] == CameraCharacteristics.LENS_FACING_BACK) { backCameraId = cameraId backCameraCharacteristics = cameraCharacteristics } } }
接下來咱們要作的就是調用 CameraManager.openCamera() 方法開啓相機了,該方法要求咱們傳遞兩個參數,一個是相機 ID,一個是監聽相機狀態的 CameraStateCallback。當相機被成功開啓的時候會經過 CameraStateCallback.onOpened() 方法回調一個 CameraDevice 實例給你,不然的話會經過 CameraStateCallback.onError() 方法回調一個 CameraDevice 實例和一個錯誤碼給你。onOpened() 和 onError() 其實都意味着相機已經被開啓了,惟一的區別是 onError() 表示開啓過程當中出了問題,你必須把傳遞給你的 CameraDevice 關閉,而不是繼續使用它,具體的 API 介紹能夠自行查看文檔。另外,你必須確保在開啓相機以前已經被授予了相機權限,不然會拋權限異常。一個比較穩妥的作法就是每次開啓相機以前檢查相機權限。下面是主要代碼片斷:
private data class OpenCameraMessage(val cameraId: String, val cameraStateCallback: CameraStateCallback) @SuppressLint("MissingPermission") override fun handleMessage(msg: Message): Boolean { when (msg.what) { MSG_OPEN_CAMERA -> { val openCameraMessage = msg.obj as OpenCameraMessage val cameraId = openCameraMessage.cameraId val cameraStateCallback = openCameraMessage.cameraStateCallback cameraManager.openCamera(cameraId, cameraStateCallback, cameraHandler) Log.d(TAG, "Handle message: MSG_OPEN_CAMERA") } } return false } private fun openCamera() { // 有限選擇後置攝像頭,其次纔是前置攝像頭。 val cameraId = backCameraId ?: frontCameraId if (cameraId != null) { val openCameraMessage = OpenCameraMessage(cameraId, CameraStateCallback()) cameraHandler?.obtainMessage(MSG_OPEN_CAMERA, openCameraMessage)?.sendToTarget() } else { throw RuntimeException("Camera id must not be null.") } }
private inner class CameraStateCallback : CameraDevice.StateCallback() { @WorkerThread override fun onOpened(camera: CameraDevice) { cameraDevice = camera runOnUiThread { Toast.makeText(this@MainActivity, "相機已開啓", Toast.LENGTH_SHORT).show() } } @WorkerThread override fun onError(camera: CameraDevice, error: Int) { camera.close() cameraDevice = null } }
和其餘硬件資源的使用同樣,當咱們再也不須要使用相機時記得調用 CameraDevice.close() 方法及時關閉相機回收資源。關閉相機的操做相當重要,由於若是你一直佔用相機資源,其餘基於相機開發的功能都會沒法正常使用,嚴重狀況下直接致使其餘相機相關的 APP 沒法正常使用,當相機被徹底關閉的時候會經過 CameraStateCallback.onCllosed() 方法通知你相機已經被關閉。那麼在何時關閉相機最合適呢?我我的的建議是在 onPause() 的時候就必定要關閉相機,由於在這個時候相機頁面已經不是用戶關注的焦點,大部分狀況下已經能夠關閉相機了。
@SuppressLint("MissingPermission") override fun handleMessage(msg: Message): Boolean { when (msg.what) { MSG_CLOSE_CAMERA -> { cameraDevice?.close() Log.d(TAG, "Handle message: MSG_CLOSE_CAMERA") } } return false } override fun onPause() { super.onPause() closeCamera() } private fun closeCamera() { cameraHandler?.sendEmptyMessage(MSG_CLOSE_CAMERA) }
private inner class CameraStateCallback : CameraDevice.StateCallback() { @WorkerThread override fun onClosed(camera: CameraDevice) { cameraDevice = null runOnUiThread { Toast.makeText(this@MainActivity, "相機已關閉", Toast.LENGTH_SHORT).show() } } }
至此,關於開關相機的教程就結束了,下一章咱們會介紹如何開啓預覽。