Android中CameraX的基本使用(Kotlin實現)

?Andoird中拍照、錄像是很常見的功能,但是系統相機的Api目前發生了很大的變化,有Camera1、Camera2、CameraX三個api,每個api的使用和方法都不一樣,如果做過相機開發的小伙伴應該會很頭疼這三個api在不同安卓系統手機的適配,由于目前的App有一部分工作涉及到這部分,所以總結了一下,目前由基礎到深入慢慢總結.

一.簡介:(官方介紹如下)

CameraX 是一個 Jetpack 支持庫,旨在幫助您簡化相機應用的開發工作。它提供一致且易用的 API 接口,適用于大多數 Android 設備,并可向后兼容至 Android 5.0(API 級別 21)。

具體內容可以參考官網介紹,網站地址為:

CameraX 概覽 ?|? Android 開發者 ?|? Android Developers

二.優勢:(參考官網)

易用性

圖 1.CameraX 以 Android 5.0(API 級別 21)及更高版本為目標平臺,涵蓋了大多數 Android 設備

CameraX 引入了多個用例,使您可以專注于需要完成的任務,而無需花時間處理不同設備之間的細微差別。一些基本用例如下所示:

預覽:在屏幕上顯示圖像

圖像分析:無縫訪問緩沖區中的圖像以便在算法中使用,例如將其傳入 MLKit

圖片拍攝:保存優質圖片

這些用例適用于搭載 Android 5.0(API 級別 21)或更高版本的所有設備,從而確保了同樣的代碼適用于市場中的大多數設備。

?三.實戰代碼如下:

1.項目引入CameraX的依賴如下:

在項目的build.gradle導入如下配置:

// CameraX 核心庫使用 camera2 實現

implementation "androidx.camera:camera-camera2:1.0.0-beta07"

// 可以使用CameraView

implementation "androidx.camera:camera-view:1.0.0-alpha14"

// 可以使用供應商擴展

implementation "androidx.camera:camera-extensions:1.0.0-alpha14"

//camerax的生命周期庫

implementation "androidx.camera:camera-lifecycle:1.0.0-beta07"

2.項目的Application:

/**

* @auth: njb

* @date: 2021/10/20 16:19

* @desc: 描述

*/

public class MyApp extends Application {

? ? public? static MyApp app = null;

? ? @Override

? ? public void onCreate() {

? ? ? ? super.onCreate();

? ? ? ? app = this;

? ? }

? ? public static MyApp getInstance(){

? ? ? ? return app;

? ? }

}

3.MainActivity代碼如下:

項目的主要3個功能方法:

3.1、拍照方法:startCamera()?

? ? /**

? ? * 開始拍照

? ? */

? ? private fun startCamera() {

? ? ? ? cameraExecutor = Executors.newSingleThreadExecutor()

? ? ? ? val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

? ? ? ? cameraProviderFuture.addListener(Runnable {

? ? ? ? ? ? cameraProvider = cameraProviderFuture.get()//獲取相機信息

? ? ? ? ? ? //預覽配置

? ? ? ? ? ? preview = Preview.Builder()

? ? ? ? ? ? ? ? .build()

? ? ? ? ? ? ? ? .also {

? ? ? ? ? ? ? ? ? ? it.setSurfaceProvider(viewFinder.createSurfaceProvider())

? ? ? ? ? ? ? ? }

? ? ? ? ? ? imageCamera = ImageCapture.Builder()

? ? ? ? ? ? ? ? .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)

? ? ? ? ? ? ? ? .build()

? ? ? ? ? ? videoCapture = VideoCapture.Builder()//錄像用例配置

//? ? ? ? ? ? ? ? .setTargetAspectRatio(AspectRatio.RATIO_16_9) //設置高寬比

//? ? ? ? ? ? ? ? .setTargetRotation(viewFinder.display.rotation)//設置旋轉角度

//? ? ? ? ? ? ? ? .setAudioRecordSource(AudioSource.MIC)//設置音頻源麥克風

? ? ? ? ? ? ? ? .build()

? ? ? ? ? ? try {

? ? ? ? ? ? ? ? cameraProvider?.unbindAll()//先解綁所有用例

? ? ? ? ? ? ? ? camera = cameraProvider?.bindToLifecycle(

? ? ? ? ? ? ? ? ? ? this,

? ? ? ? ? ? ? ? ? ? cameraSelector,

? ? ? ? ? ? ? ? ? ? preview,

? ? ? ? ? ? ? ? ? ? imageCamera,

? ? ? ? ? ? ? ? ? ? videoCapture

? ? ? ? ? ? ? ? )//綁定用例

? ? ? ? ? ? } catch (exc: Exception) {

? ? ? ? ? ? ? ? Log.e(TAG, "Use case binding failed", exc)

? ? ? ? ? ? }

? ? ? ? }, ContextCompat.getMainExecutor(this))

? ? }

3.2、錄像方法:takeVideo()

/**

* 開始錄像

*/

@SuppressLint("RestrictedApi", "ClickableViewAccessibility")

private fun takeVideo() {

? ? val mDateFormat = SimpleDateFormat(FILENAME_FORMAT, Locale.US)

? ? //視頻保存路徑

? ? val file = File(FileUtils.getVideoName(), mDateFormat.format(Date()) + ".mp4")

? ? //開始錄像

? ? videoCapture?.startRecording(

? ? ? ? file,

? ? ? ? Executors.newSingleThreadExecutor(),

? ? ? ? object : OnVideoSavedCallback {

? ? ? ? ? ? override fun onVideoSaved(@NonNull file: File) {

? ? ? ? ? ? ? ? //保存視頻成功回調,會在停止錄制時被調用

? ? ? ? ? ? ? ? ToastUtils.shortToast(" 錄像成功 $file.absolutePath")

? ? ? ? ? ? }

? ? ? ? ? ? override fun onError(videoCaptureError: Int, message: String, cause: Throwable?) {

? ? ? ? ? ? ? ? //保存失敗的回調,可能在開始或結束錄制時被調用

? ? ? ? ? ? ? ? Log.e("", "onError: $message")

? ? ? ? ? ? ? ? ToastUtils.shortToast(" 錄像失敗 $message")

? ? ? ? ? ? }

? ? ? ? })

? ? btnVideo.setOnClickListener {

? ? ? ? videoCapture?.stopRecording()//停止錄制

? ? ? ? //preview?.clear()//清除預覽

? ? ? ? btnVideo.text = "Start Video"

? ? ? ? btnVideo.setOnClickListener {

? ? ? ? ? ? btnVideo.text = "Stop Video"

? ? ? ? ? ? takeVideo()

? ? ? ? }

? ? ? ? Log.d("path", file.path)

? ? }

}

3.3、切換前后置攝像頭方法:

3.4、完整代碼如下:

package com.example.cameraxapp

import android.Manifest

import android.annotation.SuppressLint

import android.content.pm.PackageManager

import android.net.Uri

import android.os.Bundle

import android.util.Log

import android.widget.Toast

import androidx.annotation.NonNull

import androidx.appcompat.app.AppCompatActivity

import androidx.camera.core.*

import androidx.camera.core.VideoCapture.OnVideoSavedCallback

import androidx.camera.lifecycle.ProcessCameraProvider

import androidx.core.app.ActivityCompat

import androidx.core.content.ContextCompat

import com.example.cameraxapp.utils.FileUtils

import com.example.cameraxapp.utils.ToastUtils

import kotlinx.android.synthetic.main.activity_main.*

import java.io.File

import java.text.SimpleDateFormat

import java.util.*

import java.util.concurrent.ExecutorService

import java.util.concurrent.Executors

class MainActivity : AppCompatActivity() {

? ? private var imageCamera: ImageCapture? = null

? ? private lateinit var cameraExecutor: ExecutorService

? ? var videoCapture: VideoCapture? = null//錄像用例

? ? var cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA//當前相機

? ? var preview: Preview? = null//預覽對象

? ? var cameraProvider: ProcessCameraProvider? = null//相機信息

? ? var camera: Camera? = null//相機對象

? ? override fun onCreate(savedInstanceState: Bundle?) {

? ? ? ? super.onCreate(savedInstanceState)

? ? ? ? setContentView(R.layout.activity_main)

? ? ? ? initPermission()

? ? }

? ? private fun initPermission() {

? ? ? ? if (allPermissionsGranted()) {

? ? ? ? ? ? // ImageCapture

? ? ? ? ? ? startCamera()

? ? ? ? } else {

? ? ? ? ? ? ActivityCompat.requestPermissions(

? ? ? ? ? ? ? ? this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS

? ? ? ? ? ? )

? ? ? ? }

? ? ? ? btnCameraCapture.setOnClickListener {

? ? ? ? ? ? takePhoto()

? ? ? ? }

? ? ? ? btnVideo.setOnClickListener {

? ? ? ? ? ? btnVideo.text = "Stop Video"

? ? ? ? ? ? takeVideo()

? ? ? ? }

? ? ? ? btnSwitch.setOnClickListener {

? ? ? ? ? ? cameraSelector = if (cameraSelector == CameraSelector.DEFAULT_BACK_CAMERA) {

? ? ? ? ? ? ? ? CameraSelector.DEFAULT_FRONT_CAMERA

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? CameraSelector.DEFAULT_BACK_CAMERA

? ? ? ? ? ? }

? ? ? ? ? ? startCamera()

? ? ? ? }

? ? }

? ? private fun takePhoto() {

? ? ? ? val imageCapture = imageCamera ?: return

? ? ? ? val mDateFormat = SimpleDateFormat("yyyyMMddHHmmss", Locale.US)

? ? ? ? val file =

? ? ? ? ? ? File(FileUtils.getImageFileName(), mDateFormat.format(Date()).toString() + ".jpg")

? ? ? ? val outputOptions = ImageCapture.OutputFileOptions.Builder(file).build()

? ? ? ? imageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(this),

? ? ? ? ? ? object : ImageCapture.OnImageSavedCallback {

? ? ? ? ? ? ? ? override fun onError(exc: ImageCaptureException) {

? ? ? ? ? ? ? ? ? ? Log.e(TAG, "Photo capture failed: ${exc.message}", exc)

? ? ? ? ? ? ? ? ? ? ToastUtils.shortToast(" 拍照失敗 ${exc.message}")

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? override fun onImageSaved(output: ImageCapture.OutputFileResults) {

? ? ? ? ? ? ? ? ? ? val savedUri = Uri.fromFile(file)

? ? ? ? ? ? ? ? ? ? val msg = "Photo capture succeeded: $savedUri"

? ? ? ? ? ? ? ? ? ? ToastUtils.shortToast(" 拍照成功 $savedUri")

? ? ? ? ? ? ? ? ? ? Log.d(TAG, msg)

? ? ? ? ? ? ? ? }

? ? ? ? ? ? })

? ? }

? ? /**

? ? * 開始錄像

? ? */

? ? @SuppressLint("RestrictedApi", "ClickableViewAccessibility")

? ? private fun takeVideo() {

? ? ? ? val mDateFormat = SimpleDateFormat(FILENAME_FORMAT, Locale.US)

? ? ? ? //視頻保存路徑

? ? ? ? val file = File(FileUtils.getVideoName(), mDateFormat.format(Date()) + ".mp4")

? ? ? ? //開始錄像

? ? ? ? videoCapture?.startRecording(

? ? ? ? ? ? file,

? ? ? ? ? ? Executors.newSingleThreadExecutor(),

? ? ? ? ? ? object : OnVideoSavedCallback {

? ? ? ? ? ? ? ? override fun onVideoSaved(@NonNull file: File) {

? ? ? ? ? ? ? ? ? ? //保存視頻成功回調,會在停止錄制時被調用

? ? ? ? ? ? ? ? ? ? ToastUtils.shortToast(" 錄像成功 $file.absolutePath")

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? override fun onError(videoCaptureError: Int, message: String, cause: Throwable?) {

? ? ? ? ? ? ? ? ? ? //保存失敗的回調,可能在開始或結束錄制時被調用

? ? ? ? ? ? ? ? ? ? Log.e("", "onError: $message")

? ? ? ? ? ? ? ? ? ? ToastUtils.shortToast(" 錄像失敗 $message")

? ? ? ? ? ? ? ? }

? ? ? ? ? ? })

? ? ? ? btnVideo.setOnClickListener {

? ? ? ? ? ? videoCapture?.stopRecording()//停止錄制

? ? ? ? ? ? //preview?.clear()//清除預覽

? ? ? ? ? ? btnVideo.text = "Start Video"

? ? ? ? ? ? btnVideo.setOnClickListener {

? ? ? ? ? ? ? ? btnVideo.text = "Stop Video"

? ? ? ? ? ? ? ? takeVideo()

? ? ? ? ? ? }

? ? ? ? ? ? Log.d("path", file.path)

? ? ? ? }

? ? }

? ? /**

? ? * 開始拍照

? ? */

? ? private fun startCamera() {

? ? ? ? cameraExecutor = Executors.newSingleThreadExecutor()

? ? ? ? val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

? ? ? ? cameraProviderFuture.addListener(Runnable {

? ? ? ? ? ? cameraProvider = cameraProviderFuture.get()//獲取相機信息

? ? ? ? ? ? //預覽配置

? ? ? ? ? ? preview = Preview.Builder()

? ? ? ? ? ? ? ? .build()

? ? ? ? ? ? ? ? .also {

? ? ? ? ? ? ? ? ? ? it.setSurfaceProvider(viewFinder.createSurfaceProvider())

? ? ? ? ? ? ? ? }

? ? ? ? ? ? imageCamera = ImageCapture.Builder()

? ? ? ? ? ? ? ? .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)

? ? ? ? ? ? ? ? .build()

? ? ? ? ? ? videoCapture = VideoCapture.Builder()//錄像用例配置

//? ? ? ? ? ? ? ? .setTargetAspectRatio(AspectRatio.RATIO_16_9) //設置高寬比

//? ? ? ? ? ? ? ? .setTargetRotation(viewFinder.display.rotation)//設置旋轉角度

//? ? ? ? ? ? ? ? .setAudioRecordSource(AudioSource.MIC)//設置音頻源麥克風

? ? ? ? ? ? ? ? .build()

? ? ? ? ? ? try {

? ? ? ? ? ? ? ? cameraProvider?.unbindAll()//先解綁所有用例

? ? ? ? ? ? ? ? camera = cameraProvider?.bindToLifecycle(

? ? ? ? ? ? ? ? ? ? this,

? ? ? ? ? ? ? ? ? ? cameraSelector,

? ? ? ? ? ? ? ? ? ? preview,

? ? ? ? ? ? ? ? ? ? imageCamera,

? ? ? ? ? ? ? ? ? ? videoCapture

? ? ? ? ? ? ? ? )//綁定用例

? ? ? ? ? ? } catch (exc: Exception) {

? ? ? ? ? ? ? ? Log.e(TAG, "Use case binding failed", exc)

? ? ? ? ? ? }

? ? ? ? }, ContextCompat.getMainExecutor(this))

? ? }

? ? override fun onRequestPermissionsResult(

? ? ? ? requestCode: Int, permissions: Array<String>, grantResults:

? ? ? ? IntArray

? ? ) {

? ? ? ? if (requestCode == REQUEST_CODE_PERMISSIONS) {

? ? ? ? ? ? if (allPermissionsGranted()) {

? ? ? ? ? ? ? ? startCamera()

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? Toast.makeText(

? ? ? ? ? ? ? ? ? ? this,

? ? ? ? ? ? ? ? ? ? "Permissions not granted by the user.",

? ? ? ? ? ? ? ? ? ? Toast.LENGTH_SHORT

? ? ? ? ? ? ? ? ).show()

? ? ? ? ? ? ? ? finish()

? ? ? ? ? ? }

? ? ? ? }

? ? }

? ? private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {

? ? ? ? ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED

? ? }

? ? override fun onDestroy() {

? ? ? ? super.onDestroy()

? ? ? ? cameraExecutor.shutdown()

? ? }

? ? companion object {

? ? ? ? private const val TAG = "CameraXBasic"

? ? ? ? private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"

? ? ? ? private const val REQUEST_CODE_PERMISSIONS = 10

? ? ? ? private val REQUIRED_PERMISSIONS = arrayOf(

? ? ? ? ? ? Manifest.permission.CAMERA,

? ? ? ? ? ? Manifest.permission.WRITE_EXTERNAL_STORAGE,

? ? ? ? ? ? Manifest.permission.READ_EXTERNAL_STORAGE,

? ? ? ? ? ? Manifest.permission.RECORD_AUDIO

? ? ? ? )

? ? }

}

4.項目封裝的文件工具類:

/**

* @auth: njb

* @date: 2021/10/20 17:47

* @desc: 文件工具類

*/

object FileUtils {

? ? /**

? ? * 獲取視頻文件路徑

? ? */

? ? fun getVideoName(): String {

? ? ? ? val videoPath = Environment.getExternalStorageDirectory().toString() + "/CameraX"

? ? ? ? val dir = File(videoPath)

? ? ? ? if (!dir.exists() && !dir.mkdirs()) {

? ? ? ? ? ? ToastUtils.shortToast("Trip")

? ? ? ? }

? ? ? ? return videoPath

? ? }

? ? /**

? ? * 獲取圖片文件路徑

? ? */

? ? fun getImageFileName(): String {

? ? ? ? val imagePath = Environment.getExternalStorageDirectory().toString() + "/images"

? ? ? ? val dir = File(imagePath)

? ? ? ? if (!dir.exists() && !dir.mkdirs()) {

? ? ? ? ? ? ToastUtils.shortToast("Trip")

? ? ? ? }

? ? ? ? return imagePath

? ? }

}

5.項目的ToastUtils工具類代碼:

package com.example.cameraxapp.utils;

import android.annotation.SuppressLint;

import android.app.Activity;

import android.content.Context;

import android.os.Handler;

import android.os.Looper;

import android.os.Message;

import android.util.Log;

import android.view.Gravity;

import android.widget.Toast;

import androidx.annotation.NonNull;

import androidx.annotation.StringRes;

import com.example.cameraxapp.app.MyApp;

import org.jetbrains.annotations.NotNull;

import java.lang.reflect.Field;

/**

* toast工具類

*/

public final class ToastUtils {

? ? private static final String TAG = "ToastUtil";

? ? private static Toast mToast;

? ? private static Field sField_TN;

? ? private static Field sField_TN_Handler;

? ? private static boolean sIsHookFieldInit = false;

? ? private static final String FIELD_NAME_TN = "mTN";

? ? private static final String FIELD_NAME_HANDLER = "mHandler";

? ? private static void showToast(final Context context, final CharSequence text,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? final int duration, final boolean isShowCenterFlag) {

? ? ? ? ToastRunnable toastRunnable = new ToastRunnable(context, text, duration, isShowCenterFlag);

? ? ? ? if (context instanceof Activity) {

? ? ? ? ? ? final Activity activity = (Activity) context;

? ? ? ? ? ? if (!activity.isFinishing()) {

? ? ? ? ? ? ? ? activity.runOnUiThread(toastRunnable);

? ? ? ? ? ? }

? ? ? ? } else {

? ? ? ? ? ? Handler handler = new Handler(context.getMainLooper());

? ? ? ? ? ? handler.post(toastRunnable);

? ? ? ? }

? ? }

? ? public static void shortToast(Context context, CharSequence text) {

? ? ? ? showToast(context, text, Toast.LENGTH_SHORT, false);

? ? }

? ? public static void longToast(Context context, CharSequence text) {

? ? ? ? showToast(context, text, Toast.LENGTH_LONG, false);

? ? }

? ? public static void shortToast(String msg) {

? ? ? ? showToast(MyApp.getInstance(), msg, Toast.LENGTH_SHORT, false);

? ? }

? ? public static void shortToast(@StringRes int resId) {

? ? ? ? showToast(MyApp.getInstance(), MyApp.getInstance().getText(resId),

? ? ? ? ? ? ? ? Toast.LENGTH_SHORT, false);

? ? }

? ? public static void centerShortToast(@NonNull String msg) {

? ? ? ? showToast(MyApp.getInstance(), msg, Toast.LENGTH_SHORT, true);

? ? }

? ? public static void centerShortToast(@StringRes int resId) {

? ? ? ? showToast(MyApp.getInstance(), MyApp.getInstance().getText(resId),

? ? ? ? ? ? ? ? Toast.LENGTH_SHORT, true);

? ? }

? ? public static void cancelToast() {

? ? ? ? Looper looper = Looper.getMainLooper();

? ? ? ? if (looper.getThread() == Thread.currentThread()) {

? ? ? ? ? ? mToast.cancel();

? ? ? ? } else {

? ? ? ? ? ? new Handler(looper).post(() -> mToast.cancel());

? ? ? ? }

? ? }

? ? private static void hookToast(Toast toast) {

? ? ? ? try {

? ? ? ? ? ? if (!sIsHookFieldInit) {

? ? ? ? ? ? ? ? sField_TN = Toast.class.getDeclaredField(FIELD_NAME_TN);

? ? ? ? ? ? ? ? sField_TN.setAccessible(true);

? ? ? ? ? ? ? ? sField_TN_Handler = sField_TN.getType().getDeclaredField(FIELD_NAME_HANDLER);

? ? ? ? ? ? ? ? sField_TN_Handler.setAccessible(true);

? ? ? ? ? ? ? ? sIsHookFieldInit = true;

? ? ? ? ? ? }

? ? ? ? ? ? Object tn = sField_TN.get(toast);

? ? ? ? ? ? Handler originHandler = (Handler) sField_TN_Handler.get(tn);

? ? ? ? ? ? sField_TN_Handler.set(tn, new SafelyHandlerWrapper(originHandler));

? ? ? ? } catch (Exception e) {

? ? ? ? ? ? Log.e(TAG, "Hook toast exception=" + e);

? ? ? ? }

? ? }

? ? private static class ToastRunnable implements Runnable {

? ? ? ? private Context context;

? ? ? ? private CharSequence text;

? ? ? ? private int duration;

? ? ? ? private boolean isShowCenter;

? ? ? ? public ToastRunnable(Context context, CharSequence text, int duration, boolean isShowCenter) {

? ? ? ? ? ? this.context = context;

? ? ? ? ? ? this.text = text;

? ? ? ? ? ? this.duration = duration;

? ? ? ? ? ? this.isShowCenter = isShowCenter;

? ? ? ? }

? ? ? ? @Override

? ? ? ? @SuppressLint("ShowToast")

? ? ? ? public void run() {

? ? ? ? ? ? if (mToast == null) {

? ? ? ? ? ? ? ? mToast = Toast.makeText(context, text, duration);

? ? ? ? ? ? } else {

? ? ? ? ? ? ? ? mToast.setText(text);

? ? ? ? ? ? ? ? if (isShowCenter) {

? ? ? ? ? ? ? ? ? ? mToast.setGravity(Gravity.CENTER, 0, 0);

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? mToast.setDuration(duration);

? ? ? ? ? ? }

? ? ? ? ? ? hookToast(mToast);

? ? ? ? ? ? mToast.show();

? ? ? ? }

? ? }

? ? private static class SafelyHandlerWrapper extends Handler {

? ? ? ? private Handler originHandler;

? ? ? ? public SafelyHandlerWrapper(Handler originHandler) {

? ? ? ? ? ? this.originHandler = originHandler;

? ? ? ? }

? ? ? ? @Override

? ? ? ? public void dispatchMessage(@NotNull Message msg) {

? ? ? ? ? ? try {

? ? ? ? ? ? ? ? super.dispatchMessage(msg);

? ? ? ? ? ? } catch (Exception e) {

? ? ? ? ? ? ? ? Log.e(TAG, "Catch system toast exception:" + e);

? ? ? ? ? ? }

? ? ? ? }

? ? ? ? @Override

? ? ? ? public void handleMessage(@NotNull Message msg) {

? ? ? ? ? ? if (originHandler != null) {

? ? ? ? ? ? ? ? originHandler.handleMessage(msg);

? ? ? ? ? ? }

? ? ? ? }

? ? }

}

6.項目的Manifest代碼如下:

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

? ? xmlns:tools="http://schemas.android.com/tools"

? ? package="com.example.cameraxapp">

? ? <uses-feature android:name="android.hardware.camera.any" />

? ? <uses-permission android:name="android.permission.CAMERA"/>

? ? <!--存儲圖像或者視頻權限-->

? ? <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

? ? <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

? ? <!--錄制音頻權限-->

? ? <uses-permission android:name="android.permission.RECORD_AUDIO" />

? ? <application

? ? ? ? android:name=".app.MyApp"

? ? ? ? android:allowBackup="true"

? ? ? ? android:icon="@mipmap/ic_launcher"

? ? ? ? android:label="@string/app_name"

? ? ? ? android:roundIcon="@mipmap/ic_launcher_round"

? ? ? ? android:requestLegacyExternalStorage="true"

? ? ? ? android:supportsRtl="true"

? ? ? ? android:theme="@style/AppTheme">

? ? ? ? <activity android:name=".MainActivity">

? ? ? ? ? ? <intent-filter>

? ? ? ? ? ? ? ? <action android:name="android.intent.action.MAIN" />

? ? ? ? ? ? ? ? <category android:name="android.intent.category.LAUNCHER" />

? ? ? ? ? ? </intent-filter>

? ? ? ? </activity>

? ? ? ? <provider

? ? ? ? ? ? android:name="androidx.core.content.FileProvider"

? ? ? ? ? ? android:authorities="${applicationId}.fileprovider"

? ? ? ? ? ? android:exported="false"

? ? ? ? ? ? android:grantUriPermissions="true"

? ? ? ? ? ? tools:replace="android:authorities">

? ? ? ? ? ? <meta-data

? ? ? ? ? ? ? ? android:name="android.support.FILE_PROVIDER_PATHS"

? ? ? ? ? ? ? ? android:resource="@xml/file_paths" />

? ? ? ? </provider>

? ? </application>

</manifest>

7.運行效果如下圖:可以看到拍照、錄像,切換攝像頭都是正常的

四、遇到的問題如下:?

1.拍照成功但后臺打印日志圖片文件寫入失敗。

2.在Android 10及以上系統提示讀寫文件失敗。

3.錄像后屏幕黑屏,預覽失敗。

五、解決方法如下:

1.拍照成功,圖片文件寫入失敗,根據以前項目的經驗沒有配置FileProvider。

2.在項目的res目錄下配置file_paths

?file_paths代碼如下:

?3.在manifest配置FileProvider,代碼如下:

<provider

? ? android:name="androidx.core.content.FileProvider"

? ? android:authorities="${applicationId}.fileprovider"

? ? android:exported="false"

? ? android:grantUriPermissions="true"

? ? tools:replace="android:authorities">

? ? <meta-data

? ? ? ? android:name="android.support.FILE_PROVIDER_PATHS"

? ? ? ? android:resource="@xml/file_paths" />

</provider>

4.Android10讀寫文件權限適配如下:

在AndroidManifest的application中設置android:requestLegacyExternalStorage="true"。

5.解決錄像后屏幕黑屏,預覽失敗的方法:由于我在錄像成功后主動調用了清除預覽的方法,所以導致黑屏,預覽失敗,注銷此方法即可。

?6.以上就是今天的CameraXApi的使用,測試了小米、華為、三星、google、oppo、vivo等幾款主流機型,Android 9、Android 10的系統,后面有機型會適配Android 11,主邏輯全部使用的是kotlin,實現了預覽、拍照、錄像、切換前后置攝像頭等功能,當然本文沒有仔細展開講解和Camera1、Camera2的區別,因為這塊內容很多,所以后面有時間整理一下,本文還有很多不足之處,望大家諒解,有問題及時提出,共同學習進步。

最后,項目的源碼如下:

CameraXApp: Android CameraX相機Api的使用實例

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容