Arquitectura de CameraX

CameraX es un agregado de Jetpack que permite aprovechar fácilmente las funciones de las API de Camera2. En este tema se describe la arquitectura de CameraX, que incluye la estructura, cómo trabajar con la API y con ciclos de vida, y cómo combinar los casos de uso.

Estructura de CameraX

Los desarrolladores usan CameraX para interactuar con la cámara del dispositivo mediante una abstracción llamada caso de uso. Actualmente, se encuentran disponibles los siguientes casos de uso:

  • Vista previa: Acepta una plataforma para mostrar una vista previa, como un elemento PreviewView.
  • Análisis de imágenes: Proporciona búferes a los que se puede acceder desde la CPU para análisis, que sirven, por ejemplo, para aprendizaje automático.
  • Captura de imágenes: Captura y guarda una foto.

Los casos de uso pueden combinarse y estar activos en simultáneo. Por ejemplo, una app puede permitir que el usuario vea lo mismo que la cámara con un caso de uso de vista previa, tenga otro caso de análisis de imágenes para determinar si las personas que aparecen en la foto están sonriendo, y que incluya otro de captura de imágenes que tome una foto cuando efectivamente estén sonriendo.

Modelo de API

Para trabajar con la biblioteca, tienes que especificar lo siguiente:

  • El caso práctico deseado con sus opciones de configuración
  • Qué hacer con los datos resultantes al juntar objetos de escucha
  • Cuál es el flujo pretendido, como los momentos de habilitación de cámaras y de producción de datos, mediante la vinculación del caso de uso a los ciclos de vida de la arquitectura de Android.

Puedes configurar casos de uso con los métodos set() y finalizarlos con el método build(). Cada objeto de caso de uso proporciona un conjunto de API específicas de casos de uso. Por ejemplo, el caso de uso de la captura de imagen proporciona una llamada al método takePicture().

Una aplicación especificará un ciclo de vida con el que asociar la cámara mediante cameraProvider.bindToLifecycle(), en lugar de llamar métodos de inicio y finalización en onResume() y onPause(). El ciclo de vida informa a CameraX cuándo se debe configurar la sesión de captura de la cámara y se asegura de que el estado de la cámara cambie de manera adecuada para que coincida con sus transiciones.

Si quieres conocer los pasos de implementación para cada caso de uso, consulta Cómo implementar una vista previa, Cómo analizar imágenes y Cómo capturar imágenes.

Ejemplo de modelo de API

El caso de uso de la vista previa interactúa con un elemento Surface para mostrarla. Las aplicaciones crean el caso de uso con opciones de configuración que utilizan el siguiente código:

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();

Para ver más ejemplos de código, consulta la app de muestra de CameraX oficial.

Ciclos de vida de CameraX

CameraX obedece un ciclo de vida para determinar cuándo abrir la cámara, crear una sesión de captura, detenerse y apagarse. Las API de casos de uso proporcionan llamadas y devoluciones de llamada de métodos con el fin de supervisar el progreso.

Según se explica en Cómo combinar casos de uso, puedes vincular combinaciones de casos de uso a un solo ciclo de vida. Cuando tu app necesite admitir casos de uso que no se puedan combinar, podrás realizar una de las siguientes acciones:

  • Agrupar casos de uso compatibles en más de un fragmento y, luego, intercambiar fragmentos
  • Crear un componente del ciclo de vida personalizado y usarlo para controlar el ciclo de vida de la cámara de forma manual

En caso de desacoplar la vista y los LifecycleOwner de los casos de uso de la cámara (por ejemplo, si usas un ciclo de vida personalizado o un fragmento de retención), debes asegurarte de que todos los casos de uso estén desvinculados de CameraX. Para ello, utiliza ProcessCameraProvider.unbindAll() o desvincula cada caso de uso de forma individual. De manera alternativa, cuando vinculas casos de uso a un ciclo de vida, puedes permitir que CameraX administre la apertura y el cierre de la sesión de captura y la desvinculación de los casos de uso.

Si todas las funciones de tu cámara corresponden al ciclo de vida de un solo componente relacionado con los ciclos de vida, como un fragmento AppCompatActivity o AppCompat, el uso del ciclo de vida de ese componente cuando vincules todos los casos de uso deseados garantizará que las funciones de la cámara estén listas cuando el componente esté activo o que se descarten de manera segura y sin consumir recursos.

LifecycleOwner personalizado

Para casos avanzados, puedes crear un LifecycleOwner personalizado a fin de permitir que tu app controle explícitamente el ciclo de vida de la sesión de CameraX en lugar de vincularlo a un LifecycleOwner de Android estándar.

En la siguiente muestra de código, se indica cómo crear un LifecycleOwner simple y personalizado:

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;
    }
}

Con este LifecycleOwner, tu app puede colocar transiciones de estado en los puntos deseados de su código. Si quieres obtener más información para implementar esta función en tu app, consulta Cómo implementar un LifecycleOwner personalizado.

Casos de uso simultáneos

Los casos de uso pueden ejecutarse en simultáneo. Si bien pueden estar vinculados de manera secuencial a un ciclo de vida, se recomienda vincular todos los casos de uso con una sola llamada a CameraProcessProvider.bindToLifecycle(). Obtén más información sobre las prácticas recomendadas para los cambios de configuración en Cómo controlar los cambios en la configuración.

En la siguiente muestra de código, la app especifica los dos casos de uso que se crearán y ejecutarán de manera simultánea. También especifica el ciclo de vida que se usará para ambos casos de uso, a fin de que ambos se inicien y se detengan según el ciclo de vida.

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));
}

Se admiten las siguientes combinaciones de configuración:

Vista previa Análisis Captura de imágenes Combinación de casos prácticos
Proporciona una vista previa al usuario, permite tomar una foto y analiza la transmisión de imágenes.
  Toma una foto y analiza la transmisión de imágenes.
  Proporciona una vista previa con efectos visuales aplicados según el análisis de las imágenes que se transmiten.
  Muestra la vista de la cámara y toma una foto con la acción del usuario.

Cuando se habilitan las extensiones, solo están garantizadas ImageCapture y Preview. En función de la implementación del OEM, es posible que tampoco se pueda usar ImageAnalysis.

ImageCapture no funciona por sí solo, aunque Preview y ImageAnalysis sí lo hacen.

Permisos

La app necesita el permiso CAMERA. Para guardar imágenes en archivos, también necesitarás el permiso WRITE_EXTERNAL_STORAGE, excepto en dispositivos que ejecuten Android 10 o versiones posteriores.

Si quieres obtener más información para configurar permisos en tu app, consulta Cómo solicitar permisos de app.

Requisitos

CameraX tiene los siguientes requisitos mínimos de versión:

  • Nivel 21 de la API de Android
  • Componentes de la arquitectura de Android 1.1.1

Para actividades relacionadas con el ciclo de vida, usa FragmentActivity o AppCompatActivity.

Cómo declarar dependencias

Para agregar una dependencia en CameraX, debes agregar el repositorio de Maven de Google a tu proyecto.

Abre el archivo build.gradle de tu proyecto y agrega el repositorio google() como se muestra a continuación:

Groovy

allprojects {
    repositories {
        google()
        mavenCentral()
    }
}

Kotlin

allprojects {
    repositories {
        google()
        mavenCentral()
    }
}

Agrega lo siguiente al final del bloque de 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"
    }
}

Agrega lo siguiente al archivo build.gradle de cada módulo para una app:

Groovy

dependencies {
  // CameraX core library using the camera2 implementation
  def camerax_version = "1.0.2"
  // 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 View class
  implementation "androidx.camera:camera-view:1.0.0-alpha31"
  // If you want to additionally use the CameraX Extensions library
  implementation "androidx.camera:camera-extensions:1.0.0-alpha31"
}

Kotlin

dependencies {
    // CameraX core library using the camera2 implementation
    val camerax_version = "1.0.2"
    // 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 View class
    implementation("androidx.camera:camera-view:1.0.0-alpha31")
    // If you want to additionally use the CameraX Extensions library
    implementation("androidx.camera:camera-extensions:1.0.0-alpha31")
}

Si quieres obtener más información para configurar tu app a fin de cumplir con estos requisitos, consulta Cómo declarar dependencias.

Recursos adicionales

Para obtener más información acerca de CameraX, consulta los siguientes recursos adicionales.

Codelab

  • Comienza a usar CameraX
  • Muestra de código

  • App de muestra de CameraX oficial