
上一章《Camera2 概覽》里我們介紹了一些 Camera2 的基礎(chǔ)知識(shí),但是并沒有涉及太多的 API,從本章開始我們會(huì)開發(fā)一個(gè)具有完整相機(jī)功能的應(yīng)用程序,并且將相機(jī)知識(shí)分成多個(gè)篇章進(jìn)行介紹,而本章所要介紹的就是相機(jī)的開啟流程。
閱讀本章之后,你將學(xué)會(huì)以下幾個(gè)知識(shí)點(diǎn):
- 如何注冊(cè)相機(jī)相關(guān)的權(quán)限
- 如何配置相機(jī)特性要求
- 如何開啟相機(jī)
- 如何關(guān)閉相機(jī)
你可以在 https://github.com/darylgo/Camera2Sample 下載相關(guān)的源碼,并且切換到 Tutorial2 標(biāo)簽下。
1 創(chuàng)建相機(jī)項(xiàng)目
正如前所說的,我們會(huì)開發(fā)一個(gè)具有完整相機(jī)功能的應(yīng)用程序,所以第一步要做的就是創(chuàng)建一個(gè)相機(jī)項(xiàng)目,這里我用 AS 創(chuàng)建了一個(gè)叫 Camera2Sample 的項(xiàng)目,并且有一個(gè) Activity 叫 MainActivity。我們使用的開發(fā)語言是 Kotlin,所以如果你對(duì) Kotlin 還不熟悉的話,建議你先去學(xué)習(xí)下 Kotlin 的基礎(chǔ)知識(shí)。
為了降低源碼的閱讀難度,我不打算引入任何的第三方庫,不去關(guān)注性能問題,也不進(jìn)行任何模式上的設(shè)計(jì),大部分的代碼我都會(huì)寫在這個(gè) MainActivity 里面,所有的功能的實(shí)現(xiàn)都盡可能簡(jiǎn)化,讓閱讀者可以只關(guān)注重點(diǎn)。
2 注冊(cè)相關(guān)權(quán)限
在使用相機(jī) API 之前,必須在 AndroidManifest.xml 注冊(cè)相機(jī)權(quán)限 android.permission.CAMERA,聲明我們開發(fā)的應(yīng)用程序需要相機(jī)權(quán)限,另外如果你有保存照片的操作,那么讀寫 SD 卡的權(quán)限也是必須的:
<?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 以上的系統(tǒng)需要我們?cè)诔绦蜻\(yùn)行的時(shí)候進(jìn)行動(dòng)態(tài)權(quán)限申請(qǐng),所以我們需要在程序啟動(dòng)的時(shí)候去檢查權(quán)限,有任何一個(gè)必要的權(quán)限被用戶拒絕時(shí),我們就彈窗提示用戶程序因?yàn)闄?quán)限被拒絕而無法正常工作:
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
)
}
/**
* 判斷我們需要的權(quán)限是否被授予,只要有一個(gè)沒有授權(quán),我們都會(huì)返回 false,并且進(jìn)行權(quán)限申請(qǐng)操作。
*
* @return true 權(quán)限都被授權(quán)
*/
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()
}
}
3 配置相機(jī)特性要求
你一定不希望用戶在一臺(tái)沒有任何相機(jī)的手機(jī)上安裝你的相機(jī)應(yīng)用程序吧,因?yàn)槟菢幼鍪菦]有意義的。所以接下來要做的就是在 AndroidManifest.xml 中配置一些程序運(yùn)行時(shí)必要的相機(jī)特性,如果這些特性不支持,那么用戶在安裝 apk 的時(shí)候就會(huì)因?yàn)闂l件不符合而無法安裝。
<?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> 標(biāo)簽聲明了我們的應(yīng)用程序必須在具有相機(jī)的手機(jī)上才能運(yùn)行。另外你還可以配置更多的特性要求,例如必須支持自動(dòng)對(duì)焦的相機(jī)才能運(yùn)行你的應(yīng)用程序,更多的特性可以在 官方文檔 上查詢。
4 獲取 CameraManager 實(shí)例
CameraManager 是一個(gè)負(fù)責(zé)查詢和建立相機(jī)連接的系統(tǒng)服務(wù),可以說 CameraManager 是 Camera2 使用流程的起點(diǎn),所以首先我們要通過 getSystemService() 獲取 CameraManager 實(shí)例:
private val cameraManager: CameraManager by lazy { getSystemService(CameraManager::class.java) }
5 獲取相機(jī) ID 列表
接下來我們要獲取所有可用的相機(jī) ID 列表,這個(gè) ID 列表的長(zhǎng)度也代表有多少個(gè)相機(jī)可以使用。使用的 API 是 CameraManager.getCameraIdList(),它會(huì)返回一個(gè)包含所有可用相機(jī) ID 的字符串?dāng)?shù)組:
val cameraIdList = cameraManager.cameraIdList
注意:Kotlin 會(huì)將很多 Java API 的 getter 直接轉(zhuǎn)換成 Kotlin 的 property 語法,所以你會(huì)看到 getCameraIdList() 被轉(zhuǎn)換成了 cameraIdList,后續(xù)會(huì)有很多類似的轉(zhuǎn)換,這里提前說明下,避免誤解。
6 根據(jù)相機(jī) ID 獲取 CameraCharacteristics
CameraCharacteristics 是相機(jī)信息的提供者,通過它我們可以獲取所有相機(jī)信息,這里我們需要根據(jù)攝像頭的方向篩選出前置和后置攝像頭,并且要求相機(jī)的 Hardware Level 必須是 FULL 及以上,所以首先我們要獲取所有相機(jī)的 CameraCharacteristics 實(shí)例,涉及的 API 是 CameraManager.getCameraCharacteristics(),它會(huì)根據(jù)你指定的相機(jī) ID 返回對(duì)應(yīng)的相機(jī)信息:
/**
* 判斷相機(jī)的 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
}
}
}
7 開啟相機(jī)
接下來我們要做的就是調(diào)用 CameraManager.openCamera() 方法開啟相機(jī)了,該方法要求我們傳遞兩個(gè)參數(shù),一個(gè)是相機(jī) ID,一個(gè)是監(jiān)聽相機(jī)狀態(tài)的 CameraStateCallback。當(dāng)相機(jī)被成功開啟的時(shí)候會(huì)通過 CameraStateCallback.onOpened() 方法回調(diào)一個(gè) CameraDevice 實(shí)例給你,否則的話會(huì)通過 CameraStateCallback.onError() 方法回調(diào)一個(gè) CameraDevice 實(shí)例和一個(gè)錯(cuò)誤碼給你。onOpened() 和 onError() 其實(shí)都意味著相機(jī)已經(jīng)被開啟了,唯一的區(qū)別是 onError() 表示開啟過程中出了問題,你必須把傳遞給你的 CameraDevice 關(guān)閉,而不是繼續(xù)使用它,具體的 API 介紹可以自行查看文檔。另外,你必須確保在開啟相機(jī)之前已經(jīng)被授予了相機(jī)權(quán)限,否則會(huì)拋權(quán)限異常。一個(gè)比較穩(wěn)妥的做法就是每次開啟相機(jī)之前檢查相機(jī)權(quán)限。下面是主要代碼片段:
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, "相機(jī)已開啟", Toast.LENGTH_SHORT).show() }
}
@WorkerThread
override fun onError(camera: CameraDevice, error: Int) {
camera.close()
cameraDevice = null
}
}
8 關(guān)閉相機(jī)
和其他硬件資源的使用一樣,當(dāng)我們不再需要使用相機(jī)時(shí)記得調(diào)用 CameraDevice.close() 方法及時(shí)關(guān)閉相機(jī)回收資源。關(guān)閉相機(jī)的操作至關(guān)重要,因?yàn)槿绻阋恢闭加孟鄼C(jī)資源,其他基于相機(jī)開發(fā)的功能都會(huì)無法正常使用,嚴(yán)重情況下直接導(dǎo)致其他相機(jī)相關(guān)的 APP 無法正常使用,當(dāng)相機(jī)被完全關(guān)閉的時(shí)候會(huì)通過 CameraStateCallback.onCllosed() 方法通知你相機(jī)已經(jīng)被關(guān)閉。那么在什么時(shí)候關(guān)閉相機(jī)最合適呢?我個(gè)人的建議是在 onPause() 的時(shí)候就一定要關(guān)閉相機(jī),因?yàn)樵谶@個(gè)時(shí)候相機(jī)頁面已經(jīng)不是用戶關(guān)注的焦點(diǎn),大部分情況下已經(jīng)可以關(guān)閉相機(jī)了。
@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, "相機(jī)已關(guān)閉", Toast.LENGTH_SHORT).show() }
}
}
至此,關(guān)于開關(guān)相機(jī)的教程就結(jié)束了,下一章我們會(huì)介紹如何開啟預(yù)覽。