Remarque : La classe Camera, qui est mentionnée sur cette page, est obsolète. Nous vous recommandons d'utiliser CameraX ou, dans des cas d'utilisation spécifiques, Camera2. CameraX et Camera2 sont compatibles avec Android 5.0 (niveau d'API 21) ou version ultérieure.
Cette leçon explique comment prendre une photo en déléguant la tâche à une autre application Appareil photo sur l'appareil. (Si vous préférez créer les fonctionnalités de votre propre appareil photo, consultez Contrôler l'appareil photo.)
Supposons que vous implémentez un service météorologique alimenté par les utilisateurs, qui crée une carte météo mondiale en combinant les images du ciel prises par les appareils équipés de votre application cliente. L'intégration de photos ne représente qu'une petite partie de votre application. Vous voulez prendre des photos facilement, sans réinventer l'appareil photo. Heureusement, la plupart des appareils Android ont déjà d'au moins une application Appareil photo. Dans cette leçon, vous allez apprendre comment lui faire prendre une photo pour vous.
Demander la fonctionnalité Appareil photo
Si l'une des fonctions essentielles de votre application consiste à prendre des photos, limitez sa visibilité
sur Google Play aux appareils équipés d'un appareil photo. Pour annoncer que votre application nécessite un appareil photo,
placez une balise
<uses-feature>
dans
le fichier manifeste :
<manifest ... > <uses-feature android:name="android.hardware.camera" android:required="true" /> ... </manifest>
Si votre application fonctionne sans avoir toutefois besoin d'un appareil photo, définissez plutôt
android:required
sur false
. De cette façon, Google Play autorisera les appareils
sans appareil photo à télécharger votre application. Vous devez ensuite vérifier la disponibilité
de l'appareil photo au moment de l'exécution en appelant
hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
.
S'il ne l'est pas, vous devez alors désactiver ses fonctionnalités.
Obtenir la vignette
Si prendre une simple photo n'est pas votre ambition majeure avec votre application, vous voudrez probablement récupérer ensuite l'image dans l'application Appareil photo pour en faire quelque chose.
L'application Appareil photo d'Android encode la photo dans
l'Intent
envoyé à
onActivityResult()
sous la forme d'un petit Bitmap
en plus,
sous la clé "data"
. Le code suivant récupère cette image et l'affiche dans une
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); } }
Remarque : Cette vignette de "data"
peut convenir pour une icône,
mais guère plus. Traiter une image en taille réelle nécessite un peu plus de travail.
Enregistrer la photo en taille réelle
L'application Appareil photo d'Android enregistre une photo en taille réelle si vous lui donnez un fichier pour le faire. Vous devez attribuer un nom complet au fichier dans lequel l'application Appareil photo doit enregistrer la photo.
En général, toutes les photos que l'utilisateur prend avec l'appareil photo doivent être enregistrées dans l'espace de stockage
externe public, afin que toutes les applications y aient accès. Le répertoire approprié pour les photos
partagées est fourni par
getExternalStoragePublicDirectory()
,
avec l'argument
DIRECTORY_PICTURES
. Lorsqu'il est fourni via cette méthode, le répertoire est partagé entre toutes les applications. Sous Android 9 (niveau d'API 28)
ou versions antérieures, la lecture et l'écriture dans ce répertoire nécessitent
respectivement
les autorisations
READ_EXTERNAL_STORAGE
et WRITE_EXTERNAL_STORAGE
:
<manifest ...> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> ... </manifest>
Sous Android 10 (niveau d'API 29) ou version ultérieure, le répertoire approprié pour le partage de
photos est la table MediaStore.Images
.
Vous n'avez pas besoin de déclarer d'autorisations d'accès à l'espace de stockage, tant que votre application a
uniquement besoin d'accéder aux photos que l'utilisateur a prises avec elle.
Toutefois, si vous voulez que les photos restent seulement dans votre application, vous pouvez utiliser à la place
le répertoire fourni par
Context.getExternalFilesDir()
.
Sous Android 4.3 et versions antérieures, l'écriture dans ce répertoire nécessite également l'autorisation
WRITE_EXTERNAL_STORAGE
. À partir d'Android 4.4, l'autorisation n'est plus nécessaire, car le répertoire est inaccessible
aux autres applications. Vous pouvez donc déclarer que l'autorisation ne doit être demandée que sur les
versions antérieures d'Android en ajoutant
l'attribut maxSdkVersion
:
<manifest ...> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" /> ... </manifest>
Remarque : Les fichiers que vous enregistrez dans les répertoires fournis par
getExternalFilesDir()
ou
getFilesDir()
sont
supprimés lorsque l'utilisateur désinstalle votre application.
Une fois que vous avez choisi le répertoire du fichier, vous devez créer un nom de fichier "anti-collision".
Vous pouvez également enregistrer le chemin d'accès dans une variable de membre pour une utilisation ultérieure. Voici un exemple de solution dans une méthode qui renvoie un nom de fichier unique pour une nouvelle photo avec un horodatage.
(Dans cet exemple, nous partons du principe que vous appelez la méthode à partir d'un élément 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; }
Avec cette méthode disponible pour créer un fichier pour la photo, vous pouvez désormais créer et appeler Intent
comme suit :
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); } } }
Remarque : Nous utilisons
getUriForFile(Context, String, File)
qui renvoie un URI content://
. Pour les applications plus récentes ciblant Android 7.0 (niveau d'API 24)
et versions ultérieures, la transmission d'un URI file://
au-delà des limites d'un package entraîne un
FileUriExposedException
.
C'est pourquoi nous présentons désormais un moyen plus générique de stocker des images à l'aide d'un
FileProvider
.
Vous devez maintenant configurer le
FileProvider
. Dans le fichier
manifeste de votre application, ajoutez un fournisseur à votre application :
<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>
Assurez-vous que la chaîne d'autorités correspond au deuxième argument de
getUriForFile(Context, String, File)
.
Dans la section des métadonnées de la définition du fournisseur, vous pouvez voir que celui-ci s'attend
à ce que les chemins d'accès éligibles soient configurés dans un fichier de ressources dédié,
<?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>
Le composant de chemin d'accès correspond au chemin d'accès renvoyé par
getExternalFilesDir()
lorsqu'il est appelé avec
Environment.DIRECTORY_PICTURES
.
Veillez à remplacer com.example.package.name
par le nom de package réel de votre application.
Consultez également la documentation du
FileProvider
pour avoir une
description détaillée des spécificateurs de chemin d'accès que vous pouvez utiliser en plus de external-path
.
Ajouter la photo à une galerie
Lorsque vous créez une photo par le biais d'un intent, vous devez savoir où se trouve votre image, car vous avez indiqué où l'enregistrer en premier lieu. Pour n'importe qui d'autre, le moyen le plus simple de rendre votre photo accessible consiste à le faire à partir du fournisseur multimédia du système.
Remarque : Si vous avez enregistré votre photo dans le répertoire fourni par
getExternalFilesDir()
,
l'outil d'analyse multimédia ne peut pas accéder aux fichiers, car ils sont privés.
L'exemple de méthode ci-dessous montre comment appeler l'outil d'analyse multimédia du système pour ajouter votre photo à la base de données du fournisseur multimédia, afin qu'elle soit disponible dans l'application Galerie d'Android et dans d'autres applications.
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); }
Décoder une image mise à l'échelle
Gérer plusieurs images en taille réelle peut être compliqué quand la mémoire est limitée. Si vous constatez que votre application n'a pas suffisamment de mémoire après avoir affiché seulement quelques images, vous pouvez réduire considérablement la quantité de tas de mémoire dynamique utilisée en développant le fichier JPEG dans un tableau de mémoire déjà mis à l'échelle pour correspondre à la taille de la vue de destination. L'exemple de méthode ci-dessous illustre cette technique.
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); }