CameraX architecture

CameraX is an addition to Jetpack that makes it easier to leverage the capabilities of Camera2 APIs. This topic covers the architecture of CameraX, including its structure, how to work with the API, how to work with Lifecycles, and how to combine use cases.

CameraX structure

Developers use CameraX to interface with a device’s camera through an abstraction called a use case. The following use cases are currently available:

  • Preview: prepares a preview SurfaceTexture
  • Image analysis: provides CPU-accessible buffers for analysis, such as for machine learning
  • Image capture: captures and saves a photo

Use cases can be combined and active concurrently. For example, an app can let the user view the image that the camera sees using a preview use case, have an image analysis use case that determines whether the people in the photo are smiling, and include an image capture use case to take a picture once they are.

API model

To work with the library, you specify the following things:

  • The desired use case with configuration options
  • What to do with output data by attaching listeners
  • The intended flow, such as when to enable cameras and when to produce data, by binding the use case to Android Architecture Lifecycles

The configuration objects for use cases are configured using set() methods and finalized with the build() method. Each use case object provides a set of use case-specific APIs. For example, the image capture use case provides a takePicture() method call.

Instead of an application placing specific start and stop method calls in onResume() and onPause(), the application specifies a lifecycle to associate the camera with, using CameraX.bindToLifecycle(). The startup, shutdown, and production of data are governed by the specified Android Architecture Lifecycle. That lifecycle then informs CameraX when to configure the camera capture session and ensures camera state changes appropriately to match lifecycle transitions.

For implementation steps for each use case, see Implement preview, Analyze images, and Take a photo.

API model example

The preview use case provides a SurfaceTexture for display. Applications create the use case with configuration options using the following code:

Kotlin

val previewConfig = PreviewConfig.Builder().build()
val preview = Preview(previewConfig)

val textureView: TextureView = findViewById(R.id.textureView)

// The output data-handling is configured in a listener.
preview.setOnPreviewOutputUpdateListener { previewOutput ->
    textureView.surfaceTexture = previewOutput.surfaceTexture
}

// The use case is bound to an Android Lifecycle with the following code.
CameraX.bindToLifecycle(this as LifecycleOwner, preview)

Java

PreviewConfig config = new PreviewConfig.Builder().build();
Preview preview = new Preview(config);

TextureView textureView = findViewById(R.id.textureView);

preview.setOnPreviewOutputUpdateListener(
    new Preview.OnPreviewOutputUpdateListener() {
        @Override
        public void onUpdated(Preview.PreviewOutput previewOutput) {
            // The output data-handling is configured in a listener.
            textureView.setSurfaceTexture(previewOutput.getSurfaceTexture());
            // Your custom code here.
        });
});

// The use case is bound to an Android Lifecycle with the following code.
CameraX.bindToLifecycle((LifecycleOwner) this, preview);

For more example code, see the official CameraX sample app.

CameraX Lifecycles

CameraX observes a lifecycle to determine when to open the camera, when to create a capture session, and when to stop and shut down. Use case APIs provide method calls and callbacks to monitor progress.

As explained in Combine use cases, you can bind some mixes of use cases to a single lifecycle. When your app needs to support use cases that cannot be combined, you can do one of the following:

  • Group compatible use cases together into more than one fragment and then switch between fragments
  • Create a custom lifecycle component and use it to manually control the camera lifecycle

If you decouple your view and camera use cases' Lifecycle owners (for example, if you use a custom lifecycle or a retain fragment), then you must ensure that all use cases are unbound from CameraX by using CameraX.unbindAll() or by unbinding each use case individually. Alternatively, when you bind use cases in the onCreate method for a standard Lifecycle, you can let CameraX manage opening and closing the capture session and unbinding the use cases.

If all of your camera functionality corresponds to the lifecycle of a single lifecycle-aware component such as an AppCompatActivity or an AppCompat fragment, then using the lifecycle of that component when binding all the desired use cases will ensure that the camera functionality is ready when the lifecycle-aware component is active, and safely disposed of, not consuming any resources, otherwise.

Custom LifecycleOwners

For advanced cases, you can create a custom LifecycleOwner to enable your app to explicitly control the CameraX session lifecycle instead of tying it to a standard Android LifecycleOwner.

The following code sample shows how to create a simple custom 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 mLifecycleRegistry;
    public CustomLifecycle() {
        mLifecycleRegistry = new LifecycleRegistry(this);
        mLifecycleRegistry.markState(Lifecycle.State.CREATED);
    }
   ...
   public void doOnResume() {
        mLifecycleRegistry.markState(State.RESUMED);
    }
   ...
    public Lifecycle getLifecycle() {
        return mLifecycleRegistry;
    }
}

Using this LifecycleOwner, your app can place state transitions at desired points in its code. For more on implementing this functionality in your app, see Implementing a custom LifecycleOwner.

Combine use cases

Use cases can run concurrently. While use cases can be sequentially bound to a lifecycle, it is better to bind all use cases with a single call to CameraX.bindToLifecycle(). For more information on best practices for configuration changes, see Handle configuration changes.

In the following code sample, the app specifies the two use cases to be created and run simultaneously. It also specifies the lifecycle to use for both use cases, so that they both start and stop according to the lifecycle.

Kotlin

val imageCapture: ImageCapture

override fun onCreate() {
    val previewConfig = PreviewConfig.Builder().build()
    val imageCaptureConfig = ImageCaptureConfiguration.Builder().build()

    val imageCapture = ImageCapture(imageCaptureConfig)
    val preview = Preview(previewConfig)

    val textureView = findViewById(R.id.textureView)

    preview.setOnPreviewOutputUpdateListener { previewOutput ->
        textureView.surfaceTexture = previewOutput.surfaceTexture
    }

    CameraX.bindToLifecycle(this as LifecycleOwner, preview, imageCapture)
}

Java

private ImageCapture imageCapture;

void onCreate() {
    PreviewConfig previewConfig = new PreviewConfig.Builder().build();
    imageCaptureConfig imageCaptureConfig =
        new ImageCaptureConfig.Builder().build();

    imageCapture = new ImageCapture(imageCaptureConfig);
    preview = new Preview(previewConfig);

    TextureView textureView = findViewById(R.id.textureView);

    preview.setOnPreviewOutputUpdateListener(
        previewOutput -> {
            textureView.setSurfaceTexture(previewOutput.getSurfaceTexture());
    });

    CameraX.bindToLifecycle((LifecycleOwner) this, preview, imageCapture);
}

The following configuration combinations are supported:

Preview Analysis Image capture Combining use cases
Provide user a preview, take a photo, and analyze the image stream.
  Take a photo and analyze the image stream.
  Provide a preview with visual effects applied based on analysis of the images being streamed.
  Show what the camera sees and take a photo on user action.

Permissions

Your app will need the CAMERA permission. To save images to files, it will also require the WRITE_EXTERNAL_STORAGE permission, except on devices running Android Q or later.

For more information about configuring permissions for your app, read Request App Permissions.

Requirements

CameraX has the following minimum version requirements:

  • Android API level 21
  • Android Architecture Components 1.1.1

For lifecycle-aware activities, use FragmentActivity or AppCompatActivity.

Declare dependencies

To add a dependency on CameraX, you must add the Google Maven repository to your project.

Open the build.gradle file for your project and add the google() repository as shown in the following:

allprojects {
    repositories {
        google()
        jcenter()
    }
}

Add the following to the end of the Android block:

compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}

Add the following to each module’s build.gradle file:

dependencies {
    // CameraX core library.
    def camerax_version = "1.0.0-alpha05"
    implementation "androidx.camera:camera-core:${camerax_version}"
    // If you want to use Camera2 extensions.
    implementation "androidx.camera:camera-camera2:${camerax_version}"
}

For more information on configuring your app to conform to these requirements, see Declaring dependencies.

Additional resources

To learn more about CameraX, consult the following additional resources.

Codelab

  • Getting Started with CameraX
  • Code sample

  • Official CameraX sample app