web-dev-qa-db-fra.com

Android: Comment obtenir un contenu: // URI pour un fichier dans le répertoire PUBLIC de stockage externe

J'ai suivi ce tutoriel Google pour démarrer une intention de capturer une image avec new Intent(MediaStore.ACTION_IMAGE_CAPTURE). Le didacticiel recommande d'utiliser le répertoire public avec getExternalStoragePublicDirectory, ce qui fonctionnerait très bien pour mon application. Mais alors leur exemple utilise à la place getExternalFilesDir. Ensuite, pour passer l'URI du fichier à l'intention avec MediaStore.EXTRA_OUTPUT I je dois obtenir un URI de conten parce que je voudrais cibler Android N. Avant de cibler NI, il suffit de passer un fichier: // URI et tout le monde, mais Google était content. Maintenant, NI a commencé à obtenir le FileUriExposedException qui semble être pas très favorable .

Donc, étant donné que j'ai un fichier comme celui-ci ...

private File createImageFile() throws IOException {
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    File storageDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "MyAppFolder");
    if (!storageDir.exists() && !storageDir.mkdir())
        Log.w(TAG, "Couldn't create photo folder: " + storageDir.getAbsolutePath());
    File image = new File(storageDir, timeStamp + ".jpg");
    mCurrentPhotoPath = image.getAbsolutePath();
    return image;
}

... puis-je utiliser un fournisseur intégré pour le répertoire public d'images pour obtenir un URI de contenu? Si c'est le cas, comment?

J'ai essayé quelque chose comme

takePictureIntent.putExtra(
    MediaStore.EXTRA_OUTPUT, 
    FileProvider.getUriForFile(this, MediaStore.AUTHORITY, createImageFile()));

mais ça jette juste

IllegalArgumentException: métadonnées Android.support.FILE_PROVIDER_PATHS manquantes

Cette autorité est-elle correcte? Si je dois utiliser mon propre fournisseur pour partager le fichier public, quel chemin puis-je spécifier dans mes métadonnées FILE_PROVIDER_PATHS? Tous les options que je peux trouver sont pour les répertoires privés.

18
Paul

TL: DR <external-path name="MyAppFolder" path="." /> fonctionne, mais est-ce sûr?

Vous devrez créer votre propre FileProvider. Cela vaut la peine de lire son documentation , mais voici un bref aperçu.

Comme @CommonsWare l'a mentionné, vous devrez remplacer MediaStore.AUTHORITY de votre exemple avec "com.example.fileprovider", qui est l'autorité que vous définirez dans votre manifeste comme ça ...

Dans le <application> tag dans le AndroidManifest, saisissez ce qui suit:

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

L'exception que vous obtenez est due au fait que ce n'est pas <meta-data> balise qui relie à un fichier xml avec tous les chemins d'accès que votre FileProvider est autorisé à toucher. Mais c'est ce qui se passe dans ce dossier que j'ai eu du mal avec moi-même.

Vers la fin du didacticiel que vous avez lié, à la fin de la section Enregistrer la photo en taille réelle , l'exemple donné par Google pour ce file_paths.xml est:

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

"my_images" serait remplacé par "MyAppFolder" dans votre exemple. Dans ce didacticiel, Google affirme

Le composant path correspond au chemin qui est retourné par getExternalFilesDir () lorsqu'il est appelé avec Environment.DIRECTORY_PICTURES. Assurez-vous de remplacer com.example.package.name par le nom de package réel de votre application.

... et le taper m'a fait comprendre que cela ne fait PAS référence au répertoire public d'images que vous et moi recherchons. Ce qui explique pourquoi l'utilisation de ce chemin produit cette exception:

Java.lang.IllegalArgumentException: impossible de trouver la racine configurée qui contient /storage/emulated/0/Pictures/MyAppFolder/{filename}.jpg

Je vais laisser cela ici pour référence future, et passer à répondre à votre grande question - quel chemin pouvez-vous spécifier pour permettre à d'autres applications d'interagir avec un fichier dans le répertoire d'images public créé par votre application ciblant l'API de niveau 24+?

Comme l'a dit @CommonsWare, "les détails du sous-répertoire souhaité varient selon le périphérique", vous ne pouvez donc pas déclarer le chemin d'accès au répertoire public des images, mais j'ai trouvé un moyen qui fonctionne.

<external-path name="MyAppFolder" path="." /> permettra à votre FileProvider de donner un URI de contenu à d'autres applications (comme la caméra), qui pourront ensuite lire et écrire dans le fichier dans lequel il se résout.

S'il y a des dangers ou des inconvénients à cela, j'aimerais les entendre.

12
pumpkinpie65

Voici ce que j'utilise dans mon annuaire public de l'application pour les images.

  1. Créez le fichier comme ceci:

    File theFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "YOUR_APP_NAME" + File.separator + "YOUR_FILE_NAME");
    
  2. Spécifiez le chemin suivant dans les métadonnées du fournisseur de fichiers:

    <paths>
        <external-path name="secure_name" path="Pictures/YOUR_APP_NAME" />
    </paths>
    

"secure_name" est utilisé pour masquer le nom du sous-répertoire ("YOUR_APP_NAME" dans ce cas) que vous partagez pour des raisons de sécurité.

"Images" correspond à Environment.DIRECTORY_PICTURES.

  1. Obtenez Uri pour envoyer avec Intent:

    final Uri theUri = FileProvider.getUriForFile(activity,
                        getString(R.string.fileprovider_authorities),
                        theFile);
    theIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
    theIntent.putExtra(MediaStore.EXTRA_OUTPUT, theUri);
    
5
Stan Mots