
Michael Greifeneder
Antes de KitKat (o antes de la nueva Galería) el Intent.ACTION_GET_CONTENT
devolvió un URI como este
contenido://media/external/images/media/3951.
Utilizando el ContentResolver
y preguntando por
MediaStore.Images.Media.DATA
devolvió la URL del archivo.
En KitKat, sin embargo, la Galería devuelve un URI (a través de “Último”) como este:
contenido://com.android.providers.media.documents/document/image:3951
¿Cómo manejo esto?

Pablo Burke
Esto no requiere permisos especiales y funciona con Storage Access Framework, así como con el no oficial ContentProvider
patrón (ruta del archivo en _data
campo).
/**
* 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() + "https://stackoverflow.com/" + 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());
}
Ver una versión actualizada de este método aquí.
Prueba esto:
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);
}
Probablemente necesite
@SuppressLint(“Nueva API”)
por
takePersistableUriPermission

Voytez
Tuve el mismo problema, probé la solución anterior, pero aunque funcionó en general, por alguna razón recibí una denegación de permiso en el proveedor de contenido de Uri para algunas imágenes, aunque tenía la android.permission.MANAGE_DOCUMENTS
permiso añadido correctamente.
De todos modos, encontré otra solución que es forzar la apertura de la galería de imágenes en lugar de la vista de documentos de KITKAT con:
// KITKAT
i = new Intent(Intent.ACTION_PICK,android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(i, CHOOSE_IMAGE_REQUEST);
y luego carga la imagen:
Uri selectedImageURI = data.getData();
input = c.getContentResolver().openInputStream(selectedImageURI);
BitmapFactory.decodeStream(input , null, opts);
EDITAR
ACTION_OPEN_DOCUMENT
puede requerir que persista las banderas de permisos, etc. y, en general, a menudo da como resultado excepciones de seguridad …
Otra solución es usar el ACTION_GET_CONTENT
combinado con c.getContentResolver().openInputStream(selectedImageURI)
que funcionará tanto en pre-KK como en KK. Kitkat usará una nueva vista de documentos entonces y esta solución funcionará con todas las aplicaciones como Fotos, Galería, Explorador de archivos, Dropbox, Google Drive, etc.), pero recuerde que al usar esta solución debe crear una imagen en su onActivityResult()
y guárdelo en la tarjeta SD, por ejemplo. Recrear esta imagen desde el uri guardado en el próximo lanzamiento de la aplicación arrojaría una Excepción de seguridad en la resolución de contenido incluso cuando agrega indicadores de permiso como se describe en los documentos de la API de Google (eso es lo que sucedió cuando hice algunas pruebas)
Además, las Directrices de la API para desarrolladores de Android sugieren:
ACTION_OPEN_DOCUMENT no pretende ser un reemplazo de ACTION_GET_CONTENT. El que debes usar depende de las necesidades de tu aplicación:
Use ACTION_GET_CONTENT si desea que su aplicación simplemente lea/importe datos. Con este enfoque, la aplicación importa una copia de los datos, como un archivo de imagen.
Use ACTION_OPEN_DOCUMENT si desea que su aplicación tenga acceso persistente a largo plazo a los documentos propiedad de un proveedor de documentos. Un ejemplo sería una aplicación de edición de fotos que permite a los usuarios editar imágenes almacenadas en un proveedor de documentos.

Michał Klimczak
Tal como mencionó Commonsware, no debe asumir que la transmisión que obtiene a través de ContentResolver
es convertible en archivo.
Lo que realmente deberías hacer es abrir el InputStream
desde el ContentProvider
, luego cree un mapa de bits a partir de él. Y también funciona en 4.4 y versiones anteriores, sin necesidad de reflexión.
//cxt -> current context
InputStream input;
Bitmap bmp;
try {
input = cxt.getContentResolver().openInputStream(fileUri);
bmp = BitmapFactory.decodeStream(input);
} catch (FileNotFoundException e1) {
}
Por supuesto, si maneja imágenes grandes, debe cargarlas con inSampleSize
: http://developer.android.com/training/displaying-bitmaps/load-bitmap.html. Pero ese es otro tema.

LEÓN
Creo que las respuestas ya publicadas deberían hacer que la gente vaya en la dirección correcta. Sin embargo, esto es lo que hice que tenía sentido para el código heredado que estaba actualizando. El código heredado usaba el URI de la galería para cambiar y luego guardar las imágenes.
Antes de 4.4 (y Google Drive), los URI se verían así:
contenido://medios/externo/imágenes/medios/41
Como se indica en la pregunta, con mayor frecuencia se ven así:
contenido://com.android.providers.media.documents/document/image:3951
Como necesitaba la capacidad de guardar imágenes y no alterar el código ya existente, simplemente copié el URI de la galería en la carpeta de datos de la aplicación. Luego originó un nuevo URI a partir del archivo de imagen guardado en la carpeta de datos.
Aquí está la idea:
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 */
}
Nota: copyAndClose() solo hace E/S de archivo para copiar InputStream en un FileOutputStream. El código no está publicado.
Solo quería decir que esta respuesta es brillante y la estoy usando durante mucho tiempo sin problemas. Pero hace algún tiempo me encontré con un problema que DownloadsProvider devuelve URI en formato content://com.android.providers.downloads.documents/document/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2Fdoc.pdf
y por lo tanto la aplicación se bloquea con NumberFormatException
ya que es imposible analizar sus segmentos uri tanto tiempo. Pero raw:
El segmento contiene un uri directo que se puede usar para recuperar un archivo al que se hace referencia. Así que lo he arreglado reemplazando isDownloadsDocument(uri)
if
contenido con lo siguiente:
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;
}
}

Grzegorz Pawełczuk
Combiné varias respuestas en una solución de trabajo que da como resultado la ruta del archivo
El tipo Mime es irrelevante para el propósito del ejemplo.
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);
}
Resultado de manejo
@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() + "https://stackoverflow.com/" + 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());
}
}
De manera improvisada, encontraría formas de usar el contenido que no requiera acceso directo al archivo. por ejemplo, que
Uri
debe poder abrirse como una secuencia a través deContentResolver
. Durante mucho tiempo he estado nervioso por las aplicaciones que asumen que uncontent://
Uri
que representa un archivo siempre se puede convertir en unFile
.– CommonsWare
7 de noviembre de 2013 a las 12:31
@CommonsWare, si quiero guardar una ruta de imagen en sqlite db para poder abrirla más tarde, ¿debo guardar el URI o la ruta de archivo absoluta?
– Serpiente
19 de diciembre de 2013 a las 20:45
@CommonsWare Estoy de acuerdo con tu nerviosismo. 🙂 Sin embargo, necesito poder pasar un nombre de archivo (para una imagen) al código nativo. Una solución es copiar los datos obtenidos mediante un
InputStream
sobre elContentResolver
a un lugar predesignado para que tenga un nombre de archivo conocido. Sin embargo, esto suena un desperdicio para mí. ¿Cualquier otra sugerencia?– darrenp
15 de enero de 2014 a las 17:22
@darrenp: Ummm…, reescriba el código nativo para que funcione con un
InputStream
sobre JNI? Desafortunadamente, no hay tantas opciones para ti.– CommonsWare
15 de enero de 2014 a las 17:34
Es útil saberlo. Gracias por su respuesta. Desde entonces, descubrí que ahora estamos pasando la imagen a C ++ en la memoria en lugar de a través de un archivo, por lo que ahora podemos usar un
InputStream
en lugar de un archivo (que es genial). Solo la lectura de etiquetas EXIF es un poco complicada y requiere La biblioteca de Drew Noakes. Muchas gracias por tus comentarios.– darrenp
15 de enero de 2014 a las 20:49