Nota: Esta página hace referencia a la clase Camera, que dejó de estar disponible. Te recomendamos que uses CameraX o, en casos de uso específicos, Camera2. CameraX y Camera2 admiten Android 5.0 (nivel de API 21) y versiones posteriores.
En esta lección, se describe cómo capturar una foto delegando el trabajo a otra app de cámara en el dispositivo. (Si prefieres desarrollar tu propia funcionalidad de cámara, consulta Cómo controlar la cámara).
Supongamos que estás implementando un servicio meteorológico a partir de un aporte colectivo que crea un mapa climático global mediante la combinación de imágenes del cielo tomadas por dispositivos que ejecutan tu app cliente. La integración de fotos es solo una pequeña parte de la aplicación. Tu objetivo es tomar fotos con un mínimo de complicaciones, no reinventar la cámara. Afortunadamente, la mayoría de los dispositivos con Android ya tienen instalada al menos una aplicación de cámara. En esta lección, aprenderás cómo hacer que tome una foto por ti.
Cómo solicitar la función de cámara
Si una función esencial de tu aplicación es tomar fotos, restringe su visibilidad en Google Play a los dispositivos que tienen una cámara. Para anunciar que tu aplicación depende de tener una cámara, coloca una etiqueta <uses-feature>
en el archivo de manifiesto:
<manifest ... > <uses-feature android:name="android.hardware.camera" android:required="true" /> ... </manifest>
Si tu aplicación usa una cámara, pero no la requiere para funcionar, configura android:required
como false
en su lugar. De esa manera, Google Play permitirá que los dispositivos sin cámara descarguen la aplicación. Luego, es tu responsabilidad verificar la disponibilidad de la cámara durante el tiempo de ejecución llamando a hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
.
Si no hay una cámara disponible, debes inhabilitar las funciones de la cámara.
Cómo obtener la miniatura
Si la simple tarea de tomar una foto no es el objetivo principal de tu app, seguramente desees recuperar la imagen de la aplicación de cámara y hacer algo con ella.
La aplicación de cámara de Android codifica la foto en el Intent
de devolución que se entrega a onActivityResult()
como un Bitmap
pequeño en la carpeta Extras, en los "data"
clave. El siguiente código recupera esta imagen y la muestra en una 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); } }
Nota: Esta imagen en miniatura de "data"
podría usarse para un ícono, pero no para mucho más. Usar una imagen de tamaño original requiere un poco más de trabajo.
Cómo guardar la foto de tamaño original
La aplicación de cámara de Android guarda una foto de tamaño original si proporcionas un archivo para hacerlo. Debes incluir un nombre de archivo completo donde la app de cámara debe guardar la foto.
En general, todas las fotos que el usuario captura con la cámara del dispositivo deben guardarse en el almacenamiento externo público del dispositivo para que todas las apps puedan acceder a ellas. getExternalStoragePublicDirectory()
proporciona el directorio adecuado para las fotos compartidas, con el argumento DIRECTORY_PICTURES
. El directorio proporcionado por este método se comparte con todas las apps. En Android 9 (nivel de API 28) y versiones anteriores, la lectura y escritura en este directorio requiere los permisos READ_EXTERNAL_STORAGE
y WRITE_EXTERNAL_STORAGE
, respectivamente:
<manifest ...> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> ... </manifest>
En Android 10 (nivel de API 29) y versiones posteriores, el directorio adecuado para compartir fotos es la tabla MediaStore.Images
.
No necesitas declarar ningún permiso de almacenamiento, siempre que tu app solo necesite acceder a las fotos que el usuario tomó con esta.
Sin embargo, si deseas que las fotos sean privadas para tu app, puedes usar el directorio que proporciona Context.getExternalFilesDir()
en su lugar.
En Android 4.3 y versiones anteriores, para escribir en este directorio, también se requiere el permiso WRITE_EXTERNAL_STORAGE
. A partir de Android 4.4, el permiso ya no es necesario porque otras apps no pueden acceder al directorio. Por lo tanto, para declarar que el permiso solo debe solicitarse en las versiones anteriores de Android, agrega el atributo maxSdkVersion
:
<manifest ...> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> ... </manifest>
Nota: Los archivos que guardes en los directorios que proporcionan getExternalFilesDir()
o getFilesDir()
se borrarán cuando el usuario desinstale la app.
Una vez que elijas el directorio para el archivo, deberás crear un nombre de archivo resistente a colisiones.
Es posible que también desees guardar la ruta en una variable de miembro para su uso posterior. A continuación, se incluye una solución de ejemplo en un método que muestra un nombre de archivo único para una foto nueva usando una marca de fecha y hora.
En este ejemplo, se supone que llamas al método desde un 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; }
Con este método disponible para crear un archivo para la foto, ahora puedes crear el Intent
y, luego, invocarlo de esta manera:
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); } } }
Nota: Utilizamos getUriForFile(Context, String, File)
, que muestra un URI de content://
. Para las apps más recientes dirigidas a Android 7.0 (nivel de API 24) y versiones posteriores, pasar un URI de file://
a través de un límite de paquete provoca un FileUriExposedException
.
Por lo tanto, presentamos una forma más genérica de almacenar imágenes con un FileProvider
.
Ahora debes configurar FileProvider
. En el manifiesto de la app, agrega un proveedor a la aplicación:
<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>
Asegúrate de que la cadena de autoridades coincida con el segundo argumento en getUriForFile(Context, String, File)
.
En la sección de metadatos de la definición del proveedor, puedes ver que el proveedor espera que las rutas aptas se configuren en un archivo de recursos exclusivo,
<?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>
El componente de ruta corresponde a la ruta que muestra getExternalFilesDir()
cuando se llama con Environment.DIRECTORY_PICTURES
.
Asegúrate de reemplazar com.example.package.name
con el nombre del paquete real de tu app. Además, consulta la documentación de FileProvider
para obtener una descripción detallada de los especificadores de ruta que puedes usar aparte de external-path
.
Cómo agregar la foto a una galería
Cuando creas una foto a través de un intent, debes saber dónde se encuentra tu imagen, porque indicaste dónde guardarla en primer lugar. Para todos los demás, quizás la forma más fácil de hacer que tu foto sea accesible es desde el proveedor de contenido multimedia del sistema.
Nota: Si guardaste la foto en el directorio que proporciona getExternalFilesDir()
, el escáner multimedia no podrá acceder a los archivos, porque son privados de tu app.
En el siguiente método de ejemplo, se muestra cómo invocar el escáner multimedia del sistema para agregar la foto a la base de datos del proveedor de contenido multimedia con el objetivo de que esté disponible en la aplicación de Galería de Android y en otras 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); }
Cómo decodificar una imagen ajustada a escala
Administrar múltiples imágenes de tamaño original puede ser complicado con memoria limitada. Si ves que tu aplicación se está quedando sin memoria después de mostrar solo algunas imágenes, puedes reducir drásticamente la cantidad de almacenamiento dinámico utilizado con la expansión del JPEG a un array de memoria que ya esté ajustado a escala para coincidir con el tamaño de la vista de destino. En el siguiente método de ejemplo, se muestra esta 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); }