217 votes

La Galerie Android sur Android 4.4 (KitKat) renvoie une URI différente pour Intent.ACTION_GET_CONTENT

Avant KitKat (ou avant la nouvelle galerie), la fonction Intent.ACTION_GET_CONTENT a retourné un URI comme ceci

contenu://media/external/images/media/3951.

Utilisation de la ContentResolver et de quêter pour MediaStore.Images.Media.DATA a renvoyé l'URL du fichier.

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

contenu://com.Android.providers.media.documents/document/image:3951

Comment dois-je gérer cela ?

21 votes

De façon spontanée, je trouverais des moyens d'utiliser le contenu qui ne nécessitent pas un accès direct au fichier. Par exemple, que Uri doit pouvoir être ouvert en tant que flux via ContentResolver . J'ai longtemps été nerveux à l'égard des applications qui supposent qu'une content:// Uri qui représente un fichier peut toujours être convertie en un fichier File .

0 votes

Je pense qu'une réponse valable est de passer à l'option ContentResolver et travailler avec Uri au lieu de File-URLs. Je vais le faire. Cela permet également de mieux gérer les éléments qui ne sont pas des galeries. Uri s.

1 votes

@CommonsWare,Si je veux enregistrer le chemin d'une image dans une base de données sqlite pour pouvoir l'ouvrir plus tard, dois-je enregistrer l'URI ou le chemin absolu du fichier ?

180voto

Paul Burke Points 9869

Cela ne nécessite aucune autorisation particulière et fonctionne avec le cadre d'accès au stockage, ainsi qu'avec l'application non officielle ContentProvider motif (chemin du fichier dans _data ).

/**
 * 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 actualisée de cette méthode aquí .

2 votes

Cela a fonctionné de manière fantastique sur un Nexus 5 Documents UI 4.4 et d'autres appareils KitKat préférés utilisant les applications de galerie standard, merci Paul !

1 votes

Merci pour cela, il m'a fallu des années pour arriver aussi loin avec le sdk 19 ! Mon problème est que mon appareil utilise Google drive comme navigateur de fichiers. Si le fichier se trouve sur l'appareil, le chemin de l'image est correct, mais si le fichier se trouve sur le disque, il ne s'ouvre pas. Peut-être que j'ai juste besoin de regarder à traiter l'ouverture des images de Google Drive. Le problème est que mon application est écrite pour utiliser le chemin du fichier et obtenir l'image en utilisant l'insampling...

2 votes

@RuAware Lorsque vous sélectionnez un fichier Drive, il donne en retour Authority: com.google.android.apps.docs.storage y Segments: [document, acc=1;doc=667] . Je ne suis pas sûr, mais je suppose que la doc est la valeur Uri ID que vous pouvez interroger. Vous aurez probablement besoin d'autorisations pour être configuré comme indiqué dans "Autoriser votre application sur Android" ici : developers.google.com/drive/integrate-Android-ui . Veuillez mettre à jour ici si vous trouvez la solution.

110voto

finder Points 476

Essayez ç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);

}

Il faut probablement

@SuppressLint("NewApi")

pour

takePersistableUriPermission

1 votes

Pourriez-vous préciser ce que fait le code KitKat ? Est-ce que cela nécessite une nouvelle autorisation ? Le code pré-KitKat fonctionne pour moi sur KitKat, aussi. Alors pourquoi voudrais-je utiliser un code différent pour KitKat ? Merci.

0 votes

Mon ancien code refusait de fonctionner sur KitKat. J'ai dû lire le manuel . Eh bien, que de nouvelles permissions ne sont pas nécessaires.

69 votes

Il semble que nous ne pouvons pas obtenir le chemin à partir de la nouvelle uri sdks. C'est aussi une honte que google ait fait ce genre de changement sans documentation et annonce appropriée.

68voto

voytez Points 450

J'ai eu le même problème, j'ai essayé la solution ci-dessus mais bien qu'elle ait fonctionné en général, pour une raison quelconque, j'ai obtenu un refus d'autorisation sur le fournisseur de contenu Uri pour certaines images bien que j'aie eu le android.permission.MANAGE_DOCUMENTS permission ajoutée correctement.

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

et ensuite charger l'image :

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

EDITAR

ACTION_OPEN_DOCUMENT peut vous obliger à faire persister les drapeaux de permissions, etc. et entraîne souvent des exceptions de sécurité...

L'autre solution est d'utiliser le ACTION_GET_CONTENT combiné avec c.getContentResolver().openInputStream(selectedImageURI) qui fonctionnera à la fois sur les pré-KK et les KK. Kitkat utilisera alors la nouvelle vue des documents et cette solution fonctionnera avec toutes les applications comme Photos, Gallery, File Explorer, Dropbox, Google Drive etc...) mais n'oubliez pas que lorsque vous utilisez cette solution vous devez créer une image dans votre onActivityResult() et le stocker sur une carte SD par exemple. Recréer cette image à partir de l'url sauvegardée au prochain lancement de l'application entraînerait une exception de sécurité sur le résolveur de contenu, même si vous ajoutez des drapeaux de permission comme décrit dans les documents de l'API Google (c'est ce qui s'est passé lorsque j'ai fait quelques tests)

De plus, les directives de l'API pour les développeurs Android suggèrent :

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

Utilisez ACTION_GET_CONTENT si vous voulez que votre application se contente de lire/importer données. Avec cette approche, l'application importe une copie des données, comme par exemple un fichier image.

Utilisez ACTION_OPEN_DOCUMENT si vous voulez que votre application ait un accès persistant et à long terme aux documents détenus par un fournisseur de fournisseur de documents. Un exemple serait une application d'édition de photos qui permet aux utilisateurs de modifier des images stockées dans un fournisseur de documents.

1 votes

Cette réponse contenait les bonnes informations pour mes besoins. L'utilisation conditionnelle de l'ACTION_PICK et de l'EXTERNAL_CONTENT_URI sur KitKat permet d'obtenir des méta-données sur les images de la galerie via le ContentResolver, comme cela est possible sur les anciennes versions en utilisant simplement l'ACTION_GET_CONTENT.

0 votes

@voytez, l'URI renvoyé par votre message peut-il être converti en chemin réel complet de l'image ?

0 votes

Je pense 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 des images, cette solution est meilleure car la conversion en chemin réel est une étape supplémentaire non nécessaire.

39voto

Michał K Points 3304

Comme Commonsware l'a mentionné, vous ne devez pas supposer que le flux que vous obtenez via ContentResolver est convertible en fichier.

Ce que vous devriez faire, c'est d'ouvrir le InputStream de la ContentProvider puis créez 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 des images de grande taille, vous devez les charger avec des moyens appropriés. inSampleSize : http://developer.Android.com/training/displaying-bitmaps/load-bitmap.html . Mais c'est un autre sujet.

0 votes

Cela ne fonctionne pas avec mon Nexus 4 sous Kitkat, mais avec mon Galaxy S3 sous 4.1.2.

0 votes

@Dan2552 quelle partie ne fonctionne pas ? Obtenez-vous une exception ?

0 votes

Il s'avère que j'ai utilisé le mauvais appel d'intention à la galerie. J'en utilisais un qui était destiné à tout type de document, mais avec un filtre d'extension de fichier.

34voto

LEO Points 526

Je pense que les réponses déjà postées devraient permettre aux gens d'avancer dans la bonne direction. Cependant, voici ce que j'ai fait et qui était logique pour le code existant que je mettais à jour. L'ancien code utilisait l'URI de la galerie pour modifier et ensuite sauvegarder les images.

Avant la version 4.4 (et google drive), les URIs ressemblaient à ceci : contenu://media/external/images/media/41

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

Comme j'avais besoin de pouvoir sauvegarder des images et de ne pas perturber le code déjà existant, j'ai simplement copié l'URI de la galerie dans le dossier de données de l'application. Puis j'ai créé un nouvel URI à partir du fichier image sauvegardé 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 qu'effectuer des entrées/sorties de fichiers pour copier un InputStream dans un FileOutputStream. Le code n'est pas affiché.

0 votes

Très astucieux. Moi aussi, j'avais besoin de l'url du fichier actuel.

0 votes

Vous êtes mon héros, c'est exactement ce dont j'avais besoin ! cela fonctionne aussi très bien pour les fichiers Google Drive

0 votes

Pensez en dehors de la boîte, non ? :D Ce code fonctionne exactement comme je l'attendais.

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