209 votes

Obtenir un chemin réel à partir d'un URI, nouveau cadre d'accès au stockage d'Android KitKat

Avant l'accès à la nouvelle galerie dans Android 4.4 (KitKat) J'ai obtenu mon vrai chemin sur la carte SD avec cette méthode :

public String getPath(Uri uri) {
   String[] projection = { MediaStore.Images.Media.DATA };
   Cursor cursor = managedQuery(uri, projection, null, null, null);
   startManagingCursor(cursor);
   int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
   cursor.moveToFirst();
 return cursor.getString(column_index);
}

Maintenant, l'Intent.ACTION_GET_CONTENT renvoie des données différentes :

Avant :

content://media/external/images/media/62

Maintenant :

content://com.android.providers.media.documents/document/image:62

Comment pourrais-je obtenir le chemin réel sur la carte SD ?

2 votes

Je pense que vous pouvez trouver la solution ici stackoverflow.com/questions/19834842/

1 votes

Avez-vous vraiment besoin d'un chemin ? Peut-être voulez-vous simplement obtenir un objet Bitmap à partir d'un URI ? Si c'est le cas, j'ai une solution pour vous.

6 votes

Oui, j'ai vraiment besoin d'un chemin et je voudrais l'obtenir à partir du nouveau cadre d'accès au stockage. Ma solution provisoire a été d'éviter le nouvel accès au stockage. J'ai remplacé mon Intent.ACTION_GET_CONTENT par Intent.ACTION_PICK.

523voto

Paul Burke Points 9869

Cela permettra d'obtenir le chemin d'accès au fichier à partir du MediaProvider, du DownloadsProvider et du ExternalStorageProvider, tout en revenant à la méthode non officielle du ContentProvider que vous mentionnez.

/**
 * Get a file path from a Uri. This will get the the path for Storage Access
 * Framework Documents, as well as the _data field for the MediaStore and
 * other file-based ContentProviders.
 *
 * @param context The context.
 * @param uri The Uri to query.
 * @author paulburke
 */
public static String getPath(final Context context, final Uri uri) {

    final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;

    // DocumentProvider
    if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
        // ExternalStorageProvider
        if (isExternalStorageDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];

            if ("primary".equalsIgnoreCase(type)) {
                return Environment.getExternalStorageDirectory() + "/" + split[1];
            }

            // TODO handle non-primary volumes
        }
        // DownloadsProvider
        else if (isDownloadsDocument(uri)) {

            final String id = DocumentsContract.getDocumentId(uri);
            final Uri contentUri = ContentUris.withAppendedId(
                    Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));

            return getDataColumn(context, contentUri, null, null);
        }
        // MediaProvider
        else if (isMediaDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];

            Uri contentUri = null;
            if ("image".equals(type)) {
                contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            } else if ("video".equals(type)) {
                contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
            } else if ("audio".equals(type)) {
                contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
            }

            final String selection = "_id=?";
            final String[] selectionArgs = new String[] {
                    split[1]
            };

            return getDataColumn(context, contentUri, selection, selectionArgs);
        }
    }
    // MediaStore (and general)
    else if ("content".equalsIgnoreCase(uri.getScheme())) {
        return getDataColumn(context, uri, null, null);
    }
    // File
    else if ("file".equalsIgnoreCase(uri.getScheme())) {
        return uri.getPath();
    }

    return null;
}

/**
 * Get the value of the data column for this Uri. This is useful for
 * MediaStore Uris, and other file-based ContentProviders.
 *
 * @param context The context.
 * @param uri The Uri to query.
 * @param selection (Optional) Filter used in the query.
 * @param selectionArgs (Optional) Selection arguments used in the query.
 * @return The value of the _data column, which is typically a file path.
 */
public static String getDataColumn(Context context, Uri uri, String selection,
        String[] selectionArgs) {

    Cursor cursor = null;
    final String column = "_data";
    final String[] projection = {
            column
    };

    try {
        cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
                null);
        if (cursor != null && cursor.moveToFirst()) {
            final int column_index = cursor.getColumnIndexOrThrow(column);
            return cursor.getString(column_index);
        }
    } finally {
        if (cursor != null)
            cursor.close();
    }
    return null;
}

/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is ExternalStorageProvider.
 */
public static boolean isExternalStorageDocument(Uri uri) {
    return "com.android.externalstorage.documents".equals(uri.getAuthority());
}

/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is DownloadsProvider.
 */
public static boolean isDownloadsDocument(Uri uri) {
    return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}

/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is MediaProvider.
 */
public static boolean isMediaDocument(Uri uri) {
    return "com.android.providers.media.documents".equals(uri.getAuthority());
}

Ils sont tirés de ma bibliothèque open source, aFileChooser .

1 votes

Cela fonctionne ! Merci beaucoup !

2 votes

Juste une note : pour les fournisseurs qui nécessitent un réseau, le code renvoie null. Testé avec Google Drive et Picasa. Ma solution consiste à copier le fichier dans le répertoire de cache local.

0 votes

Monsieur, vous êtes mon sauveur aujourd'hui !

122voto

Vikram Points 17845

Remarque : Cette réponse aborde une partie du problème. Pour une solution complète (sous la forme d'une bibliothèque), voir La réponse de Paul Burke .

Vous pouvez utiliser l'URI pour obtenir document id puis interrogez soit MediaStore.Images.Media.EXTERNAL_CONTENT_URI o MediaStore.Images.Media.INTERNAL_CONTENT_URI (selon la situation de la carte SD).

Pour obtenir l'identifiant du document :

// Will return "image:x*"
String wholeID = DocumentsContract.getDocumentId(uriThatYouCurrentlyHave);

// Split at colon, use second item in the array
String id = wholeID.split(":")[1];

String[] column = { MediaStore.Images.Media.DATA };     

// where id is equal to             
String sel = MediaStore.Images.Media._ID + "=?";

Cursor cursor = getContentResolver().
                          query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 
                          column, sel, new String[]{ id }, null);

String filePath = "";

int columnIndex = cursor.getColumnIndex(column[0]);

if (cursor.moveToFirst()) {
    filePath = cursor.getString(columnIndex);
}   

cursor.close();

Référence : Je ne parviens pas à trouver le message dont cette solution est tirée. Je voulais demander au posteur original de contribuer ici. Je chercherai encore ce soir.

0 votes

Cela vous donnera simplement l'ID unique dans DocumentsProvider, qui n'a rien à voir avec l'ID dans MediaStore. Si vous trouvez un ID correspondant dans MediaStore, ce n'est certainement pas l'image correspondante que DocumentsProvider a retournée dans son URI

7 votes

@Magnus Merci de ne pas avoir essayé le code ci-dessus, et de partager votre opinion malgré tout. ...that has nothing to do with the ID in MediaStore. If you somehow find a matching ID in MediaStore it's **most certainly** not the corresponding image that DocumentsProvider returned in its URI . S'il existait une documentation officielle étayant ma réponse, je l'aurais référencée. Mais tout ce que je peux dire, c'est que le code ci-dessus a produit le résultat souhaité les quelques fois où je l'ai testé. Il aurait été courtois de votre part d'essayer la solution avant de la considérer comme erronée.

1 votes

"Chaque backend de stockage fait référence aux fichiers et répertoires individuels en les référençant avec un COLUMN_DOCUMENT_ID unique. Les ID de document doivent être uniques et ne pas changer une fois émis, car ils sont utilisés pour les attributions d'URI persistantes lors des redémarrages du dispositif", extrait de Cadre d'accès au stockage c'est l'identification que vous obtenez de DocumentsContract.getDocumentId comme vous pouvez le constater, cela n'a rien à voir avec MediaStore, que je sois courtois ou non.

75voto

bluebrain Points 862

La réponse ci-dessous est écrite par https://stackoverflow.com/users/3082682/cvizv sur une page qui n'existe plus, comme il n'a pas assez de rep pour répondre à une question, je la poste. Aucun crédit de ma part.

public String getImagePath(Uri uri){
   Cursor cursor = getContentResolver().query(uri, null, null, null, null);
   cursor.moveToFirst();
   String document_id = cursor.getString(0);
   document_id = document_id.substring(document_id.lastIndexOf(":")+1);
   cursor.close();

   cursor = getContentResolver().query( 
   android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
   null, MediaStore.Images.Media._ID + " = ? ", new String[]{document_id}, null);
   cursor.moveToFirst();
   String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
   cursor.close();

   return path;
}

Edit : Il y a un flux sur le code ; si le dispositif a plus d'un stockage externe (sdcard externe, usb externe etc.), le code ci-dessus ne fonctionnera pas les stockages non primaires.

1 votes

C'est la bonne réponse pour obtenir le chemin de l'image choisie dans la galerie, merci beaucoup. J'ai essayé beaucoup de solutions mais la vôtre fonctionne. Auparavant, mon code fonctionnait en utilisant managedQuery mais j'ai changé de niveau d'api et managedQuery api est déprécié. Donc je dois changer le code. J'ai cherché des nombres d'essais de solution mais rien ne fonctionne, votre solution fonctionne parfaitement bien. Merci encore une fois. Si je pouvais donner deux points de plus, je le ferais certainement, mais le système ne le permet pas.

0 votes

Tous les lecteurs de ce problème, il suffit d'utiliser cette solution qui fonctionne parfaitement !!! C'est la seule solution que j'ai trouvé qui fonctionne sur Android L et Android M.

0 votes

Il donne CursorIndexOutOfBoundsException : Index 0 demandé, avec une taille de 0

28voto

CommonsWare Points 402670

Avant le nouvel accès à la galerie dans KitKat, j'ai obtenu mon vrai chemin dans la carte SD avec cette méthode.

Ce n'était jamais fiable. Il n'y a aucune exigence que le Uri que vous êtes de retour d'un ACTION_GET_CONTENT o ACTION_PICK doit être indexée par le MediaStore ou doit même représenter un fichier sur le système de fichiers. Le site Uri pourrait, par exemple, représenter un flux, où un fichier crypté est décrypté pour vous à la volée.

Comment pourrais-je réussir à obtenir le vrai chemin dans sdcard ?

Il n'est pas nécessaire qu'il y ait un fichier correspondant à l'adresse de l'utilisateur. Uri .

Oui, j'ai vraiment besoin d'un chemin

Copiez ensuite le fichier du flux vers votre propre fichier temporaire, et utilisez-le. Mieux encore, utilisez directement le flux et évitez le fichier temporaire.

J'ai remplacé mon Intent.ACTION_GET_CONTENT par Intent.ACTION_PICK.

Cela n'aidera pas votre situation. Il n'est pas nécessaire qu'un ACTION_PICK être pour un Uri qui a un fichier sur le système de fichiers que tu peux dériver par magie.

0 votes

En général, nous utilisons le chemin pour obtenir les balises Exif de l'image, par exemple pour déterminer si l'image doit être tournée pour être affichée. En copiant le flux vers un nouveau fichier, les balises Exif ne sont-elles pas perdues ? J'ai essayé d'écrire le bitmap depuis l'inputStream à travers un FileOutputStream, mais je n'ai pas pu récupérer les balises exif originales.

2 votes

@l : "En copiant le flux vers un nouveau fichier, les balises exif ne sont-elles pas perdues ?" -- ils ne devraient pas l'être. Le décodage d'une image vers un fichier Bitmap Mais vous perdrez les balises EXIF. Tant que vous le gardez en tant que byte[] Cependant, si vous êtes en train d'essayer de modifier ces octets, je m'attends à ce que les balises EXIF survivent.

0 votes

@CommonsWare Ok, donc vous suggérez de travailler avec les URIs au lieu d'essayer d'obtenir le chemin du fichier. Mais maintenant, avec un format d'URI différent, cela ne fonctionnera pas. Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(uri, "video/*"); startActivity(intent);

8voto

Magnus Points 896

Cette réponse est basée sur votre description quelque peu vague. Je suppose que vous avez tiré une intention avec une action : Intent.ACTION_GET_CONTENT

Et maintenant vous avez content://com.android.providers.media.documents/document/image:62 au lieu de l'URI du fournisseur de médias précédent, correct ?

Sous Android 4.4 (KitKat), la nouvelle activité DocumentsActivity s'ouvre lorsqu'une page d'accueil est ouverte. Intent.ACTION_GET_CONTENT est déclenché, ce qui conduit à une vue en grille (ou en liste) où vous pouvez choisir une image, ce qui renverra les URI suivants au contexte d'appel (exemple) : content://com.android.providers.media.documents/document/image:62 (il s'agit des URI du nouveau fournisseur de documents, qui s'abstrait des données sous-jacentes en fournissant des URI de fournisseur de documents génériques aux clients).

Vous pouvez cependant accéder à la fois à la galerie et aux autres activités en répondant à Intent.ACTION_GET_CONTENT en utilisant le tiroir de l'activité Documents (faites glisser de gauche à droite et vous verrez apparaître une interface de tiroir avec une galerie à choisir). Tout comme avant KitKat.

Si vous voulez toujours choisir la classe DocumentsActivity et que vous avez besoin de l'URI du fichier, vous devriez être en mesure de faire la requête suivante (attention, c'est un peu compliqué !) (avec contentresolver) : content://com.android.providers.media.documents/document/image:62 URI et lire la valeur _display_name du curseur. Il s'agit d'un nom quelque peu unique (juste le nom de fichier sur les fichiers locaux) et vous pouvez l'utiliser dans une sélection (lors d'une requête) à mediaprovider pour obtenir la ligne correcte correspondant à cette sélection, à partir de laquelle vous pouvez également récupérer l'URI du fichier.

Les méthodes recommandées pour accéder au fournisseur de documents peuvent être trouvées ici (obtenir un flux d'entrée ou un descripteur de fichier pour lire un fichier/bitmap) :

Exemples d'utilisation de documentprovider

2 votes

Google vient de mettre à jour la documentation, super. Désolé pour ma description vague. Je veux connaître le chemin réel à partir de la nouvelle interface de sélection pour effectuer un processus hors ligne. Je sais comment obtenir le nom du fichier mais pas le chemin complet, je voudrais obtenir ce chemin /storage/sdcard0/DCIM/Camera/IMG_20131118_153817_119.jpg à partir de ces données content://com.Android.providers.media.documents/document/image:62

0 votes

Je pense que vous n'avez pas de chance alors. Si vous n'êtes pas prêt à changer pour choisir l'intention avec le type mime image/*, utilisez alternativement la méthode suggérée ci-dessus (hack). Depuis que google a pris possession de Intent.ACTION_GET_CONTENT avec cette nouvelle DocumentActivity, votre seule option est d'utiliser les méthodes décrites dans le lien ci-dessus. Ils semblent penser que les méthodes actuelles sont suffisantes parce que le fournisseur de documents englobe également des services en nuage tels que Drive, etc., alors la seule option est le retour du flux d'entrée du fournisseur de documents (il n'y a pas de fichier dans ce cas).

0 votes

Regardez ce lien : Colonnes dans le fournisseur de documents Ce sont celles qui sont disponibles une fois que vous accédez au curseur de ce fournisseur de documents, d'où l'absence d'option d'uri de fichier :(

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X