web-dev-qa-db-fra.com

Une réponse finale sur la façon d'obtenir des données Exif à partir de l'URI

Ce sujet a été abordé dans de nombreuses questions ici, avec des résultats principalement différents et, en raison des modifications de l'API et des différents types d'URI, aucune réponse définitive .

Je n'ai pas de réponse moi-même, mais parlons-en. ExifInterface a un seul constructeur qui accepte un filePath. Cela en soi est ennuyeux, car il est maintenant déconseillé de se fier aux chemins - vous devriez plutôt utiliser Uris et ContentResolver. D'ACCORD.

Notre Uri nommé uri peut être récupéré à partir de l'intention dans onActivityResult (si vous choisissez l'image dans la galerie avec ACTION_GET_CONTENT) Ou peut être un Uri que nous avions précédemment (si vous choisissez l'image de l'appareil photo et appelez intent.putExtra(MediaStore.EXTRA_OUTPUT, uri)).

API <19

Notre uri peut avoir deux schémas différents:

  • Les uris provenant de caméras auront généralement un schéma file://. Celles-ci sont assez faciles à traiter, car elles tiennent le chemin. Vous pouvez appeler new ExifInterface(uri.getPath()) et vous avez terminé.
  • Les uris provenant de galeries ou d'autres fournisseurs de contenu ont généralement une interface content://. Personnellement, je ne sais pas de quoi il s'agit, mais ça me rend fou.

Ce deuxième cas, pour autant que je sache, doit être traité avec un ContentResolver que vous pouvez obtenir avec Context.getContentResolver(). Les éléments suivants fonctionnent avec toutes les applications que j'ai testées, dans tous les cas:

public static ExifInterface getPictureData(Context context, Uri uri) {
    String[] uriParts = uri.toString().split(":");
    String path = null;

    if (uriParts[0].equals("content")) {
        // we can use ContentResolver.
        // let’s query the DATA column which holds the path
        String col = MediaStore.Images.ImageColumns.DATA;
        Cursor c = context.getContentResolver().query(uri,
                new String[]{col},
                null, null, null);

        if (c != null && c.moveToFirst()) {
            path = c.getString(c.getColumnIndex(col));
            c.close();
            return new ExifInterface(path);
        }

    } else if (uriParts[0].equals("file")) {
        // it's easy to get the path
        path = uri.getEncodedPath();
        return new ExifInterface(path);
    }
    return null;
}

API19 +

Mes problèmes surviennent à partir de KitKat avec les URI content://. KitKat présente le Storage Access Framework (Voir ici ) avec une nouvelle intention, ACTION_OPEN_DOCUMENT, Et un sélecteur de plateforme. Cependant, il est dit que

Sur Android 4.4 et supérieur, vous avez la possibilité supplémentaire d'utiliser l'intention ACTION_OPEN_DOCUMENT, qui affiche une interface utilisateur de sélecteur contrôlée par le système qui permet à l'utilisateur de parcourir tous les fichiers que d'autres applications ont mis à disposition. À partir de cette interface utilisateur unique, l'utilisateur peut choisir un fichier à partir de n'importe quelle application prise en charge.

ACTION_OPEN_DOCUMENT n'est pas destiné à remplacer ACTION_GET_CONTENT. Celui que vous devez utiliser dépend des besoins de votre application.

Donc, pour que cela reste très simple, disons que nous sommes d'accord avec l'ancien ACTION_GET_CONTENT: Il déclenchera une boîte de dialogue de sélection dans laquelle vous pourrez choisir une application de galerie.

Cependant, l'approche du contenu ne fonctionne plus. Parfois, cela fonctionne sur KitKat, mais ne fonctionne jamais sur Lollipop, par exemple. Je ne sais pas exactement ce qui a changé.

J'ai cherché et essayé beaucoup; une autre approche adoptée spécifiquement pour KitKat est la suivante:

String wholeId = DocumentsContract.getDocumentId(uri);
String[] parts = wholeId.split(“:”);
String numberId = parts[1];

Cursor c = context.getContentResolver().query(
    // why external and not internal ?
    MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
    new String[]{ col },
    MediaStore.Images.Media._ID + “=?”,
    new String[]{ numberId },
    null);

Cela fonctionne parfois, mais pas d'autres. Plus précisément, cela fonctionne lorsque wholeId est quelque chose comme image:2839, Mais se casse évidemment lorsque wholeId est simplement un nombre.

Vous pouvez essayer ceci en utilisant le sélecteur de système (c.-à-d. Tirer la galerie avec ACTION_OPEN_DOCUMENT): Si vous choisissez une image dans "Récents", cela fonctionne; si vous choisissez une image dans "Téléchargements", elle se casse.

Alors comment faire?!

La réponse immédiate est Vous ne le faites pas , vous ne trouvez pas les chemins de fichiers à partir des uris de contenu dans la nouvelle version du système d'exploitation. On pourrait dire que tous les contenus ne pointent pas vers des images ou même des fichiers.

C'est tout à fait correct pour moi, et au début, j'ai travaillé pour éviter cela. Mais alors, Comment sommes-nous censés utiliser la classe ExifInterface si nous ne devons pas utiliser de chemins?

Je ne comprends pas comment les applications modernes procèdent de la sorte. Trouver l'orientation et les métadonnées est un problème auquel vous êtes immédiatement confronté et ContentResolver ne propose aucune API dans ce sens. Vous avez ContentResolver.openFileDescriptor() et des trucs similaires, mais pas d'API pour lire les métadonnées (qui sont vraiment dans ce fichier). Il peut y avoir des bibliothèques externes qui lisent Exif des trucs à partir d'un flux, mais je me pose des questions sur la manière commune/plate-forme de résoudre ce problème.

J'ai recherché un code similaire dans les applications open source de Google, mais je n'ai rien trouvé.

34
natario

Dans tous les cas, ce qui suit fonctionne avec toutes les applications que j'ai testées:

Cela ne fonctionnera que si le Uri se trouve être quelque chose venant du MediaStore. Il échouera si le Uri vient d’autre chose.

La réponse immédiate est: vous ne le faites pas, vous ne trouvez pas les chemins de fichiers à partir des uris de contenu dans la nouvelle version du système d'exploitation. On pourrait dire que tous les contenus ne pointent pas vers des images ou même des fichiers.

Correct. Je l'ai souligné à plusieurs reprises, comme ici .

Comment sommes-nous censés utiliser la classe ExifInterface si nous ne devons pas utiliser de chemins?

Non. Utilisez un autre code pour obtenir les en-têtes EXIF.

Il peut y avoir des bibliothèques externes qui lisent des fichiers Exif à partir d'un flux, mais je me pose des questions sur la manière commune/de résoudre ce problème.

Utilisez des bibliothèques externes.

J'ai recherché un code similaire dans les applications open source de Google, mais je n'ai rien trouvé.

Vous en trouverez dans l'application Mms .

9
CommonsWare

Pour développer la réponse de alex.dorokhov avec un exemple de code. La bibliothèque de support est une excellente façon de procéder.

build.gradle

dependencies {
...    
compile "com.Android.support:exifinterface:25.0.1"
...
}

Exemple de code:

import Android.support.media.ExifInterface;
...
try (InputStream inputStream = context.getContentResolver().openInputStream(uri)) {
      ExifInterface exif = new ExifInterface(inputStream);
      int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
    } catch (IOException e) {
      e.printStackTrace();
    }

La raison pour laquelle j'ai dû le faire de cette façon une fois que nous avons commencé à cibler l'api 25 (peut-être un problème sur 24+ également) mais que nous continuons à prendre en charge l'api 19, sur Android 7 notre application se bloquerait si je transmis dans un URI à la caméra qui faisait simplement référence à un fichier. J'ai donc dû créer un URI pour passer à l'intention de la caméra comme celle-ci.

FileProvider.getUriForFile(context, context.getApplicationContext().getPackageName() + ".fileprovider", tempFile);

Le problème est que ce fichier n'est pas possible de transformer l'URI en un vrai chemin de fichier (autre que de conserver le chemin du fichier temporaire).

42
startoftext

Obtenir EXIF ​​à partir d'un URI de contenu (un InputStream en fait) est maintenant disponible dans la bibliothèque de support. Voir: https://Android-developers.googleblog.com/2016/12/introducing-the-exifinterface-support-library.html

10
alex.dorokhov