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