上一章《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() } } }
至此,关于开关相机的教程就结束了,下一章咱们会介绍如何开启预览。