201 votes

La Galerie Android sur KitKat renvoie différents Uri pour Intent.ACTION_GET_CONTENT

Avant Kitkat (ou avant la nouvelle Galerie) le Intent.ACTION_GET_CONTENT renvoyait un Uri comme celui-ci content://media/external/images/media/3951 .

L'utilisation de ContentResolver et de la requête pour MediaStore.Images.Media.DATA renvoyé l'URL du fichier.

En Kitkat cependant la Galerie renvoie un Uri (via "Last") comme ceci:

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

Comment gérer cela?

171voto

Paul Burke Points 9869

Cela ne nécessite aucune autorisation spéciale et fonctionne avec Storage Access Framework, ainsi qu'avec le modèle non officiel ContentProvider (chemin du fichier dans le champ _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 à jour de cette méthode ici .

108voto

finder Points 476

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

}
 

Probablement besoin

@SuppressLint ("NewApi")

pour

takePersistableUriPermission

67voto

voytez Points 450

Eu le même problème, essayé la solution ci-dessus, mais si elle a fonctionné de façon générale, pour une raison quelconque, j'ai été d'obtenir l'autorisation de déni de l'uri fournisseur de contenu pour quelques images bien que j'ai eu le android.la permission.MANAGE_DOCUMENTS autorisation ajouté correctement.

De toute façon trouver une autre solution qui consiste à forcer l'ouverture de la galerie d'image au lieu de KITKAT vue documents avec :

//KITKAT

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

et puis charger l'image:

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

*EDIT ACTION_OPEN_DOCUMENT peut vous obliger à persister autorisations drapeaux etc et généralement souvent des résultats en matière de Sécurité des Exceptions...

Autre solution consiste à utiliser la ACTION_GET_CONTENT combiné avec c.getContentResolver().openInputStream(selectedImageURI), qui travaillera à la fois sur la pré-KK et KK. Kitkat utilisation de nouveaux documents à vue et cette solution fonctionne avec toutes les applications comme des Photos, Galerie, Explorateur de Fichiers, Dropbox, Google Drive, etc...) mais n'oubliez pas que lors de l'utilisation de cette solution, vous devez créer une image dans votre onActivityResult et de le stocker sur une Carte SD par exemple. Pour recréer cette image à partir enregistré uri sur l'application prochaine de lancement serait jeter de l'Exception de Sécurité sur le contenu de résolution, même lorsque vous ajoutez l'autorisation des drapeaux comme décrit dans l'Api de Google docs (c'est ce qui s'est passé lorsque j'ai fait quelques tests)

De plus le Développeur Android API lignes Directrices suggèrent:

ACTION_OPEN_DOCUMENT n'est pas destiné à être un remplacement pour ACTION_GET_CONTENT. Celui que vous devez utiliser dépend des besoins de votre application:

Utilisation ACTION_GET_CONTENT si vous voulez que votre application il suffit de lire/d'importation les données. Avec cette approche, l'application permet d'importer une copie des données, telles que un fichier image.

Utilisation ACTION_OPEN_DOCUMENT si vous voulez que votre application à long terme, la persistance de l'accès aux documents détenus par un document fournisseur de. Un exemple serait un photo-montage application qui permet aux utilisateurs de modifier les images stockées dans un document du fournisseur.

38voto

Michał K Points 3304

Tout comme Commonsware mentionné, vous ne devriez pas supposer que le flux que vous obtenez via ContentResolver est convertible en fichier.

Ce que vous devriez vraiment faire est d'ouvrir l' InputStream de la ContentProvider, puis créer une image Bitmap. Et il fonctionne sur 4.4 et versions antérieures, nul besoin de réflexion.

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

    }

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

31voto

LEO Points 526

Je crois que les réponses déjà postées devraient amener les gens à aller dans la bonne direction. Cependant voici ce que j'ai fait qui fait sens pour l'héritage de code j'ai été mise à jour. Le code héritage a été en utilisant l'Uri de la galerie d'en changer et enregistrer les images.

Avant 4.4 (et google drive), l'uri devrait ressembler à ceci:
contenu://media/externe/images/media/41

Comme indiqué dans la question, le plus souvent, ils ressemblent à ceci: content://com.android.les prestataires.médias.documents/document/image:3951

Depuis que j'ai besoin de la capacité à enregistrer des images et ne pas perturber le déjà existantes code, j'ai juste copié l'Uri de la galerie dans le dossier de données de l'application.Alors créé une nouvelle Uri 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 temp 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() juste ne Fichier I/O pour copier InputStream dans un FileOutputStream. Le Code n'est pas affiché.

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: