Implementação Eficiente de Captura de Fotos no Android: Do Intent ao CameraX

A funcionalidade de captura de imagem é um pilar fundamental no desenvolvimento Android moderno. Seja para redes sociais, verificação de identidade ou digitalização de documentos, integrar a câmera de forma robusta exige conhecimento sobre permissões, gestão de memória e APIs de hardware. Este guia explora as melhores práticas para implementar desde chamadas simples via Intent até o uso da biblioteca CameraX.

1. Captura de Imagem via Intents do Sistema

A maneira mais rápida de implementar a captura de fotos é delegar a tarefa ao aplicativo de câmera padrão do dispositivo. Isso reduz a complexidade do código e garante uma interface familiar ao usuário.

Configuração do Intent de Captura

Para obter uma imagem em alta resolução, não basta apenas disparar o Intent; é necessário fornecer um URI onde o sistema salvará o arquivo original.


// Definição do código de requisição
private static final int CODIGO_DISPARO_CAMERA = 101;
private String caminhoFotoAtual;

private void dispararIntentCamera() {
    Intent intentCaptura = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    
    // Verifica se existe um app de câmera disponível
    if (intentCaptura.resolveActivity(getPackageManager()) != null) {
        File arquivoFoto = null;
        try {
            arquivoFoto = criarArquivoDeImagem();
        } catch (IOException ex) {
            // Tratar erro na criação do arquivo
        }
        
        if (arquivoFoto != null) {
            Uri uriFoto = FileProvider.getUriForFile(this,
                    "com.exemplo.app.fileprovider",
                    arquivoFoto);
            intentCaptura.putExtra(MediaStore.EXTRA_OUTPUT, uriFoto);
            startActivityForResult(intentCaptura, CODIGO_DISPARO_CAMERA);
        }
    }
}

2. Segurança e Compartilhamento com FileProvider

Desde o Android 7.0 (Nougat), o compartilhamento de URIs file:// é proibido por motivos de segurança. Deve-se utilizar o FileProvider para gerar URIs content://.

Configuração do provider_paths.xml

Crie um arquivo em res/xml/caminhos_arquivos.xml:


<paths>
    <external-files-path name="fotos_app" path="Pictures/" />
</paths>

Declaração no AndroidManifest.xml


<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="com.exemplo.app.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/caminhos_arquivos" />
</provider>

3. Processamento de Imagem e Gestão de Memória

Imagens modernas podem ter resoluções altíssimas, consumindo dezenas de megabytes se carregadas diretamente na memória RAM. Para evitar o erro OutOfMemoryError (OOM), é essencial realizar o redimensionamento antes da decodificação.

Cálculo de Amostragem (Sampling)


public static Bitmap decodificarImagemOtimizada(String path, int larguraAlvo, int alturaAlvo) {
    BitmapFactory.Options opcoes = new BitmapFactory.Options();
    opcoes.inJustDecodeBounds = true; // Apenas lê as dimensões, sem carregar o bitmap
    BitmapFactory.decodeFile(path, opcoes);

    // Calcula o fator de escala
    opcoes.inSampleSize = calcularTamanhoAmostra(opcoes, larguraAlvo, alturaAlvo);
    
    // Decodifica o bitmap com o tamanho real otimizado
    opcoes.inJustDecodeBounds = false;
    opcoes.inPreferredConfig = Bitmap.Config.RGB_565; // Reduz o uso de memória pela metade
    
    return BitmapFactory.decodeFile(path, opcoes);
}

private static int calcularTamanhoAmostra(BitmapFactory.Options opcoes, int reqW, int reqH) {
    int alturaOriginal = opcoes.outHeight;
    int larguraOriginal = opcoes.outWidth;
    int inSampleSize = 1;

    if (alturaOriginal > reqH || larguraOriginal > reqW) {
        final int metadeH = alturaOriginal / 2;
        final int metadeW = larguraOriginal / 2;

        while ((metadeH / inSampleSize) >= reqH && (metadeW / inSampleSize) >= reqW) {
            inSampleSize *= 2;
        }
    }
    return inSampleSize;
}

4. Correção de Orientação via EXIF

Muitos dispositivos salvam a imagem em uma orientação fixa, inserindo a rotação correta nos metadados EXIF. Ignorar isso pode resultar em fotos exibidas de lado.


private Bitmap corrigirRotacao(Bitmap bitmap, String path) throws IOException {
    ExifInterface exifInterface = new ExifInterface(path);
    int orientacao = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
    
    Matrix matriz = new Matrix();
    switch (orientacao) {
        case ExifInterface.ORIENTATION_ROTATE_90:
            matriz.postRotate(90);
            break;
        case ExifInterface.ORIENTATION_ROTATE_180:
            matriz.postRotate(180);
            break;
        case ExifInterface.ORIENTATION_ROTATE_270:
            matriz.postRotate(270);
            break;
        default:
            return bitmap;
    }
    
    return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matriz, true);
}

5. Evolução para Câmera Customizada com CameraX

Quando a aplicação exige um controle maior sobre a interface de captura ou análise de imagem em tempo real (como leitura de QR Code), a Jetpack CameraX é a escolha ideal por ser ciente do ciclo de vida (lifecycle-aware).

Vantagens da CameraX:

  • Consistência: Resolve problemas de fragmentação entre fabricantes automaticamente.
  • Lifecycle: Gerencia a abertura e fechamento da câmera com base no estado da Activity/Fragment.
  • Extensibilidade: Suporte nativo para HDR, Modo Noturno e Retrato em dispositivos compatíveis.

Configuração de um Caso de Uso de Visualização


private void iniciarCameraX() {
    ListenableFuture<ProcessCameraProvider> cameraProviderFuture = 
            ProcessCameraProvider.getInstance(this);

    cameraProviderFuture.addListener(() -> {
        try {
            ProcessCameraProvider provider = cameraProviderFuture.get();
            
            Preview preview = new Preview.Builder().build();
            preview.setSurfaceProvider(previewView.getSurfaceProvider());

            CameraSelector seletor = CameraSelector.DEFAULT_BACK_CAMERA;
            
            provider.unbindAll();
            provider.bindToLifecycle(this, seletor, preview);
            
        } catch (ExecutionException | InterruptedException e) {
            // Tratar exceções
        }
    }, ContextCompat.getMainExecutor(this));
}

Considerações Finais sobre Permissões

Para qualquer operação de câmera, lembre-se de solicitar as permissões em tempo de execução para dispositivos com Android 6.0 ou superior. O uso da permissão CAMERA é obrigatório, e o acesso ao armazenamento (READ_EXTERNAL_STORAGE / WRITE_EXTERNAL_STORAGE) pode ser necessário dependenod do local de salvamento escolhido para a foto.

Tags: android CameraX bitmap ExifInterface FileProvider

Publicado em 7-25 05:06