Android Camera2 教程 · 第二章 · 開關(guān)相機(jī)

Android Camera

上一章《Camera2 概覽》里我們介紹了一些 Camera2 的基礎(chǔ)知識(shí),但是并沒有涉及太多的 API,從本章開始我們會(huì)開發(fā)一個(gè)具有完整相機(jī)功能的應(yīng)用程序,并且將相機(jī)知識(shí)分成多個(gè)篇章進(jìn)行介紹,而本章所要介紹的就是相機(jī)的開啟流程。

閱讀本章之后,你將學(xué)會(huì)以下幾個(gè)知識(shí)點(diǎn):

  1. 如何注冊(cè)相機(jī)相關(guān)的權(quán)限
  2. 如何配置相機(jī)特性要求
  3. 如何開啟相機(jī)
  4. 如何關(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ù)覽。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容