CameraX 架构

本页介绍了 CameraX 的架构,包括其结构、如何与 API 搭配使用、如何与生命周期配合使用以及如何组合各种用例。

CameraX 结构

您可以使用 CameraX,借助名为“用例”的抽象概念与设备的相机进行交互。提供的用例如下:

  • 预览:接受用于显示预览的 Surface,例如 PreviewView
  • 图片分析:为分析(例如机器学习)提供 CPU 可访问的缓冲区。
  • 图片拍摄:拍摄并保存照片。
  • 视频拍摄:通过 VideoCapture 拍摄视频和音频

不同用例可以组合使用,也可以同时处于活跃状态。例如,应用中可以加入预览用例,以便让用户查看进入相机视野的画面;加入图片分析用例,以确定照片里的人物是否在微笑;还可以加入图片拍摄用例,以在人物微笑时拍摄照片。

API 模型

如需使用该库,请指定以下内容:

  • 具有配置选项的所需用例。
  • 通过附加监听器来指定如何处理输出数据。
  • 通过将用例绑定到 Android 架构生命周期来指定目标流程,例如何时启用相机及何时生成数据。

您可以通过 2 种方式编写 CameraX 应用:CameraController(如果您希望通过最简单的方式使用 CameraX,这非常适合)或 CameraProvider(如果需要更高的灵活性,这非常适合)。

CameraController

CameraController 在单个类中提供大多数 CameraX 核心功能。它只需少量设置代码,并且可自动处理相机初始化、用例管理、目标旋转、点按对焦、双指张合缩放等操作。扩展 CameraController 的具体类为 LifecycleCameraController

Kotlin

val previewView: PreviewView = viewBinding.previewView
var cameraController = LifecycleCameraController(baseContext)
cameraController.bindToLifecycle(this)
cameraController.cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
previewView.controller = cameraController

Java

PreviewView previewView = viewBinding.previewView;
LifecycleCameraController cameraController = new LifecycleCameraController(baseContext);
cameraController.bindToLifecycle(this);
cameraController.setCameraSelector(CameraSelector.DEFAULT_BACK_CAMERA);
previewView.setController(cameraController);

CameraController 的默认 UseCasePreviewImageCaptureImageAnalysis。如需关闭 ImageCaptureImageAnalysis,或者开启 VideoCapture,请使用 setEnabledUseCases() 方法。

如需了解 CameraController 的更多用法,请参阅二维码扫描器示例CameraController 基础知识视频

CameraProvider

CameraProvider 仍然易于使用,但由于应用开发者会处理更多设置,因此有更多机会自定义配置,例如在 ImageAnalysis 中启用输出图片旋转或设置输出图像格式。您还可以使用自定义 Surface 进行相机预览以提高灵活性,而对于 CameraController,您需要使用 PreviewView。如果现有的 Surface 代码已是应用的其他部分的输入,则使用该代码会非常有用。

您可以使用 set() 方法配置用例,并使用 build() 方法完成这些用例。每个用例对象都提供一组特定于该用例的 API。例如,图片拍摄用例会提供 takePicture() 方法调用。

应用没有在 onResume()onPause() 中放置具体的启动和停止方法调用,而是使用 cameraProvider.bindToLifecycle() 指定要与相机关联的生命周期。之后,该生命周期会告知 CameraX 何时配置相机拍摄会话并确保相机状态随生命周期的转换相应地变化。

如需了解每个用例的实现步骤,请参阅实现预览分析图片图片拍摄视频拍摄

预览用例会与 Surface 互动以展示预览。应用使用以下代码创建具有配置选项的用例:

Kotlin

val preview = Preview.Builder().build()
val viewFinder: PreviewView = findViewById(R.id.previewView)

// The use case is bound to an Android Lifecycle with the following code
val camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)

// PreviewView creates a surface provider and is the recommended provider
preview.setSurfaceProvider(viewFinder.getSurfaceProvider())

Java

Preview preview = new Preview.Builder().build();
PreviewView viewFinder = findViewById(R.id.view_finder);

// The use case is bound to an Android Lifecycle with the following code
Camera camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview);

// PreviewView creates a surface provider, using a Surface from a different
// kind of view will require you to implement your own surface provider.
preview.previewSurfaceProvider = viewFinder.getSurfaceProvider();

如需查看更多示例代码,请参阅官方 CameraX 示例应用

CameraX 生命周期

CameraX 会按照生命周期确定何时打开相机、何时创建拍摄会话以及何时停止和关闭。用例 API 提供方法调用和回调来监控进度情况。

组合用例中所述,您可以将多个用例一起绑定到单个生命周期。当您的应用需要支持无法组合的用例时,您可以执行以下操作之一:

  • 将兼容的用例划分到多个 Fragment 中,然后在 Fragment 之间进行切换
  • 创建自定义生命周期组件并使用该组件来手动控制相机生命周期

如果您将视图用例和相机用例的生命周期所有者分开(例如,如果您使用自定义生命周期或保留 Fragment),则必须通过使用 ProcessCameraProvider.unbindAll() 或分别取消绑定各个用例来确保所有用例均未绑定 CameraX。或者,当您将用例绑定到生命周期时,可以让 CameraX 管理拍摄会话的开启和关闭操作以及用例的取消绑定操作。

如果所有相机功能都对应于单个生命周期感知型组件(例如 AppCompatActivityAppCompat Fragment)的生命周期,那么在绑定全部所需用例时使用该组件的生命周期,可确保相机功能在该组件处于活动状态时准备就绪,并在该组件进入非活动状态时获得安全处置,而不会消耗任何资源。

自定义 LifecycleOwner

对于高级用例,您可以创建自定义 LifecycleOwner,以使您的应用显式控制 CameraX 会话生命周期,而不是将其绑定到标准 Android LifecycleOwner

以下代码示例展示了如何创建简单的自定义 LifecycleOwner:

Kotlin

class CustomLifecycle : LifecycleOwner {
    private val lifecycleRegistry: LifecycleRegistry

    init {
        lifecycleRegistry = LifecycleRegistry(this);
        lifecycleRegistry.markState(Lifecycle.State.CREATED)
    }
    ...
    fun doOnResume() {
        lifecycleRegistry.markState(State.RESUMED)
    }
    ...
    override fun getLifecycle(): Lifecycle {
        return lifecycleRegistry
    }
}

Java

public class CustomLifecycle implements LifecycleOwner {
    private LifecycleRegistry lifecycleRegistry;
    public CustomLifecycle() {
        lifecycleRegistry = new LifecycleRegistry(this);
        lifecycleRegistry.markState(Lifecycle.State.CREATED);
    }
   ...
   public void doOnResume() {
        lifecycleRegistry.markState(State.RESUMED);
    }
   ...
    public Lifecycle getLifecycle() {
        return lifecycleRegistry;
    }
}

借助此 LifecycleOwner,您的应用可以将状态转换放置在代码中所需的位置。如需详细了解如何在应用中实现此功能,请参阅实现自定义 LifecycleOwner

并发用例

多个用例可以同时运行。虽然可以将多个用例依序绑定到一个生命周期,但最好通过对 CameraProcessProvider.bindToLifecycle() 的一次调用来绑定所有用例。如需详细了解配置更改的最佳做法,请参阅处理配置更改

在以下代码示例中,应用指定了要创建并同时运行的两个用例,还指定了要同时用于这两个用例的生命周期,以便这两个用例都按照该生命周期启动和停止。

Kotlin

private lateinit var imageCapture: ImageCapture

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

    cameraProviderFuture.addListener(Runnable {
        // Camera provider is now guaranteed to be available
        val cameraProvider = cameraProviderFuture.get()

        // Set up the preview use case to display camera preview.
        val preview = Preview.Builder().build()

        // Set up the capture use case to allow users to take photos.
        imageCapture = ImageCapture.Builder()
                .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
                .build()

        // Choose the camera by requiring a lens facing
        val cameraSelector = CameraSelector.Builder()
                .requireLensFacing(CameraSelector.LENS_FACING_FRONT)
                .build()

        // Attach use cases to the camera with the same lifecycle owner
        val camera = cameraProvider.bindToLifecycle(
                this as LifecycleOwner, cameraSelector, preview, imageCapture)

        // Connect the preview use case to the previewView
        preview.setSurfaceProvider(
                previewView.getSurfaceProvider())
    }, ContextCompat.getMainExecutor(this))
}

Java

private ImageCapture imageCapture;

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    PreviewView previewView = findViewById(R.id.previewView);

    ListenableFuture<ProcessCameraProvider> cameraProviderFuture =
            ProcessCameraProvider.getInstance(this);

    cameraProviderFuture.addListener(() -> {
        try {
            // Camera provider is now guaranteed to be available
            ProcessCameraProvider cameraProvider = cameraProviderFuture.get();

            // Set up the view finder use case to display camera preview
            Preview preview = new Preview.Builder().build();

            // Set up the capture use case to allow users to take photos
            imageCapture = new ImageCapture.Builder()
                    .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
                    .build();

            // Choose the camera by requiring a lens facing
            CameraSelector cameraSelector = new CameraSelector.Builder()
                    .requireLensFacing(lensFacing)
                    .build();

            // Attach use cases to the camera with the same lifecycle owner
            Camera camera = cameraProvider.bindToLifecycle(
                    ((LifecycleOwner) this),
                    cameraSelector,
                    preview,
                    imageCapture);

            // Connect the preview use case to the previewView
            preview.setSurfaceProvider(
                    previewView.getSurfaceProvider());
        } catch (InterruptedException | ExecutionException e) {
            // Currently no exceptions thrown. cameraProviderFuture.get()
            // shouldn't block since the listener is being called, so no need to
            // handle InterruptedException.
        }
    }, ContextCompat.getMainExecutor(this));
}

CameraX 允许同时使用 PreviewVideoCaptureImageAnalysisImageCapture。此外,

  • 每个用例都可以单独使用。例如,应用可以在不使用预览的情况下录制视频。
  • 启用扩展后,只能保证能够使用 ImageCapturePreview 的组合。根据 OEM 实现情况,可能无法同时添加 ImageAnalysis;无法为 VideoCapture 用例启用扩展。如需了解详情,请参阅扩展参考文档
  • 对于某些相机而言,在较低分辨率模式下可以支持的组合,在较高的分辨率下将无法支持,这具体取决于相机的功能。
  • 在相机硬件级别为 FULL 或更低的设备上,组合使用 PreviewVideoCaptureImageCaptureImageAnalysis 可能会迫使 CameraX 为 PreviewVideoCapture 复制相机的 PRIV 数据流。这个 借助于所谓的信息流共享,用户可以同时使用 但代价是处理需求增加。您可能会 延迟时间会略长一些,而且电池续航时间也会缩短。

所支持的硬件级别可以从 Camera2CameraInfo 中检索。例如,以下代码可检查默认的后置摄像头是否是 LEVEL_3 设备:

Kotlin

@androidx.annotation.OptIn(ExperimentalCamera2Interop::class)
fun isBackCameraLevel3Device(cameraProvider: ProcessCameraProvider) : Boolean {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        return CameraSelector.DEFAULT_BACK_CAMERA
            .filter(cameraProvider.availableCameraInfos)
            .firstOrNull()
            ?.let { Camera2CameraInfo.from(it) }
            ?.getCameraCharacteristic(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL) ==
            CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3
    }
    return false
}

Java

@androidx.annotation.OptIn(markerClass = ExperimentalCamera2Interop.class)
Boolean isBackCameraLevel3Device(ProcessCameraProvider cameraProvider) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        List\<CameraInfo\> filteredCameraInfos = CameraSelector.DEFAULT_BACK_CAMERA
                .filter(cameraProvider.getAvailableCameraInfos());
        if (!filteredCameraInfos.isEmpty()) {
            return Objects.equals(
                Camera2CameraInfo.from(filteredCameraInfos.get(0)).getCameraCharacteristic(
                        CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL),
                CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3);
        }
    }
    return false;
}

权限

您的应用需要 CAMERA 权限。如需将图片保存到文件中,除非所用设备搭载 Android 10 或更高版本,否则应用还需要 WRITE_EXTERNAL_STORAGE 权限。

如需详细了解如何为应用配置权限,请参阅请求应用权限

要求

CameraX 具有以下最低版本要求:

  • Android API 级别 21
  • Android 架构组件 1.1.1

对于能够感知生命周期的 Activity,请使用 FragmentActivityAppCompatActivity

声明依赖项

要添加 CameraX 的依赖项,您必须将 Google Maven 代码库添加到项目中。

打开项目的 settings.gradle 文件并添加 google() 代码库,如下所示:

Groovy

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}

Kotlin

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}

将以下内容添加到 Android 代码块的末尾:

Groovy

android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    // For Kotlin projects
    kotlinOptions {
        jvmTarget = "1.8"
    }
}

Kotlin

android {
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    // For Kotlin projects
    kotlinOptions {
        jvmTarget = "1.8"
    }
}

将以下内容添加到应用的每个模块的 build.gradle 文件中:

Groovy

dependencies {
  // CameraX core library using the camera2 implementation
  def camerax_version = "1.5.0-alpha01"
  // The following line is optional, as the core library is included indirectly by camera-camera2
  implementation "androidx.camera:camera-core:${camerax_version}"
  implementation "androidx.camera:camera-camera2:${camerax_version}"
  // If you want to additionally use the CameraX Lifecycle library
  implementation "androidx.camera:camera-lifecycle:${camerax_version}"
  // If you want to additionally use the CameraX VideoCapture library
  implementation "androidx.camera:camera-video:${camerax_version}"
  // If you want to additionally use the CameraX View class
  implementation "androidx.camera:camera-view:${camerax_version}"
  // If you want to additionally add CameraX ML Kit Vision Integration
  implementation "androidx.camera:camera-mlkit-vision:${camerax_version}"
  // If you want to additionally use the CameraX Extensions library
  implementation "androidx.camera:camera-extensions:${camerax_version}"
}

Kotlin

dependencies {
    // CameraX core library using the camera2 implementation
    val camerax_version = "1.5.0-alpha01"
    // The following line is optional, as the core library is included indirectly by camera-camera2
    implementation("androidx.camera:camera-core:${camerax_version}")
    implementation("androidx.camera:camera-camera2:${camerax_version}")
    // If you want to additionally use the CameraX Lifecycle library
    implementation("androidx.camera:camera-lifecycle:${camerax_version}")
    // If you want to additionally use the CameraX VideoCapture library
    implementation("androidx.camera:camera-video:${camerax_version}")
    // If you want to additionally use the CameraX View class
    implementation("androidx.camera:camera-view:${camerax_version}")
    // If you want to additionally add CameraX ML Kit Vision Integration
    implementation("androidx.camera:camera-mlkit-vision:${camerax_version}")
    // If you want to additionally use the CameraX Extensions library
    implementation("androidx.camera:camera-extensions:${camerax_version}")
}

如需详细了解如何配置应用以满足上述要求,请参阅声明依赖项

CameraX 与 Camera2 的互操作性

CameraX 基于 Camera2 构建而成,并且 CameraX 提供了在 Camera2 实现中读取甚至写入属性的方式。如需了解完整详情,请参阅互操作性软件包

如需详细了解 CameraX 如何配置 Camera2 属性,请使用 Camera2CameraInfo 读取底层 CameraCharacteristics。您还可以选择采用以下两个途径之一来写入底层 Camera2 属性:

以下代码示例使用数据流用例来优化视频通话。使用 Camera2CameraInfo 可提取视频通话流用例是否可用。然后,使用 Camera2Interop.Extender 设置底层数据流用例。

Kotlin

// Set underlying Camera2 stream use case to optimize for video calls.

val videoCallStreamId =
    CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL.toLong()

// Check available CameraInfos to find the first one that supports
// the video call stream use case.
val frontCameraInfo = cameraProvider.getAvailableCameraInfos()
    .first { cameraInfo ->
        val isVideoCallStreamingSupported = Camera2CameraInfo.from(cameraInfo)
            .getCameraCharacteristic(
                CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES
            )?.contains(videoCallStreamId)
        val isFrontFacing = (cameraInfo.getLensFacing() == 
                             CameraSelector.LENS_FACING_FRONT)
        (isVideoCallStreamingSupported == true) && isFrontFacing
    }

val cameraSelector = frontCameraInfo.cameraSelector

// Start with a Preview Builder.
val previewBuilder = Preview.Builder()
    .setTargetAspectRatio(screenAspectRatio)
    .setTargetRotation(rotation)

// Use Camera2Interop.Extender to set the video call stream use case.
Camera2Interop.Extender(previewBuilder).setStreamUseCase(videoCallStreamId)

// Bind the Preview UseCase and the corresponding CameraSelector.
val preview = previewBuilder.build()
camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview)

Java

// Set underlying Camera2 stream use case to optimize for video calls.

Long videoCallStreamId =
    CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_CALL.toLong();

// Check available CameraInfos to find the first one that supports
// the video call stream use case.
List<CameraInfo> cameraInfos = cameraProvider.getAvailableCameraInfos();
CameraInfo frontCameraInfo = null;
for (cameraInfo in cameraInfos) {
    Long[] availableStreamUseCases = Camera2CameraInfo.from(cameraInfo)
        .getCameraCharacteristic(
            CameraCharacteristics.SCALER_AVAILABLE_STREAM_USE_CASES
        );
    boolean isVideoCallStreamingSupported = Arrays.List(availableStreamUseCases)
                .contains(videoCallStreamId);
    boolean isFrontFacing = (cameraInfo.getLensFacing() ==
                             CameraSelector.LENS_FACING_FRONT);

    if (isVideoCallStreamingSupported && isFrontFacing) {
        frontCameraInfo = cameraInfo;
    }
}

if (frontCameraInfo == null) {
    // Handle case where video call streaming is not supported.
}

CameraSelector cameraSelector = frontCameraInfo.getCameraSelector();

// Start with a Preview Builder.
Preview.Builder previewBuilder = Preview.Builder()
    .setTargetAspectRatio(screenAspectRatio)
    .setTargetRotation(rotation);

// Use Camera2Interop.Extender to set the video call stream use case.
Camera2Interop.Extender(previewBuilder).setStreamUseCase(videoCallStreamId);

// Bind the Preview UseCase and the corresponding CameraSelector.
Preview preview = previewBuilder.build()
Camera camera = cameraProvider.bindToLifecycle(this, cameraSelector, preview)

其他资源

要详细了解 CameraX,请参阅下面列出的其他资源。

Codelab

  • CameraX 使用入门
  • 代码示例

  • CameraX 示例应用