Arquitetura do CameraX

O CameraX é uma adição ao Jetpack que facilita a utilização dos recursos das APIs Camera2. Este tópico aborda a arquitetura do CameraX, incluindo a estrutura, como trabalhar com a API e com Lifecycles e como combinar casos de uso.

Estrutura do CameraX

Os desenvolvedores usam o CameraX para interagir com a câmera de um dispositivo por meio de uma abstração chamada de caso de uso. No momento, estes são os casos de uso disponíveis:

  • Visualização: aceita uma superfície para exibir uma visualização, como uma PreviewView.
  • Análise de imagem: fornece buffers acessíveis por CPU para análise, como para aprendizado de máquina.
  • Captura de imagem: captura e salva uma foto.

Os casos de uso podem ser combinados e ativados simultaneamente. Por exemplo, um app pode permitir que o usuário visualize a imagem que a câmera vê usando um caso de uso de visualização, ter um caso de uso de análise de imagem que determina se as pessoas na foto estão sorrindo e incluir um caso de uso de captura de imagem para tirar uma foto quando elas sorrirem.

Modelo de API

Para trabalhar com a biblioteca, especifique o seguinte:

  • O caso de uso desejado com opções de configuração.
  • O que fazer com os dados de saída ao anexar listeners.
  • O fluxo pretendido, por exemplo, quando ativar câmeras e produzir dados ao vincular o caso de uso ao Android Architecture Lifecycles.

Os casos de uso são configurados com métodos set() e finalizados com o método build(). Cada objeto de caso de uso fornece um conjunto de APIs específicas ao casos de uso. Por exemplo, o caso de uso de captura de imagem fornece uma chamada de método takePicture().

Em vez de um aplicativo posicionar chamadas específicas de início e parada em onResume() e onPause(), o aplicativo especifica um ciclo de vida ao qual a câmera será associada, usando cameraProvider.bindToLifecycle(). O ciclo de vida informa a CameraX quando configurar a sessão de captura de câmera, garantindo que o estado da câmera mude de maneira adequada para corresponder às transições do ciclo.

Para ver as etapas de implementação de cada caso de uso, consulte Implementar uma visualização, Analisar imagens e Tirar uma foto.

Exemplo de modelo de API

O caso de uso da visualização interage com um Surface para exibição. Os aplicativos usam o código a seguir para criar o caso de uso com opções de configuração:

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.createSurfaceProvider(camera.cameraInfo))
    

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.createSurfaceProvider(camera.getCameraInfo());
    

Para ver mais exemplos de códigos, consulte o app de amostra oficial do CameraX.

Lifecycles do CameraX

O CameraX leva em conta um ciclo de vida para determinar quando abrir a câmera, quando criar uma sessão de captura e quando parar tudo e encerrar. As APIs de caso de uso fornecem chamadas de método e callbacks para monitorar o progresso.

Conforme explicado em Combinar casos de uso, é possível vincular algumas combinações de casos de uso a um único ciclo de vida. Quando seu app tiver que ser compatível com casos de uso que não possam ser combinados, você poderá seguir um destes procedimentos:

  • Agrupe os casos de uso compatíveis em mais de um fragmento, depois alterne entre os fragmentos.
  • Crie um componente de ciclo de vida personalizado e use-o para controlar manualmente o ciclo de vida da câmera.

Se você desacoplar os proprietários do Lifecycle dos seus casos de uso de visualização e câmera (por exemplo, ao usar um ciclo de vida personalizado ou um fragmento de retenção ), será necessário garantir que todos os casos de uso estejam desvinculados do CameraX por meio de CameraX.unbindAll() ou desvincular cada um deles individualmente. Como alternativa, ao vincular casos de uso a um Lifecycle, você pode deixar que o CameraX gerencie a abertura e o encerramento da sessão de captura e a desvinculação dos casos de uso.

Se toda a funcionalidade da sua câmera corresponder ao ciclo de vida de um único componente compatível com ciclo de vida, como um fragmento AppCompatActivity ou AppCompat, usar o ciclo de vida desse componente ao vincular todos os casos de uso garantirá que a funcionalidade de câmera esteja pronta quando o componente de ciclo de vida estiver ativo e que, caso contrário, seja descartada com segurança, sem consumir recursos.

LifecycleOwners personalizados

Em casos avançados, é possível criar um LifecycleOwner personalizado para permitir que seu app controle explicitamente o ciclo de vida da sessão do CameraX, em vez de vinculá-lo a um LifecycleOwner padrão do Android.

A amostra de código a seguir mostra como criar um LifecycleOwner personalizado simples:

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

Com esse LifecycleOwner, seu app poderá posicionar transições de estado em pontos específicos do código. Para ter mais informações sobre como implementar essa funcionalidade no seu app, consulte Implementar um LifecycleOwner personalizado.

Casos de usos simultâneos

Casos de uso podem ser executados simultaneamente. Embora eles possam ser vinculados de modo sequencial a um ciclo de vida, é melhor vincular todos os casos de uso com uma única chamada a CameraProcessProvider.bindToLifecycle(). Para mais informações sobre práticas recomendadas em mudanças de configuração, consulte Gerenciar alterações de configuração.

Na amostra de código a seguir, o app especifica os dois casos de uso que serão criados e executados simultaneamente. Também determina o ciclo de vida a ser usado nos dois casos de uso, para que ambos possam ser iniciados e encerrados de acordo com esse ciclo.

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.createSurfaceProvider(camera.cameraInfo))
        }, 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 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.createSurfaceProvider(camera.getCameraInfo()));
            } catch (InterruptedException | ExecutionException e) {
                // Currently no exceptions thrown. cameraProviderFuture.get() should
                // not block since the listener is being called, so no need to
                // handle InterruptedException.
            }
        }, ContextCompat.getMainExecutor(this));
    }
    

As seguintes combinações de configuração são compatíveis:

Análise Visualização Captura de imagem Combinação de casos de usos
Fornece uma visualização ao usuário, tira uma foto e analisa o fluxo da imagem.
  Tira uma foto e analisa o fluxo da imagem.
  Fornece uma visualização com efeitos visuais aplicados com base na análise das imagens em exibição.
  Mostra o que a câmera vê e tira uma foto quando o usuário solicita.

Quando as extensões estão ativadas, apenas ImageCapture e Preview são garantidas. Dependendo da implementação do OEM, talvez não seja possível usar ImageAnalysis.

ImageCapture não funciona por conta própria, embora Preview e ImageAnalysis funcionem.

Permissões

Seu app precisará da permissão CAMERA. Para salvar imagens em arquivos, ele precisará também da permissão WRITE_EXTERNAL_STORAGE, exceto em dispositivos com Android 10 ou mais recente.

Para saber mais informações sobre como configurar permissões para seu app, leia Solicitar permissões de app.

Requisitos

O CameraX possui os seguintes requisitos mínimos de versão:

  • API Android de nível 21
  • Android Architecture Components 1.1.1

Para atividades que envolvam ciclo de vida, use FragmentActivity ou AppCompatActivity.

Declarar dependências

Para adicionar uma dependência ao CameraX, é preciso adicionar o repositório Maven do Google ao seu projeto.

Abra o arquivo build.gradle do projeto e adicione o repositório google(), conforme mostrado a seguir:

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

Adicione o seguinte código ao fim do bloco do Android:

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    

Adicione o seguinte ao arquivo build.gradle de cada módulo para um app:

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

Para mais informações sobre como configurar seu app para atender a esses requisitos, consulte Declarar dependências.

Outros recursos

Para saber mais sobre o CameraX, consulte os seguintes recursos.

Codelab

  • Introdução ao CameraX
  • Amostra de código

  • App de amostra oficial do CameraX