Tirar fotos

Observação: esta página se refere à classe Camera, que foi descontinuada. Recomendamos usar CameraX ou, para casos de uso específicos, Camera2. CameraX e Camera2 oferecem suporte ao Android 5.0 (nível 21 da API) e versões mais recentes.

Este guia ensina como capturar uma foto delegando o trabalho a outro app de câmera no dispositivo. Caso prefira criar seu próprio recurso de câmera, consulte Como controlar a câmera.

Suponha que você esteja implementando um serviço meteorológico com colaboração coletiva, que cria um mapa meteorológico global misturando fotos do céu tiradas por dispositivos que executam seu app cliente. Integrar as fotos é só uma pequena parte do aplicativo. O objetivo é tirar fotos com o mínimo de problemas possível, e não reinventar a câmera. A maioria dos dispositivos Android já tem pelo menos um aplicativo de câmera instalado. Neste guia, você vai aprender a fazer com que ele tire uma foto para você.

Solicitar o recurso de câmera

Se uma função essencial do seu aplicativo é tirar fotos, restrinja a visibilidade dele no Google Play para dispositivos que tenham câmera. Para anunciar que seu aplicativo depende de uma câmera, coloque uma tag <uses-feature> no arquivo de manifesto:

<manifest ... >
    <uses-feature android:name="android.hardware.camera"
                  android:required="true" />
    ...
</manifest>

Caso seu aplicativo use, mas não precise de uma câmera para funcionar, configure android:required como false. Dessa forma, o Google Play vai permitir que dispositivos sem câmera façam o download do seu aplicativo. Você precisa verificar a disponibilidade da câmera durante a execução, chamando hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY). Se uma câmera não estiver disponível, os recursos da câmera vão precisar ser desativados.

Usar a miniatura

Se o objetivo principal do seu app não é somente tirar fotos, talvez você queira extrair a imagem do aplicativo da câmera e fazer algo com ela.

O aplicativo Câmera do Android codifica a foto na Intent de retorno entregue ao onActivityResult() como um pequeno Bitmap nos extras, sob a chave "data". O código abaixo extrai essa imagem e a exibe em ImageView.

Kotlin

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
        val imageBitmap = data.extras.get("data") as Bitmap
        imageView.setImageBitmap(imageBitmap)
    }
}

Java

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
        Bundle extras = data.getExtras();
        Bitmap imageBitmap = (Bitmap) extras.get("data");
        imageView.setImageBitmap(imageBitmap);
    }
}

Observação: essa imagem de miniatura de "data" pode ser útil para um ícone, mas não é útil para outros fins. Processar uma imagem no tamanho original exige um pouco mais de trabalho.

Salvar a foto no tamanho original

O aplicativo Câmera do Android salva uma foto em tamanho original se você fornecer um arquivo em que ela possa ser salva. Você precisa fornecer um nome de arquivo totalmente qualificado em que o app da câmera possa salvar a foto.

Em geral, as fotos tiradas pelo usuário com a câmera do dispositivo são salvas no armazenamento externo público do dispositivo, para que possam ser acessadas por todos os apps. O diretório adequado para as fotos compartilhadas é fornecido pelo getExternalStoragePublicDirectory(), com o argumento DIRECTORY_PICTURES. O diretório fornecido por esse método é compartilhado entre todos os apps. No Android 9 (nível 28 da API) e versões anteriores, a leitura e a gravação nesse diretório exigem as permissões READ_EXTERNAL_STORAGE e WRITE_EXTERNAL_STORAGE, respectivamente:

<manifest ...>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    ...
</manifest>

No Android 10 (nível 29 da API) e versões mais recentes, o diretório adequado para o compartilhamento de fotos é a tabela MediaStore.Images. Não é necessário declarar nenhuma permissão de armazenamento, desde que o app só precise acessar as fotos que o usuário tirou com ele.

Mas se você quiser que as fotos possam ser acessadas apenas pelo seu app, pode usar o diretório fornecido por Context.getExternalFilesDir(). No Android 4.3 e versões anteriores, a gravação nesse diretório também exige a permissão WRITE_EXTERNAL_STORAGE. No Android 4.4 e versões mais recentes, a permissão não é mais necessária, porque o diretório não pode ser acessado por outros apps. Você pode declarar que a permissão precisa ser solicitada apenas nas versões anteriores do Android ao adicionar o atributo maxSdkVersion:

<manifest ...>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                     android:maxSdkVersion="28" />
    ...
</manifest>

Observação:os arquivos salvos nos diretórios fornecidos por getExternalFilesDir() ou getFilesDir() são excluídos quando o usuário desinstala o app.

Depois de decidir o diretório do arquivo, é necessário criar um nome de arquivo resistente a colisões. Você também pode salvar o caminho em uma variável de membro para uso posterior. Confira um exemplo de solução em um método que retorna um nome de arquivo exclusivo para uma nova foto, usando um carimbo de data e hora. Este exemplo pressupõe que você esteja chamando o método dentro de um Context.

Kotlin

lateinit var currentPhotoPath: String

@Throws(IOException::class)
private fun createImageFile(): File {
    // Create an image file name
    val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
    val storageDir: File = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
    return File.createTempFile(
            "JPEG_${timeStamp}_", /* prefix */
            ".jpg", /* suffix */
            storageDir /* directory */
    ).apply {
        // Save a file: path for use with ACTION_VIEW intents
        currentPhotoPath = absolutePath
    }
}

Java

String currentPhotoPath;

private File createImageFile() throws IOException {
    // Create an image file name
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    String imageFileName = "JPEG_" + timeStamp + "_";
    File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
    File image = File.createTempFile(
        imageFileName,  /* prefix */
        ".jpg",         /* suffix */
        storageDir      /* directory */
    );

    // Save a file: path for use with ACTION_VIEW intents
    currentPhotoPath = image.getAbsolutePath();
    return image;
}

Com esse método disponível para criar um arquivo para a foto, você pode criar e invocar a Intent desta forma:

Kotlin

private fun dispatchTakePictureIntent() {
    Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
        // Ensure that there's a camera activity to handle the intent
        takePictureIntent.resolveActivity(packageManager)?.also {
            // Create the File where the photo should go
            val photoFile: File? = try {
                createImageFile()
            } catch (ex: IOException) {
                // Error occurred while creating the File
                ...
                null
            }
            // Continue only if the File was successfully created
            photoFile?.also {
                val photoURI: Uri = FileProvider.getUriForFile(
                        this,
                        "com.example.android.fileprovider",
                        it
                )
                takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
                startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
            }
        }
    }
}

Java

private void dispatchTakePictureIntent() {
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    // Ensure that there's a camera activity to handle the intent
    if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
        // Create the File where the photo should go
        File photoFile = null;
        try {
            photoFile = createImageFile();
        } catch (IOException ex) {
            // Error occurred while creating the File
            ...
        }
        // Continue only if the File was successfully created
        if (photoFile != null) {
            Uri photoURI = FileProvider.getUriForFile(this,
                                                  "com.example.android.fileprovider",
                                                  photoFile);
            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
            startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
        }
    }
}

Observação: estamos usando getUriForFile(Context, String, File), que retorna um URI content://. Para apps mais recentes destinados ao Android 7.0 (nível 24 da API) e versões posteriores, transmitir um URI de file:// em um limite de pacote causa uma FileUriExposedException. Mas há uma maneira mais genérica de armazenar imagens usando um FileProvider.

Agora, você precisa configurar o FileProvider. No manifesto do app, adicione um provedor ao seu aplicativo:

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

Verifique se a string de autoridades corresponde ao segundo argumento para getUriForFile(Context, String, File). Na seção de metadados da definição do provedor, é possível notar que o provedor espera que caminhos qualificados sejam configurados em um arquivo de recurso dedicado, res/xml/file_paths.xml. Consulte o conteúdo necessário para este exemplo específico:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-files-path name="my_images" path="Pictures" />
</paths>

O componente corresponde ao caminho retornado por getExternalFilesDir() quando chamado com Environment.DIRECTORY_PICTURES. Substitua com.example.package.name pelo nome real do pacote do seu app. Além disso, confira a documentação de FileProvider para uma descrição detalhada dos especificadores de caminho que podem ser usados além de external-path.

Adicionar a foto a uma galeria

Ao criar uma foto usando uma intent, você vai saber onde sua imagem está localizada, já que você indicou onde ela é salva. Para todas as outras pessoas, é provável que a maneira mais fácil de disponibilizar sua foto seja torná-la acessível pelo provedor de mídia do sistema.

Observação: se você salvou sua foto no diretório fornecido pelo getExternalFilesDir(), o detector de mídia não pode acessar os arquivos, porque eles são particulares do seu app.

O método de exemplo abaixo demonstra como invocar o detector de mídia do sistema para adicionar sua foto ao banco de dados do provedor de mídia, para que ela fique disponível no aplicativo Galeria do Android e em outros apps.

Kotlin

private fun galleryAddPic() {
    Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE).also { mediaScanIntent ->
        val f = File(currentPhotoPath)
        mediaScanIntent.data = Uri.fromFile(f)
        sendBroadcast(mediaScanIntent)
    }
}

Java

private void galleryAddPic() {
    Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
    File f = new File(currentPhotoPath);
    Uri contentUri = Uri.fromFile(f);
    mediaScanIntent.setData(contentUri);
    this.sendBroadcast(mediaScanIntent);
}

Decodificar uma imagem dimensionada

Gerenciar várias imagens em tamanho original com memória limitada pode ser complicado. Se o aplicativo estiver com memória insuficiente depois de mostrar algumas imagens, é possível reduzir drasticamente a quantidade de heap dinâmico expandindo o JPEG em uma matriz de memória dimensionada para corresponder ao tamanho da visualização de destino. O método de exemplo abaixo demonstra essa técnica.

Kotlin

private fun setPic() {
    // Get the dimensions of the View
    val targetW: Int = imageView.width
    val targetH: Int = imageView.height

    val bmOptions = BitmapFactory.Options().apply {
        // Get the dimensions of the bitmap
        inJustDecodeBounds = true

        BitmapFactory.decodeFile(currentPhotoPath, bmOptions)

        val photoW: Int = outWidth
        val photoH: Int = outHeight

        // Determine how much to scale down the image
        val scaleFactor: Int = Math.max(1, Math.min(photoW / targetW, photoH / targetH))

        // Decode the image file into a Bitmap sized to fill the View
        inJustDecodeBounds = false
        inSampleSize = scaleFactor
        inPurgeable = true
    }
    BitmapFactory.decodeFile(currentPhotoPath, bmOptions)?.also { bitmap ->
        imageView.setImageBitmap(bitmap)
    }
}

Java

private void setPic() {
    // Get the dimensions of the View
    int targetW = imageView.getWidth();
    int targetH = imageView.getHeight();

    // Get the dimensions of the bitmap
    BitmapFactory.Options bmOptions = new BitmapFactory.Options();
    bmOptions.inJustDecodeBounds = true;

    BitmapFactory.decodeFile(currentPhotoPath, bmOptions);

    int photoW = bmOptions.outWidth;
    int photoH = bmOptions.outHeight;

    // Determine how much to scale down the image
    int scaleFactor = Math.max(1, Math.min(photoW/targetW, photoH/targetH));

    // Decode the image file into a Bitmap sized to fill the View
    bmOptions.inJustDecodeBounds = false;
    bmOptions.inSampleSize = scaleFactor;
    bmOptions.inPurgeable = true;

    Bitmap bitmap = BitmapFactory.decodeFile(currentPhotoPath, bmOptions);
    imageView.setImageBitmap(bitmap);
}