La Galería de Android en Android 4.4 (KitKat) devuelve diferentes URI para Intent.ACTION_GET_CONTENT

214

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 la ContentResolver y quering MediaStore.Images.Media.DATAdevolvió la URL del archivo.

Sin embargo, en KitKat, 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?

Michael Greifeneder
fuente
21
De improviso, encontraría formas de usar el contenido que no requiere acceso directo al archivo. Por ejemplo, eso Uridebería poder abrirse como una transmisión vía ContentResolver. Durante mucho tiempo he estado nervioso por las aplicaciones que suponen que un content:// Urique representa un archivo siempre se puede convertir en un File.
CommonsWare
1
@ CommonsWare, si quiero guardar una ruta de imagen en sqlite db para poder abrirla más tarde, ¿debo guardar el URI o la ruta absoluta del archivo?
Serpiente
2
@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 usando un InputStreamen ContentResolverun lugar previamente designado para que tenga un nombre de archivo conocido. Sin embargo, esto me suena un desperdicio. ¿Cualquier otra sugerencia?
darrenp
2
@darrenp: Ummm ..., ¿reescribir el código nativo para trabajar con un InputStreamJNI? Desafortunadamente, no hay tantas opciones para ti.
CommonsWare
1
Es útil saberlo. Gracias por su respuesta. Desde entonces descubrí que ahora estamos pasando la imagen a C ++ en la memoria en lugar de hacerlo a través de un archivo, por lo que ahora podemos usar un InputStreamarchivo en lugar de un archivo (lo cual 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

Respuestas:

108

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 necesito

@SuppressLint ("NewApi")

para

takePersistableUriPermission

descubridor
fuente
1
¿Te importaría elaborar lo que está haciendo el código KitKat? ¿Requiere esto algún permiso nuevo? El código pre KitKat también funciona para mí en KitKat. Entonces, ¿por qué elegiría usar un código diferente para KitKat? Gracias.
Michael Greifeneder
67
parece que no podemos encontrar el camino de los nuevos SDK uri. También es una pena que Google haya hecho este tipo de cambio sin la documentación y el anuncio adecuados.
usuario65721
1
¿Podría explicar cómo obtener la URL del archivo? Me gustaría obtener la ruta real en sdcard. Por ejemplo, si es una imagen, me gustaría obtener esta ruta /storage/sdcard0/DCIM/Camera/IMG_20131118_153817_119.jpg en lugar del documento Uri.
Álvaro
44
Según los documentos de KitKat ( developer.android.com/about/versions/… ), esto puede no ser lo que el OP necesita a menos que intente usar / editar documentos que son propiedad de las otras aplicaciones. Si el OP quiere una copia o hacer las cosas de manera consistente con las versiones anteriores, la respuesta de @voytez sería más apropiada.
Colin M.
8
Esto no está funcionando para mí. Recibo la siguiente excepción (en stock 4.4.2): E / AndroidRuntime (29204): Causado por: java.lang.SecurityException: Indicadores solicitados 0x1, pero solo se permiten 0x0
Russell Stewart
177

Esto no requiere permisos especiales y funciona con Storage Access Framework, así como con los no oficiales ContentProvider patrón (ruta del archivo en el _datacampo).

/**
 * 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());
}

Vea una versión actualizada de este método aquí .

Paul Burke
fuente
2
Esto funcionó fantásticamente en una interfaz de usuario 4.4 Nexus 5 Documents y algunos otros dispositivos KitKat pref usando las aplicaciones de galería estándar, ¡gracias Paul!
Josh
1
¡Gracias por esto, me ha llevado años llegar tan lejos con SDK 19! Mi problema es que mi dispositivo está usando Google Drive como explorador de archivos. Si el archivo está en la ruta de la imagen del dispositivo, está bien, pero si el archivo está en la unidad, no se abre. Tal vez solo necesito ver cómo lidiar con las imágenes de apertura de Google Drive. La cosa es que mi aplicación está escrita para usar la ruta del archivo y obtener la imagen mediante el muestreo ...
RuAware
2
@RuAware Cuando selecciona un archivo de Drive, devuelve Authority: com.google.android.apps.docs.storagey Segments: [document, acc=1;doc=667]. No estoy seguro, pero suponga que el docvalor es el UriID con el que puede consultar. Probablemente necesitará permisos para configurarlo como se detalla en "Autorice su aplicación en Android" aquí: developers.google.com/drive/integrate-android-ui . Actualice aquí si lo resuelve.
Paul Burke
30
¡Esto es absolutamente horrible! no debe continuar propagando el código que "engaña" de esta manera. solo es compatible con las aplicaciones de origen para las que conoce el patrón, y el objetivo del modelo de proveedor de documentos es admitir fuentes arbitrarias
j__m
2
El _datano iba a funcionar cuando ContentProvider no lo soporta. Se recomienda seguir las instrucciones de @CommonsWare y no usar la ruta completa del archivo, ya que podría ser un archivo en la nube de Dropbox en lugar de un archivo real.
soshial
67

Tuve el mismo problema, probé la solución anterior, pero aunque funcionó en general, por alguna razón estaba recibiendo la denegación de permiso en el proveedor de contenido de Uri para algunas imágenes, aunque tenía el android.permission.MANAGE_DOCUMENTSpermiso agregado 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 KITKAT con:

// KITKAT

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

y luego cargue la imagen:

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

EDITAR

ACTION_OPEN_DOCUMENT podría requerir que usted mantenga marcas de permisos, etc. y, en general, a menudo genera Excepciones de seguridad ...

Otra solución es utilizar el ACTION_GET_CONTENTcombinado con el c.getContentResolver().openInputStream(selectedImageURI)que funcionará tanto en pre-KK como en KK. Kitkat usará una nueva vista de documentos y esta solución funcionará con todas las aplicaciones como Fotos, Galería, Explorador de archivos, Dropbox, Google Drive, etc.) pero recuerde que cuando use esta solución, debe crear una imagen en su onActivityResult()y almacenarla en Tarjeta SD por ejemplo. La recreación de esta imagen desde la uri guardada 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 marcas 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 pautas de la API para desarrolladores de Android sugieren:

ACTION_OPEN_DOCUMENT no pretende reemplazar a ACTION_GET_CONTENT. El que debe usar depende de las necesidades de su 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 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.

voytez
fuente
1
Esta respuesta contenía la información correcta para mis propósitos. El uso condicional de ACTION_PICK y EXTERNAL_CONTENT_URI en KitKat proporcionó la misma capacidad para obtener metadatos sobre imágenes en la galería a través de ContentResolver, como es posible en versiones anteriores usando simplemente ACTION_GET_CONTENT.
Colin M.
@voytez, ¿puede este URI devuelto a través de su mensaje convertido a la ruta real completa de la imagen?
Serpiente
Creo que sí, debería funcionar igual que antes de KitKat, ya que este código obliga a abrir la galería de imágenes en lugar de la vista de documentos KK. Pero si tiene la intención de usarlo para crear una imagen, entonces esta solución es mejor ya que la conversión a una ruta real es un paso adicional innecesario.
voytez
44
También funcionó para mí, en lugar de Intent.ACTION_GET_CONTENT. De todos modos, mantuve el Intent.createChooser()contenedor en el nuevo Intent, para permitir al usuario elegir la aplicación para navegar, y funcionó como se esperaba. ¿Alguien puede ver los inconvenientes de esta solución?
lorenzo-s
1
Para cualquiera que se pregunte, la cita proviene de developer.android.com/guide/topics/providers/…
snark
38

Tal como mencionó Commonsware, no debe suponer que la transmisión que obtiene ContentResolver es convertible en un archivo.

Lo que realmente debe hacer es abrir InputStreamdesde ContentProvider, luego crear 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 la información adecuada inSampleSize: http://developer.android.com/training/displaying-bitmaps/load-bitmap.html . Pero ese es otro tema.

Michał K
fuente
Esto no funciona para mí con un Nexus 4 con Kitkat pero sí con un Galaxy S3 con 4.1.2
Dan2552
@ Dan2552, ¿qué parte no funciona? ¿Tienes alguna excepción?
Michał K
Resultó que estaba usando la llamada de intención incorrecta a la galería. Estaba usando uno que era para cualquier tipo de documentos, pero con un filtro de extensión de archivo.
Dan2552
2
¡Qué respuesta tan hermosa y simple, gracias! Para otros que siguen esta respuesta, 'cxt' se refiere al contexto actual y generalmente será 'esto'.
thomasforth
1
Esto probablemente significa que el archivo simplemente no está allí. El URI parece estar bien.
Michał K
33

Creo que las respuestas ya publicadas deberían hacer que las personas avancen 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 estaba usando 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í: content: // media / external / images / media / 41

Como se indicó en la pregunta, con mayor frecuencia se ven así: content: //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 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.

LEÓN
fuente
muy inteligente. yo también necesitaba el archivo uri real
GreaterKing
eres mi héroe, esto es exactamente lo que necesitaba! funciona muy bien para los archivos de Google Drive también
mishkin
Piensa fuera de la caja, ¿verdad? : D Este código funciona exactamente como esperaba.
WeirdElfB0y
2
publique el código para copyAndClose, la respuesta no está completa
Bartek Pacia
24

Solo quería decir que esta respuesta es brillante y la estoy usando durante mucho tiempo sin problemas. Pero hace algún tiempo me topé con un problema de que DownloadsProvider devuelve los URI en formato content://com.android.providers.downloads.documents/document/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2Fdoc.pdfy, por lo tanto, la aplicación se bloquea NumberFormatExceptionya que es imposible analizar sus segmentos uri por tanto tiempo. Pero el raw:segmento contiene uri directa que se puede usar para recuperar un archivo referenciado. Así que lo arreglé reemplazando el isDownloadsDocument(uri) ifcontenido 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;
}
}
Traer
fuente
2
Funciona perfecto! Gracias
mikemike396
8

Combiné múltiples respuestas en una solución de trabajo que resulta con 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() + "/" + 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());
    }

}
Grzegorz Pawełczuk
fuente
Estaba enfrentando un problema ... uri.getPath () estaba devolviendo uri con / external y no estaba devolviendo la ruta. Agregué esto más si ("content" .equalsIgnoreCase (uri.getScheme ())) bloquea y esto funciona bien ahora ... ¿puedes explicar qué hace?
FaisalAhmed
filePath se está volviendo nulo. En uri: contenido: //com.android.externalstorage.documents/document/799B-1419%3AScreenshot%2FScreenshot_20181117_162826.png
Bhavesh Moradiya
7

Pregunta

Cómo obtener una ruta de archivo real desde un URI

Responder

Que yo sepa, no necesitamos obtener la ruta del archivo desde un URI porque para la mayoría de los casos podemos usar directamente el URI para hacer nuestro trabajo (como 1. obtener un mapa de bits 2. Enviar un archivo al servidor, etc. .)

1. Envío al servidor

Podemos enviar directamente el archivo al servidor utilizando solo el URI.

Usando el URI podemos obtener InputStream, que podemos enviar directamente al servidor usando MultiPartEntity.

Ejemplo

/**
 * 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. Obtener un BitMap de un URI

Si el URI apunta a la imagen, obtendremos un mapa de bits, de lo contrario, será nulo:

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

Comentarios

  1. Android no proporciona ningún método para obtener la ruta del archivo desde un URI, y en la mayoría de las respuestas anteriores hemos codificado algunas constantes, que pueden interrumpir el lanzamiento de la función (lo siento, puedo estar equivocado).
  2. Antes de ir directamente a una solución para obtener la ruta del archivo desde un URI, intente si puede resolver su caso de uso con un URI y los métodos predeterminados de Android.

Referencia

  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
Vasanth
fuente
Gracias. Usar Uri y ContentResolver de esta manera simplificó mi aplicación de manera significativa al tratar con archivos.
jacksonakj
3

Para aquellos que todavía usan el código de @Paul Burke con Android SDK versión 23 y superior, si su proyecto encontró el error que indica que falta EXTERNAL_PERMISSION, y está muy seguro de que ya ha agregado el permiso de usuario en su archivo AndroidManifest.xml. Esto se debe a que es posible que en Android API 23 o superior y Google haga necesario garantizar el permiso nuevamente mientras realiza la acción para acceder al archivo en tiempo de ejecución.

Eso significa: si su versión de SDK es 23 o superior, se le solicitará permiso de LECTURA Y ESCRITURA mientras selecciona el archivo de imagen y desea conocer su URI.

Y el siguiente es mi código, además de la solución de Paul Burke. Agrego estos códigos y mi proyecto comienza a funcionar bien.

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

Y en su actividad y fragmento donde solicita el 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);
}

En mi caso, CompatUtils.java es donde defino el método generateStoragePermissions (como tipo estático para poder llamarlo dentro de otra actividad).

También debería tener más sentido si primero hace un estado if para ver si la versión actual del SDK es superior a 23 o no antes de llamar al método allowStoragePermissions.

Anthonyeef
fuente
2

Esto es lo que hago:

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

NOTA: el managedQuery()método está en desuso, por lo que no lo estoy usando.

Esta respuesta es de m3n0R en la pregunta de Android para obtener un camino real por Uri.getPath () y no reclamo ningún crédito. Solo pensé que las personas que aún no han resuelto este problema podrían usarlo.

Isaac Zais
fuente
2
Esta no es una respuesta a la nueva aplicación de la Galería (estrictamente la aplicación "Proveedor de documentos multimedia") en KitKat.
nagoya0
La aplicación que el interlocutor llama "Galería" es probablemente un nuevo selector de archivos en kitkat. FYI: addictivetips.com/android/…
nagoya0
Hago algo similar y obtuve IndexOutOfBound en Nexus 5X, Android 6 en esta línea:cursor.getString(idx);
Osify
1

Intente evitar usar el método takePersistableUriPermission porque generó una excepción de tiempo de ejecución para mí. / ** * Seleccionar de la galería. * /

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 para obtener resultados para manejar los datos de la imagen:

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


    }
saranya
fuente
¿Dónde estás mostrando la imagen en el segundo caso?
Quantum_VC
lo siento, no pude agregar esta línea de código en otro caso si mImgCropping.startCropImage (photoFile, AppConstants.REQUEST_IMAGE_CROP); nuevamente necesito llamar a la función de recorte de imágenes según la necesidad de mi proyecto
saranya
¿Qué tipo de archivo es photoFile y mImgCropping?
Philip BH
1

Si alguien está interesado, hice una versión funcional de Kotlin para 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)
}
0101100101
fuente
Solo quiero un código de trabajo de kotlin. Es trabajo para mi. gracias
Harvi Sirja
1

He intentado varias de las respuestas aquí, y creo que tengo una solución que funcionará siempre y que también gestiona los permisos.

Se basa en la solución inteligente de LEO. Esta publicación debe contener todo el código que necesita para que esto funcione, y debe funcionar en cualquier teléfono y versión de Android;)

Para poder elegir un archivo de una tarjeta SD, necesitará esto en su manifiesto:

<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

Verifique el permiso y ejecute ImagePick si es posible

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

Respuesta de permiso

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

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

Administrar respuesta de permiso

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

Lanzar selección de imagen

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
}

Administrar respuesta de selección de imagen

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

Eso es todo amigos; Esto funciona para mí en todos los teléfonos que tengo.

Quentin G.
fuente
0

Este es un truco total, pero esto es lo que hice ...

Entonces, mientras jugaba con la configuración de un DocumentsProvider , noté que el código de muestra (en getDocIdForFile, alrededor de la línea 450) genera una identificación única para un documento seleccionado en función de la ruta (única) del archivo en relación con la raíz especificada que le das (es decir, lo que configuró mBaseDiren la línea 96).

Entonces, el URI termina pareciéndose a:

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

Como dicen los documentos, está asumiendo solo una raíz (en mi caso, eso es, Environment.getExternalStorageDirectory()pero puede usar en otro lugar ... luego toma la ruta del archivo, comenzando en la raíz, y lo convierte en la ID única, precediendo " root:". Entonces puede determinar la ruta eliminando la "/document/root:"parte de uri.getPath (), creando una ruta de archivo real haciendo algo como esto:

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

Lo sé. Es vergonzoso, pero funcionó. Nuevamente, esto depende de que usted use su propio proveedor de documentos en su aplicación para generar la identificación del documento.

(Además, hay una mejor manera de construir la ruta que no asume que "/" es el separador de ruta, etc. Pero se entiende la idea).

fattire
fuente
Para responderme a mí mismo con un pensamiento aún más loco: si su aplicación ya está manejando file://intenciones desde un selector de archivos externo, también puede verificar la autoridad, como en el ejemplo anterior, para asegurarse de que sea de su proveedor personalizado, y si es así, podría también use la ruta para "forjar" una nueva file://intención usando la ruta que extrajo, luego, StartActivity()y deje que su aplicación la tome. Lo sé terrible.
fattire
0

Esto funcionó bien para mi:

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());
    }
}
Rafa
fuente
olvide el dumpImageMetaData (uri); es innecesario
Rafa
0

A partir de la respuesta de Paul Burke, enfrenté muchos problemas para resolver la ruta URI de la tarjeta SD externa ya que la mayoría de las funciones "incorporadas" sugeridas devuelven rutas que no se resuelven en los archivos.

Sin embargo, este es mi enfoque de su // TODO manejar volúmenes no primarios .

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

Tenga en cuenta que depende de la jerarquía, que puede ser diferente en cada fabricante de teléfonos: no los he probado todos (hasta ahora funcionó bien en Xperia Z3 API 23 y Samsung Galaxy A3 API 23).

Confirme si no funciona bien en otro lugar.

Evusas
fuente
-1

La respuesta de @paul burke funciona bien tanto para la cámara como para las imágenes de la galería para el nivel API 19 y superior, pero no funciona si el SDK mínimo de su proyecto de Android está configurado por debajo de 19, y algunas respuestas que se refieren arriba no funcionan para la galería y cámara. Bueno, he modificado el código de @paul burke que funciona para el nivel API por debajo de 19. A continuación se muestra el código.

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;
}
parvez rafi
fuente
Recibo java.lang.IllegalArgumentException: ninguna de las columnas solicitadas se puede proporcionar al seleccionar una imagen de Google Doc
dirkoneill
@dirkoneill Recibo la misma excepción. Has encontrado algún arreglo?
Henry
-4

La respuesta a su pregunta es que necesita tener permisos. Escriba el siguiente código en su archivo 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 mí me funcionó ...

ashwin
fuente