Android Gallery sur Android 4.4 (KitKat) renvoie un URI différent pour Intent.ACTION_GET_CONTENT


214

Avant KitKat (ou avant la nouvelle galerie) le Intent.ACTION_GET_CONTENTretourne un URI comme celui-ci

contenu: // média / externe / images / média / 3951.

L'utilisation de ContentResolveret la requête pour ont MediaStore.Images.Media.DATArenvoyé l'URL du fichier.

Dans KitKat, cependant, la galerie renvoie un URI (via "Last") comme ceci:

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

Comment dois-je gérer cela?


21
Hors de la manchette, je trouverais des moyens d'utiliser le contenu qui ne nécessite pas un accès direct au fichier. Par exemple, cela Uridevrait être ouvrable en tant que flux via ContentResolver. Cela fait longtemps que je suis nerveux à propos des applications qui supposent qu'un content:// Urifichier qui représente un fichier peut toujours être converti en un fichier File.
CommonsWare

1
@ CommonsWare, si je veux enregistrer un chemin d'image dans sqlite db afin de pouvoir l'ouvrir plus tard, dois-je enregistrer l'URI ou le chemin de fichier absolu?
Snake

2
@CommonsWare Je suis d'accord avec votre nervosité. :-) Cependant, je dois pouvoir passer un nom de fichier (pour une image) en code natif. Une solution consiste à copier les données obtenues à l'aide d'un InputStreamsur ContentResolverun vers un emplacement prédéterminé afin qu'il ait un nom de fichier connu. Cependant, cela me semble inutile. D'autres suggestions?
darrenp

2
@darrenp: Ummm ..., réécrire le code natif pour travailler avec un InputStreamover JNI? Il n'y a malheureusement pas beaucoup d'options pour vous.
CommonsWare

1
C'est utile de savoir. Merci pour votre réponse. J'ai depuis découvert que nous transmettons maintenant l'image au C ++ en mémoire plutôt que via un fichier, nous pouvons donc maintenant utiliser un InputStreamau lieu d'un fichier (ce qui est génial). Seule la lecture des balises EXIF ​​est légèrement délicate et nécessite la bibliothèque de Drew Noakes . Merci beaucoup pour vos commentaires.
darrenp

Réponses:


108

Essaye ça:

if (Build.VERSION.SDK_INT <19){
    Intent intent = new Intent(); 
    intent.setType("image/jpeg");
    intent.setAction(Intent.ACTION_GET_CONTENT);
    startActivityForResult(Intent.createChooser(intent, getResources().getString(R.string.select_picture)),GALLERY_INTENT_CALLED);
} else {
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    intent.setType("image/jpeg");
    startActivityForResult(intent, GALLERY_KITKAT_INTENT_CALLED);
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (resultCode != Activity.RESULT_OK) return;
    if (null == data) return;
    Uri originalUri = null;
    if (requestCode == GALLERY_INTENT_CALLED) {
        originalUri = data.getData();
    } else if (requestCode == GALLERY_KITKAT_INTENT_CALLED) {
        originalUri = data.getData();
        final int takeFlags = data.getFlags()
                & (Intent.FLAG_GRANT_READ_URI_PERMISSION
                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        // Check for the freshest data.
        getContentResolver().takePersistableUriPermission(originalUri, takeFlags);
    }

    loadSomeStreamAsynkTask(originalUri);

}

Besoin probablement

@SuppressLint ("NewApi")

pour

takePersistableUriPermission


1
Pourriez-vous expliquer ce que fait le code KitKat? Cela nécessite-t-il une nouvelle autorisation? Le code pré KitKat fonctionne aussi pour moi sur KitKat. Alors pourquoi choisirais-je d'utiliser un code différent pour KitKat? Merci.
Michael Greifeneder

67
il semble que nous ne pouvons pas obtenir le chemin des nouveaux uri sdks. C'est aussi dommage que Google ait fait ce genre de changement sans documentation et annonce appropriées.
user65721

1
Pourriez-vous expliquer comment obtenir l'URL du fichier? Je voudrais obtenir le vrai chemin en sdcard. Par exemple, s'il s'agit d'une image, j'aimerais obtenir ce chemin /storage/sdcard0/DCIM/Camera/IMG_20131118_153817_119.jpg au lieu du document Uri.
Álvaro

4
Sur la base des documents KitKat ( developer.android.com/about/versions/… ), ce n'est peut-être pas ce dont l'OP a besoin à moins qu'il n'ait l'intention d'utiliser / de modifier des documents appartenant à l'autre ou aux autres applications. Si l'OP veut une copie ou faire les choses d'une manière cohérente avec les anciennes versions, la réponse de @voytez serait plus appropriée.
Colin M.

8
Ça ne marche pas pour moi. J'obtiens l'exception suivante (en stock 4.4.2): E / AndroidRuntime (29204): causée par: java.lang.SecurityException: les drapeaux demandés 0x1, mais seulement 0x0 sont autorisés
Russell Stewart

177

Cela ne nécessite aucune autorisation spéciale et fonctionne avec Storage Access Framework, ainsi qu'avec le ContentProvidermodèle non officiel (chemin de fichier dans_data champ).

/**
 * 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 the remote address
        if (isGooglePhotosUri(uri))
            return uri.getLastPathSegment();

        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 index = cursor.getColumnIndexOrThrow(column);
            return cursor.getString(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());
}

/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is Google Photos.
 */
public static boolean isGooglePhotosUri(Uri uri) {
    return "com.google.android.apps.photos.content".equals(uri.getAuthority());
}

Voir une version à jour de cette méthode ici .


2
Cela a fonctionné de manière fantastique sur une interface utilisateur 4.4 Nexus 5 Documents et certains autres appareils KitKat préférés utilisant les applications de galerie standard, merci Paul!
Josh

1
Merci pour cela, ça m'a pris beaucoup de temps pour en arriver là avec sdk 19 !! Mon problème est que mon appareil utilise Google Drive comme navigateur de fichiers. Si le fichier se trouve sur le chemin de l'image du périphérique, tout va bien, mais si le fichier se trouve sur le lecteur, il ne s'ouvre pas. Peut-être que je dois juste regarder le traitement des images d'ouverture à partir de Google Drive. La chose est que mon application est écrite pour utiliser le chemin du fichier et obtenir l'image en utilisant l'insampling ...
RuAware

2
@RuAware Lorsque vous sélectionnez un fichier Drive, il renvoie Authority: com.google.android.apps.docs.storageet Segments: [document, acc=1;doc=667]. Je ne suis pas sûr, mais supposez que la docvaleur est l' UriID que vous pouvez interroger. Vous aurez probablement besoin d'autorisations pour la configuration, comme indiqué dans "Autoriser votre application sur Android" ici: developers.google.com/drive/integrate-android-ui . Veuillez mettre à jour ici si vous le comprenez.
Paul Burke

30
c'est absolument horrible! vous ne devez pas continuer à propager du code qui "triche" comme ça. il ne prend en charge que les applications source pour lesquelles vous connaissez le modèle, et l'intérêt du modèle de fournisseur de documents est de prendre en charge des sources arbitraires
j__m

2
Le _datane fonctionnerait pas quand ContentProvider ne le supporte pas. Il est recommandé de suivre les instructions @CommonsWare et de ne plus utiliser le chemin d'accès complet au fichier, car il pourrait s'agir d'un fichier dans le cloud Dropbox au lieu d'un vrai fichier.
soshial

67

J'ai eu le même problème, j'ai essayé la solution ci-dessus, mais bien que cela fonctionne généralement, pour une raison quelconque, j'obtenais un refus d'autorisation sur le fournisseur de contenu Uri pour certaines images bien que la android.permission.MANAGE_DOCUMENTSpermission ait été ajoutée correctement.

Quoi qu'il en soit, j'ai trouvé une autre solution qui consiste à forcer l'ouverture de la galerie d'images au lieu de la vue des documents KITKAT avec:

// KITKAT

i = new Intent(Intent.ACTION_PICK,android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
    startActivityForResult(i, CHOOSE_IMAGE_REQUEST);

puis chargez l'image:

Uri selectedImageURI = data.getData();
input = c.getContentResolver().openInputStream(selectedImageURI);
                BitmapFactory.decodeStream(input , null, opts);

ÉDITER

ACTION_OPEN_DOCUMENT peut nécessiter que vous persistiez les indicateurs d'autorisations, etc. et entraîne généralement des exceptions de sécurité ...

Une autre solution consiste à utiliser le ACTION_GET_CONTENTcombiné avec c.getContentResolver().openInputStream(selectedImageURI)lequel fonctionnera à la fois sur pré-KK et KK. Kitkat utilisera alors la nouvelle vue des documents et cette solution fonctionnera avec toutes les applications comme Photos, Galerie, Explorateur de fichiers, Dropbox, Google Drive, etc.) mais n'oubliez pas que lorsque vous utilisez cette solution, vous devez créer une image dans votre onActivityResult()et la stocker sur Carte SD par exemple. La recréation de cette image à partir de l'URI enregistré lors du prochain lancement de l'application lèverait une exception de sécurité sur le résolveur de contenu même lorsque vous ajoutez des indicateurs d'autorisation comme décrit dans les documents de l'API Google (c'est ce qui s'est produit lorsque j'ai fait des tests)

De plus, les consignes relatives aux API pour développeurs Android suggèrent:

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

Utilisez ACTION_GET_CONTENT si vous souhaitez que votre application lise / importe simplement les données. Avec cette approche, l'application importe une copie des données, comme un fichier image.

Utilisez ACTION_OPEN_DOCUMENT si vous souhaitez que votre application dispose d'un accès permanent et à long terme aux documents appartenant à un fournisseur de documents. Un exemple serait une application de retouche photo qui permet aux utilisateurs de modifier des images stockées dans un fournisseur de documents.


1
Cette réponse contenait les bonnes informations pour mes besoins. L'utilisation conditionnelle de ACTION_PICK et EXTERNAL_CONTENT_URI sur KitKat a fourni la même possibilité d'obtenir des métadonnées sur les images dans la galerie via ContentResolver, comme c'est possible sur les anciennes versions en utilisant simplement ACTION_GET_CONTENT.
Colin M.

@voytez, cet URI est-il retourné via votre message converti en chemin réel complet de l'image?
Snake

Je crois que oui, cela devrait fonctionner comme avant KitKat car ce code force l'ouverture de la galerie d'images au lieu de la vue des documents KK. Mais si vous avez l'intention de l'utiliser pour créer une image, cette solution est meilleure car la conversion en chemin réel est une étape supplémentaire inutile.
voytez le

4
A travaillé pour moi aussi, au lieu de Intent.ACTION_GET_CONTENT. Quoi qu'il en soit, j'ai gardé le Intent.createChooser()wrapper sur le nouveau Intent, pour permettre à l'utilisateur de choisir l'application pour la navigation, et j'ai travaillé comme prévu. Quelqu'un peut-il voir les inconvénients de cette solution?
lorenzo-s

1
Pour tous ceux qui se demandent la citation vient de developer.android.com/guide/topics/providers/…
snark

38

Tout comme Commonsware l'a mentionné, vous ne devez pas supposer que le flux via lequel vous obtenez ContentResolverest convertible en fichier.

Ce que vous devez vraiment faire est d'ouvrir le InputStreamdepuis le ContentProvider, puis de créer un bitmap à partir de celui-ci. Et cela fonctionne aussi sur les versions 4.4 et antérieures, pas besoin de réflexion.

    //cxt -> current context

    InputStream input;
    Bitmap bmp;
    try {
        input = cxt.getContentResolver().openInputStream(fileUri);
        bmp = BitmapFactory.decodeStream(input);
    } catch (FileNotFoundException e1) {

    }

Bien sûr, si vous manipulez de grandes images, vous devez les charger avec les fichiers appropriés inSampleSize: http://developer.android.com/training/displaying-bitmaps/load-bitmap.html . Mais c'est un autre sujet.


Cela ne fonctionne pas pour moi avec un Nexus 4 exécutant Kitkat mais c'est avec un Galaxy S3 exécutant 4.1.2
Dan2552

@ Dan2552 quelle partie ne fonctionne pas? Avez-vous une exception?
Michał K

Il s'est avéré que j'utilisais le mauvais appel d'intention à la galerie. J'en utilisais un qui était pour tout type de documents, mais avec un filtre d'extension de fichier.
Dan2552

2
Quelle réponse magnifiquement simple, merci! Pour les autres qui suivent cette réponse, «cxt» fait référence au contexte actuel et sera généralement «ceci».
thomasforth

1
Cela signifie probablement que le fichier n'est tout simplement pas là. L'URI semble OK.
Michał K

33

Je pense que les réponses déjà publiées devraient amener les gens dans la bonne direction. Cependant, voici ce que j'ai fait pour le code hérité que je mettais à jour. Le code hérité utilisait l'URI de la galerie pour modifier puis enregistrer les images.

Avant 4.4 (et google drive), les URI ressemblaient à ceci: content: // media / external / images / media / 41

Comme indiqué dans la question, ils ressemblent plus souvent à ceci: content: //com.android.providers.media.documents/document/image: 3951

Comme j'avais besoin de pouvoir enregistrer des images et ne pas perturber le code déjà existant, je viens de copier l'URI de la galerie dans le dossier de données de l'application. Puis a créé un nouvel URI à partir du fichier image enregistré dans le dossier de données.

Voici l'idée:

Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
startActivityForResult(intent), CHOOSE_IMAGE_REQUEST);

public void onActivityResult(int requestCode, int resultCode, Intent data) {

    super.onActivityResult(requestCode, resultCode, data);

    File tempFile = new File(this.getFilesDir().getAbsolutePath(), "temp_image");

    //Copy URI contents into temporary file.
    try {
        tempFile.createNewFile();
        copyAndClose(this.getContentResolver().openInputStream(data.getData()),new FileOutputStream(tempFile));
    }
    catch (IOException e) {
        //Log Error
    }

    //Now fetch the new URI
    Uri newUri = Uri.fromFile(tempFile);

    /* Use new URI object just like you used to */
 }

Remarque - copyAndClose () ne fait que des E / S de fichier pour copier InputStream dans un FileOutputStream. Le code n'est pas affiché.


assez intelligent. J'ai aussi eu besoin du fichier
URI

tu es mon héros, c'est exactement ce dont j'avais besoin! fonctionne aussi très bien pour les fichiers Google Drive
mishkin

Pensez hors de la boîte, non? : D Ce code fonctionne exactement comme je m'y attendais.
WeirdElfB0y

2
poster le code pour copyAndClose, la réponse n'est pas complète
Bartek Pacia

24

Je voulais juste dire que cette réponse est brillante et que je l'utilise depuis longtemps sans problème. Mais il y a quelque temps, je suis tombé sur un problème avec lequel DownloadsProvider renvoie les URI au format content://com.android.providers.downloads.documents/document/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2Fdoc.pdfet donc l'application est bloquée NumberFormatExceptioncar il est impossible d'analyser ses segments d' URI aussi longtemps. Mais le raw:segment contient un uri direct qui peut être utilisé pour récupérer un fichier référencé. Je l'ai donc corrigé en remplaçant le isDownloadsDocument(uri) ifcontenu par ce qui suit:

final String id = DocumentsContract.getDocumentId(uri);
if (!TextUtils.isEmpty(id)) {
if (id.startsWith("raw:")) {
    return id.replaceFirst("raw:", "");
}
try {
    final Uri contentUri = ContentUris.withAppendedId(
            Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
    return getDataColumn(context, contentUri, null, null);
} catch (NumberFormatException e) {
    Log.e("FileUtils", "Downloads provider returned unexpected uri " + uri.toString(), e);
    return null;
}
}

2
Fonctionne parfaitement! Merci
mikemike396

8

J'ai combiné plusieurs réponses en une seule solution de travail qui résulte du chemin du fichier

Le type MIME n'est pas pertinent pour l'exemple.

            Intent intent;
            if(Build.VERSION.SDK_INT >= 19){
                intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
                intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false);
                intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
            }else{
                intent = new Intent(Intent.ACTION_GET_CONTENT);
            }
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            intent.setType("application/octet-stream");
            if(isAdded()){
                startActivityForResult(intent, RESULT_CODE);
            }

Résultat de la manipulation

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if( requestCode == RESULT_CODE && resultCode == Activity.RESULT_OK) {
        Uri uri = data.getData();
        if (uri != null && !uri.toString().isEmpty()) {
            if(Build.VERSION.SDK_INT >= 19){
                final int takeFlags = data.getFlags() & Intent.FLAG_GRANT_READ_URI_PERMISSION;
                //noinspection ResourceType
                getActivity().getContentResolver()
                        .takePersistableUriPermission(uri, takeFlags);
            }
            String filePath = FilePickUtils.getSmartFilePath(getActivity(), uri);
            // do what you need with it...
        }
    }
}

FilePickUtils

import android.annotation.SuppressLint;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract;
import android.provider.MediaStore;

public class FilePickUtils {
    private static String getPathDeprecated(Context ctx, Uri uri) {
        if( uri == null ) {
            return null;
        }
        String[] projection = { MediaStore.Images.Media.DATA };
        Cursor cursor = ctx.getContentResolver().query(uri, projection, null, null, null);
        if( cursor != null ){
            int column_index = cursor
                    .getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
            cursor.moveToFirst();
            return cursor.getString(column_index);
        }
        return uri.getPath();
    }

    public static String getSmartFilePath(Context ctx, Uri uri){

        if (Build.VERSION.SDK_INT < 19) {
            return getPathDeprecated(ctx, uri);
        }
        return  FilePickUtils.getPath(ctx, uri);
    }

    @SuppressLint("NewApi")
    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());
    }

}

je faisais face à un problème .... uri.getPath () retournait uri avec / external et il ne retournait pas le chemin. j'ai ajouté ceci sinon si ("content" .equalsIgnoreCase (uri.getScheme ())) bloc et cela fonctionne bien maintenant. pouvez-vous expliquer ce que cela fait
FaisalAhmed

filePath devient nulle .. En uri: Contenu: //com.android.externalstorage.documents/document/799B-1419%3AScreenshot%2FScreenshot_20181117_162826.png
Bhavesh Moradiya

7

Question

Comment obtenir un chemin de fichier réel à partir d'un URI

Répondre

À ma connaissance, nous n'avons pas besoin d'obtenir le chemin du fichier à partir d'un URI parce que dans la plupart des cas, nous pouvons directement utiliser l'URI pour faire notre travail (comme 1. obtenir le bitmap 2. Envoyer un fichier au serveur, etc. .)

1. Envoi au serveur

Nous pouvons envoyer directement le fichier au serveur en utilisant uniquement l'URI.

En utilisant l'URI, nous pouvons obtenir InputStream, que nous pouvons envoyer directement au serveur à l'aide de MultiPartEntity.

Exemple

/**
 * Used to form Multi Entity for a URI (URI pointing to some file, which we got from other application).
 *
 * @param uri     URI.
 * @param context Context.
 * @return Multi Part Entity.
 */
public MultipartEntity formMultiPartEntityForUri(final Uri uri, final Context context) {
    MultipartEntity multipartEntity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE, null, Charset.forName("UTF-8"));
    try {
        InputStream inputStream = mContext.getContentResolver().openInputStream(uri);
        if (inputStream != null) {
            ContentBody contentBody = new InputStreamBody(inputStream, getFileNameFromUri(uri, context));
            multipartEntity.addPart("[YOUR_KEY]", contentBody);
        }
    }
    catch (Exception exp) {
        Log.e("TAG", exp.getMessage());
    }
    return multipartEntity;
}

/**
 * Used to get a file name from a URI.
 *
 * @param uri     URI.
 * @param context Context.
 * @return File name from URI.
 */
public String getFileNameFromUri(final Uri uri, final Context context) {

    String fileName = null;
    if (uri != null) {
        // Get file name.
        // File Scheme.
        if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
            File file = new File(uri.getPath());
            fileName = file.getName();
        }
        // Content Scheme.
        else if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
            Cursor returnCursor =
                    context.getContentResolver().query(uri, null, null, null, null);
            if (returnCursor != null && returnCursor.moveToFirst()) {
                int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
                fileName = returnCursor.getString(nameIndex);
                returnCursor.close();
            }
        }
    }
    return fileName;
}

2. Obtenir un BitMap à partir d'un URI

Si l'URI pointe vers l'image, nous obtiendrons un bitmap, sinon null:

/**
 * Used to create bitmap for the given URI.
 * <p>
 * 1. Convert the given URI to bitmap.
 * 2. Calculate ratio (depending on bitmap size) on how much we need to subSample the original bitmap.
 * 3. Create bitmap bitmap depending on the ration from URI.
 * 4. Reference - http://stackoverflow.com/questions/3879992/how-to-get-bitmap-from-an-uri
 *
 * @param context       Context.
 * @param uri           URI to the file.
 * @param bitmapSize Bitmap size required in PX.
 * @return Bitmap bitmap created for the given URI.
 * @throws IOException
 */
public static Bitmap createBitmapFromUri(final Context context, Uri uri, final int bitmapSize) throws IOException {

    // 1. Convert the given URI to bitmap.
    InputStream input = context.getContentResolver().openInputStream(uri);
    BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();
    onlyBoundsOptions.inJustDecodeBounds = true;
    onlyBoundsOptions.inDither = true;//optional
    onlyBoundsOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//optional
    BitmapFactory.decodeStream(input, null, onlyBoundsOptions);
    input.close();
    if ((onlyBoundsOptions.outWidth == -1) || (onlyBoundsOptions.outHeight == -1)) {
        return null;
    }

    // 2. Calculate ratio.
    int originalSize = (onlyBoundsOptions.outHeight > onlyBoundsOptions.outWidth) ? onlyBoundsOptions.outHeight : onlyBoundsOptions.outWidth;
    double ratio = (originalSize > bitmapSize) ? (originalSize / bitmapSize) : 1.0;

    // 3. Create bitmap.
    BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
    bitmapOptions.inSampleSize = getPowerOfTwoForSampleRatio(ratio);
    bitmapOptions.inDither = true;//optional
    bitmapOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//optional
    input = context.getContentResolver().openInputStream(uri);
    Bitmap bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions);
    input.close();

    return bitmap;
}

/**
 * For Bitmap option inSampleSize - We need to give value in power of two.
 *
 * @param ratio Ratio to be rounded of to power of two.
 * @return Ratio rounded of to nearest power of two.
 */
private static int getPowerOfTwoForSampleRatio(final double ratio) {
    int k = Integer.highestOneBit((int) Math.floor(ratio));
    if (k == 0) return 1;
    else return k;
}

commentaires

  1. Android ne fournit aucune méthode pour obtenir le chemin du fichier à partir d'un URI, et dans la plupart des réponses ci-dessus, nous avons codé en dur certaines constantes, qui peuvent casser dans la version des fonctionnalités (désolé, je peux me tromper).
  2. Avant de passer directement à une solution pour obtenir le chemin d'accès au fichier à partir d'un URI, essayez de résoudre votre cas d'utilisation avec un URI et des méthodes par défaut Android.

Référence

  1. https://developer.android.com/guide/topics/providers/content-provider-basics.html
  2. https://developer.android.com/reference/android/content/ContentResolver.html
  3. https://hc.apache.org/httpcomponents-client-ga/httpmime/apidocs/org/apache/http/entity/mime/content/InputStreamBody.html

Je vous remercie. L'utilisation de l'URI et de ContentResolver de cette manière a considérablement simplifié mon application lorsque je traite des fichiers.
jacksonakj


3

Pour ceux qui utilisent toujours le code de @Paul Burke avec Android SDK version 23 et supérieure, si votre projet a rencontré l'erreur en disant qu'il vous manque EXTERNAL_PERMISSION, et vous êtes très sûr que vous avez déjà ajouté l'autorisation utilisateur dans votre fichier AndroidManifest.xml. C'est parce que vous pouvez dans Android API 23 ou supérieur et Google rendre nécessaire de garantir à nouveau l'autorisation pendant que vous effectuez l'action pour accéder au fichier lors de l'exécution.

Cela signifie: si votre version du SDK est de 23 ou plus, vous êtes invité à autoriser la lecture et l'écriture pendant que vous sélectionnez le fichier image et que vous souhaitez en connaître l'URI.

Et voici mon code, en plus de la solution de Paul Burke. J'ajoute ces codes et mon projet commence à bien fonctionner.

private static final int REQUEST_EXTERNAL_STORAGE = 1;
private static final String[] PERMISSINOS_STORAGE = {
    Manifest.permission.READ_EXTERNAL_STORAGE,
    Manifest.permission.WRITE_EXTERNAL_STORAGE
};

public static void verifyStoragePermissions(Activity activity) {
    int permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);

    if (permission != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(
                activity,
                PERMISSINOS_STORAGE,
                REQUEST_EXTERNAL_STORAGE
        );
    }
}

Et dans votre activité et fragment où vous demandez l'URI:

private void pickPhotoFromGallery() {

    CompatUtils.verifyStoragePermissions(this);
    Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
    intent.setType("image/*");
    // startActivityForResult(intent, REQUEST_PHOTO_LIBRARY);
    startActivityForResult(Intent.createChooser(intent, "选择照片"),
            REQUEST_PHOTO_LIBRARY);
}

Dans mon cas, CompatUtils.java est l'endroit où je définis la méthode verifyStoragePermissions (comme type statique pour que je puisse l'appeler dans une autre activité).

De plus, il devrait être plus judicieux de créer d'abord un état if pour voir si la version actuelle du SDK est supérieure à 23 ou non avant d'appeler la méthode verifyStoragePermissions.


2

C'est ce que je fais:

Uri selectedImageURI = data.getData();    imageFile = new File(getRealPathFromURI(selectedImageURI)); 

private String getRealPathFromURI(Uri contentURI) {
  Cursor cursor = getContentResolver().query(contentURI, null, null, null, null);
  if (cursor == null) { // Source is Dropbox or other similar local file path
      return contentURI.getPath();
      } else { 
      cursor.moveToFirst(); 
      int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA); 
      return cursor.getString(idx); 
  }
}

REMARQUE: la managedQuery()méthode est obsolète, donc je ne l'utilise pas.

Cette réponse est de m3n0R à la question android get real path par Uri.getPath () et je ne revendique aucun crédit. Je pensais juste que les gens qui n'avaient pas encore résolu ce problème pouvaient l'utiliser.


2
Ce n'est pas une réponse à la nouvelle application Gallery (strictement "Media Documents Provider") sur KitKat.
nagoya0

L'application que l'interrogateur appelle "Galerie" est probablement un nouveau sélecteur de fichiers sur kitkat. Pour info: addictivetips.com/android/…
nagoya0

Je fais la même chose et j'ai obtenu IndexOutOfBound sur Nexus 5X, Android 6 sur cette ligne:cursor.getString(idx);
Osify

1

Veuillez éviter d'utiliser la méthode takePersistableUriPermission car elle a déclenché une exception d'exécution pour moi. / ** * Sélectionnez dans la galerie. * /

public void selectFromGallery() {
    if (Build.VERSION.SDK_INT < AppConstants.KITKAT_API_VERSION) {

        Intent intent = new Intent(); 
        intent.setType("image/*");
        intent.setAction(Intent.ACTION_GET_CONTENT);
        ((Activity)mCalledContext).startActivityForResult(intent,AppConstants.GALLERY_INTENT_CALLED);

    } else {

        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.setType("image/*");
        ((Activity)mCalledContext).startActivityForResult(intent, AppConstants.GALLERY_AFTER_KITKAT_INTENT_CALLED);
    }
}

OnActivity pour que le résultat gère les données d'image:

@Override protected void onActivityResult (int requestCode, int resultCode, Intent data) {

    //gallery intent result handling before kit-kat version
    if(requestCode==AppConstants.GALLERY_INTENT_CALLED 
            && resultCode == RESULT_OK) {

        Uri selectedImage = data.getData();
        String[] filePathColumn = {MediaStore.Images.Media.DATA};
        Cursor cursor = getContentResolver().query(selectedImage,filePathColumn, null, null, null);
        cursor.moveToFirst();
        int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
        String filePath = cursor.getString(columnIndex);
        cursor.close();
        photoFile = new File(filePath);
        mImgCropping.startCropImage(photoFile,AppConstants.REQUEST_IMAGE_CROP);

    }
    //gallery intent result handling after kit-kat version
    else if (requestCode == AppConstants.GALLERY_AFTER_KITKAT_INTENT_CALLED 
            && resultCode == RESULT_OK) {

        Uri selectedImage = data.getData();
        InputStream input = null;
        OutputStream output = null;

        try {
            //converting the input stream into file to crop the 
            //selected image from sd-card.
            input = getApplicationContext().getContentResolver().openInputStream(selectedImage);
            try {
                photoFile = mImgCropping.createImageFile();
            } catch (IOException e) {
                e.printStackTrace();
            }catch(Exception e) {
                e.printStackTrace();
            }
            output = new FileOutputStream(photoFile);

            int read = 0;
            byte[] bytes = new byte[1024];

            while ((read = input.read(bytes)) != -1) {
                try {
                    output.write(bytes, 0, read);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }


    }

Où affichez-vous l'image dans le deuxième cas?
Quantum_VC

désolé, je n'ai pas ajouté cette ligne de code ailleurs si mImgCropping.startCropImage (photoFile, AppConstants.REQUEST_IMAGE_CROP); besoin à nouveau d'appeler la fonction imagecropping selon les besoins de mon projet
saranya

Quel type de fichier est photoFile et mImgCropping?
Philip BH

1

Si quelqu'un est intéressé, j'ai fait une version Kotlin fonctionnelle pour ACTION_GET_CONTENT:

var path: String = uri.path // uri = any content Uri
val databaseUri: Uri
val selection: String?
val selectionArgs: Array<String>?
if (path.contains("/document/image:")) { // files selected from "Documents"
    databaseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
    selection = "_id=?"
    selectionArgs = arrayOf(DocumentsContract.getDocumentId(uri).split(":")[1])
} else { // files selected from all other sources, especially on Samsung devices
    databaseUri = uri
    selection = null
    selectionArgs = null
}
try {
    val projection = arrayOf(MediaStore.Images.Media.DATA,
        MediaStore.Images.Media._ID,
        MediaStore.Images.Media.ORIENTATION,
        MediaStore.Images.Media.DATE_TAKEN) // some example data you can query
    val cursor = context.contentResolver.query(databaseUri,
        projection, selection, selectionArgs, null)
    if (cursor.moveToFirst()) {
        // do whatever you like with the data
    }
    cursor.close()
} catch (e: Exception) {
    Log.e(TAG, e.message, e)
}

Je veux juste un code de travail de kotlin. C'est du travail pour moi. merci
Harvi Sirja

1

J'ai essayé plusieurs des réponses ici, et je pense avoir une solution qui fonctionnera à chaque fois et gère également les autorisations.

Il est basé sur la solution intelligente de LEO. Cet article devrait contenir tout le code dont vous avez besoin pour que cela fonctionne, et il devrait fonctionner sur n'importe quel téléphone et version Android;)

Afin d'avoir la possibilité de choisir un fichier sur une carte SD, vous en aurez besoin dans votre manifeste:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Constantes:

private static final int PICK_IMAGE = 456; // Whatever number you like
public static final int MY_PERMISSIONS_REQUEST_READ_EXTERNAL = 28528; // Whatever number you like
public static final String FILE_TEMP_NAME = "temp_image"; // Whatever file name you like

Vérifiez l'autorisation et lancezImagePick si possible

if (ContextCompat.checkSelfPermission(getThis(),
        Manifest.permission.READ_EXTERNAL_STORAGE)
        != PackageManager.PERMISSION_GRANTED) {

    ActivityCompat.requestPermissions(getThis(),
            new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
            MY_PERMISSIONS_REQUEST_READ_EXTERNAL);
}
else {
    launchImagePick();
}

Réponse d'autorisation

@Override
public void onRequestPermissionsResult(int requestCode,
                                       @NonNull
                                         String permissions[],
                                       @NonNull
                                         int[] grantResults) {

    if (manageReadExternalPermissionResponse(this, requestCode, grantResults)) {
        launchImagePick();
    }
}

Gérer la réponse d'autorisation

public static boolean manageReadExternalPermissionResponse(final Activity activity, int requestCode, int[] grantResults) {

    if (requestCode == MY_PERMISSIONS_REQUEST_READ_EXTERNAL) {

        // If request is cancelled, the result arrays are empty.

        if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

            // Permission was granted, yay! Do the
            // contacts-related task you need to do.
            return true;

        } else if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_DENIED) {

            boolean showRationale = ActivityCompat.shouldShowRequestPermissionRationale(activity,
                    Manifest.permission.READ_EXTERNAL_STORAGE);

            if (!showRationale) {
                // The user also CHECKED "never ask again".
                // You can either enable some fall back,
                // disable features of your app
                // or open another dialog explaining
                // again the permission and directing to
                // the app setting.

            } else {
                // The user did NOT check "never ask again".
                // This is a good place to explain the user
                // why you need the permission and ask if he/she wants
                // to accept it (the rationale).
            }
        } else {
            // Permission denied, boo! Disable the
            // functionality that depends on this permission.
        }
    }
    return false;
}

Lancer la sélection d'image

private void launchImagePick() {

    Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
    intent.setType("image/*");
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    startActivityForResult(intent, PICK_IMAGE);

    // see onActivityResult
}

Gérer la réponse de sélection d'image

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == PICK_IMAGE) {

        if (resultCode == Activity.RESULT_OK) {
            if (data != null && data.getData() != null) {

                try {
                     InputStream inputStream = getContentResolver().openInputStream(data.getData())
                     if (inputStream != null) {

                        // No special persmission needed to store the file like that
                        FileOutputStream fos = openFileOutput(FILE_TEMP_NAME, Context.MODE_PRIVATE);

                        final int BUFFER_SIZE = 1 << 10 << 3; // 8 KiB buffer
                        byte[] buffer = new byte[BUFFER_SIZE];
                        int bytesRead = -1;
                        while ((bytesRead = inputStream.read(buffer)) > -1) {
                            fos.write(buffer, 0, bytesRead);
                        }
                        inputStream.close();
                        fos.close();

                        File tempImageFile = new File(getFilesDir()+"/"+FILE_TEMP_NAME);

                        // Do whatever you want with the File

                        // Delete when not needed anymore
                        deleteFile(FILE_TEMP_NAME);
                    }
                }
                catch (Exception e) {
                    e.printStackTrace();
                }
            } else {
                // Error display
            }
        } else {
            // The user did not select any image
        }
    }
}

C'est tout le monde; cela fonctionne pour moi sur tous les téléphones que j'ai.


0

C'est un hack total, mais voici ce que j'ai fait ...

Donc, tout en jouant avec la configuration d'un DocumentsProvider , j'ai remarqué que l' exemple de code (dans getDocIdForFile, autour de la ligne 450) génère un identifiant unique pour un document sélectionné en fonction du chemin d'accès (unique) du fichier par rapport à la racine spécifiée que vous lui donnez (c'est-à-dire, ce que vous définissezmBaseDir à la ligne 96).

L'URI finit donc par ressembler à quelque chose comme:

content://com.example.provider/document/root:path/to/the/file

Comme le disent les docs, cela suppose une seule racine (dans mon cas c'est Environment.getExternalStorageDirectory()mais vous pouvez utiliser ailleurs ... alors il prend le chemin du fichier, en commençant à la racine, et en fait l'ID unique, en préfixant " root:". Donc je peut déterminer le chemin en éliminant la "/document/root:"partie de uri.getPath (), créant un chemin de fichier réel en faisant quelque chose comme ceci:

public void onActivityResult(int requestCode, int resultCode, Intent data) {
// check resultcodes and such, then...
uri = data.getData();
if (uri.getAuthority().equals("com.example.provider"))  {
    String path = Environment.getExternalStorageDirectory(0.toString()
                 .concat("/")
                 .concat(uri.getPath().substring("/document/root:".length())));
    doSomethingWithThePath(path); }
else {
    // another provider (maybe a cloud-based service such as GDrive)
    // created this uri.  So handle it, or don't.  You can allow specific
    // local filesystem providers, filter non-filesystem path results, etc.
}

Je connais. C'est honteux, mais ça a marché. Encore une fois, cela dépend de vous en utilisant votre propre fournisseur de documents dans votre application pour générer l'ID du document.

(De plus, il existe une meilleure façon de construire le chemin qui ne suppose pas que "/" est le séparateur de chemin, etc. Mais vous avez l'idée.)


Pour me répondre avec une pensée encore plus folle - si votre application gère déjà les file://intentions d'un sélecteur de fichiers externe, vous pouvez également vérifier l'autorité, comme dans l'exemple ci-dessus pour vous assurer qu'elle provient de votre fournisseur personnalisé, et si c'est le cas, vous pourriez utilisez également le chemin pour "forger" une nouvelle file://intention en utilisant le chemin que vous avez extrait, puis StartActivity()laissez votre application la reprendre. Je sais, terrible.
fattire

0

CA marchait bien pour moi:

else if(requestCode == GALLERY_ACTIVITY_NEW && resultCode == Activity.RESULT_OK)
{
    Uri uri = data.getData();
    Log.i(TAG, "old uri =  " + uri);
    dumpImageMetaData(uri);

    try {
        ParcelFileDescriptor parcelFileDescriptor =
                getContentResolver().openFileDescriptor(uri, "r");
        FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
        Log.i(TAG, "File descriptor " + fileDescriptor.toString());

        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);

        options.inSampleSize =
           BitmapHelper.calculateInSampleSize(options,
                                              User.PICTURE_MAX_WIDTH_IN_PIXELS,
                                              User.PICTURE_MAX_HEIGHT_IN_PIXELS);
        options.inJustDecodeBounds = false;

        Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options);
        imageViewPic.setImageBitmap(bitmap);

        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
        // get byte array here
        byte[] picData = stream.toByteArray();
        ParseFile picFile = new ParseFile(picData);
        user.setProfilePicture(picFile);
    }
    catch(FileNotFoundException exc)
    {
        Log.i(TAG, "File not found: " + exc.toString());
    }
}

oubliez le dumpImageMetaData (uri); c'est inutile
Rafa

0

S'appuyer sur la réponse de Paul Burke j'ai rencontré de nombreux problèmes pour résoudre le chemin URI de la carte SD externe car la plupart des fonctions "intégrées" suggérées renvoient des chemins qui ne sont pas résolus dans les fichiers.

Cependant, c'est mon approche de son // TODO gérer les volumes non primaires .

String resolvedPath = "";
File[] possibleExtSdComposites = context.getExternalFilesDirs(null);
for (File f : possibleExtSdComposites) {
    // Reset final path
    resolvedPath = "";

    // Construct list of folders
    ArrayList<String> extSdSplit = new ArrayList<>(Arrays.asList(f.getPath().split("/")));

    // Look for folder "<your_application_id>"
    int idx = extSdSplit.indexOf(BuildConfig.APPLICATION_ID);

    // ASSUMPTION: Expected to be found at depth 2 (in this case ExtSdCard's root is /storage/0000-0000/) - e.g. /storage/0000-0000/Android/data/<your_application_id>/files
    ArrayList<String> hierarchyList = new ArrayList<>(extSdSplit.subList(0, idx - 2));

    // Construct list containing full possible path to the file
    hierarchyList.add(tail);
    String possibleFilePath = TextUtils.join("/", hierarchyList);

    // If file is found --> success
    if (idx != -1 && new File(possibleFilePath).exists()) {
        resolvedPath = possibleFilePath;
        break;
    }
}

if (!resolvedPath.equals("")) {
    return resolvedPath;
} else {
    return null;
}

Notez que cela dépend de la hiérarchie qui peut être différente pour chaque fabricant de téléphones - je ne les ai pas tous testés (cela fonctionnait bien jusqu'à présent sur Xperia Z3 API 23 et Samsung Galaxy A3 API 23).

Veuillez confirmer s'il ne fonctionne pas bien ailleurs.


-1

La réponse de @paul burke fonctionne bien pour les images de la caméra et de la galerie pour le niveau API 19 et supérieur, mais cela ne fonctionne pas si le SDK minimum de votre projet Android est inférieur à 19, et certaines réponses se référant ci-dessus ne fonctionnent pas pour la galerie et caméra. Eh bien, j'ai modifié le code de @paul burke qui fonctionne pour le niveau d'API inférieur à 19. Ci-dessous, le code.

public static String getPath(final Context context, final Uri uri) {

    final boolean isKitKat = Build.VERSION.SDK_INT >=
                             Build.VERSION_CODES.KITKAT;
    Log.i("URI",uri+"");
    String result = uri+"";

    // DocumentProvider
    // if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
    if (isKitKat && (result.contains("media.documents"))) {

        String[] ary = result.split("/");
        int length = ary.length;
        String imgary = ary[length-1];
        final String[] dat = imgary.split("%3A");

        final String docId = dat[1];
        final String type = dat[0];

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

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

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

    return null;
}

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;
}

Je reçois java.lang.IllegalArgumentException: aucune des colonnes demandées ne peut être fournie lors de la sélection d'une image Google Doc
dirkoneill

@dirkoneill Je reçois la même exception. Avez-vous trouvé un correctif?
Henry

-4

La réponse à votre question est que vous devez avoir des autorisations. Tapez le code suivant dans votre fichier manifest.xml:

<uses-sdk  android:minSdkVersion="8"   android:targetSdkVersion="18" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
<uses-permission android:name="android.permission.WRITE_OWNER_DATA"></uses-permission>
<uses-permission android:name="android.permission.READ_OWNER_DATA"></uses-permission>`

Ça a marché pour moi ...

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.