¿Cómo extraer el nombre del archivo del URI devuelto por Intent.ACTION_GET_CONTENT?

98

Estoy usando un administrador de archivos de terceros para elegir un archivo (PDF en mi caso) del sistema de archivos.

Así es como lanzo la actividad:

Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType(getString(R.string.app_pdf_mime_type));
intent.addCategory(Intent.CATEGORY_OPENABLE);

String chooserName = getString(R.string.Browse);
Intent chooser = Intent.createChooser(intent, chooserName);

startActivityForResult(chooser, ActivityRequests.BROWSE);

Esto es lo que tengo en onActivityResult:

Uri uri = data.getData();
if (uri != null) {
    if (uri.toString().startsWith("file:")) {
        fileName = uri.getPath();
    } else { // uri.startsWith("content:")

        Cursor c = getContentResolver().query(uri, null, null, null, null);

        if (c != null && c.moveToFirst()) {

            int id = c.getColumnIndex(Images.Media.DATA);
            if (id != -1) {
                fileName = c.getString(id);
            }
        }
    }
}

El fragmento de código se toma prestado de las instrucciones de Open Intents File Manager disponibles aquí:
http://www.openintents.org/en/node/829

El propósito de if-elsees la compatibilidad con versiones anteriores. Me pregunto si esta es la mejor manera de obtener el nombre del archivo, ya que descubrí que otros administradores de archivos devuelven todo tipo de cosas.

Por ejemplo, Documents ToGo devuelve algo como lo siguiente:

content://com.dataviz.dxtg.documentprovider/document/file%3A%2F%2F%2Fsdcard%2Fdropbox%2FTransfer%2Fconsent.pdf

sobre el que getContentResolver().query()devuelve null.

Para hacer las cosas más interesantes, el administrador de archivos sin nombre (obtuve este URI del registro del cliente) devolvió algo como:

/./sdcard/downloads/.bin


¿Existe una forma preferida de extraer el nombre del archivo de la URI o se debe recurrir al análisis de cadenas?

Viktor Brešan
fuente
La misma pregunta con tal vez una mejor respuesta: stackoverflow.com/questions/8646246/…
Rémi

Respuestas:

159

developer.android.com tiene un buen código de ejemplo para esto: https://developer.android.com/guide/topics/providers/document-provider.html

Una versión condensada para simplemente extraer el nombre del archivo (asumiendo que "esto" es una Actividad):

public String getFileName(Uri uri) {
  String result = null;
  if (uri.getScheme().equals("content")) {
    Cursor cursor = getContentResolver().query(uri, null, null, null, null);
    try {
      if (cursor != null && cursor.moveToFirst()) {
        result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
      }
    } finally {
      cursor.close();
    }
  }
  if (result == null) {
    result = uri.getPath();
    int cut = result.lastIndexOf('/');
    if (cut != -1) {
      result = result.substring(cut + 1);
    }
  }
  return result;
}
Stefan Haustein
fuente
en caso de que intente leer mimeType o fileName del archivo de la cámara, primero debe notificar a MediaScanner que convertirá el URI de su archivo recién creado de file://a content://en el onScanCompleted(String path, Uri uri)método stackoverflow.com/a/5815005/2163045
murt
10
Agregar new String[]{OpenableColumns.DISPLAY_NAME}como segundo parámetro a la consulta filtrará las columnas para una solicitud más eficiente.
JM Lord
OpenableColumns.DISPLAY_NAMEno funcionó para mí, usé en su MediaStore.Files.FileColumns.TITLElugar.
Dmitry Kopytov
java.lang.IllegalArgumentException: Invalid column latitudeDesafortunadamente, esto se bloqueará para los videos al crear el cursor con un . ¡Funciona perfectamente para fotos!
Lucas P.
44

Estoy usando algo como esto:

String scheme = uri.getScheme();
if (scheme.equals("file")) {
    fileName = uri.getLastPathSegment();
}
else if (scheme.equals("content")) {
    String[] proj = { MediaStore.Images.Media.TITLE };
    Cursor cursor = context.getContentResolver().query(contentUri, proj, null, null, null);
    if (cursor != null && cursor.getCount() != 0) {
        int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.TITLE);
        cursor.moveToFirst();
        fileName = cursor.getString(columnIndex);
    }
    if (cursor != null) {
        cursor.close();
    }
}
Ken Fehling
fuente
4
He realizado algunas pruebas en tu código. En el URI devuelto por OI File Manager, se lanza IllegalArgumentException ya que la columna 'título' no existe. En URI devuelto por Documents ToGo, el cursor es nulo. El URI devuelto por el esquema de administrador de archivos desconocido es (obviamente) nulo.
Viktor Brešan
2
Mmmm interesante. Sí, probar el esquema! = Null es una buena idea. En realidad, no creo que TITLE sea lo que quieres. Lo estaba usando porque ciertos tipos de medios en Android (como las canciones elegidas a través del selector de música) tienen URI como content: // media / external / audio / media / 78 y quería mostrar algo más relevante que el número de identificación. Simplemente puede usar uri.getLastPathSegment () como mi código usa para file: // URIs si tiene un URI como contenido: //...somefile.pdf
Ken Fehling
1
uri.getLastPathSegment (); - me has salvado el día :)
Lumis
@Ken Fehling: Esto no funcionó para mí. Funciona cuando hago clic en un archivo en un navegador de archivos, pero cuando hago clic en un archivo adjunto de correo electrónico, todavía obtengo el contenido: // ... cosa. Probé todas las sugerencias aquí sin suerte. ¿Alguna idea de por qué?
Luis A. Florit
1
Hola, ¿por qué cursorno es close()d?
fikr4n
33

Tomado de Recuperar información de archivo | Desarrolladores de Android

Recuperando el nombre de un archivo.

private String queryName(ContentResolver resolver, Uri uri) {
    Cursor returnCursor =
            resolver.query(uri, null, null, null, null);
    assert returnCursor != null;
    int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
    returnCursor.moveToFirst();
    String name = returnCursor.getString(nameIndex);
    returnCursor.close();
    return name;
}
Bhargav
fuente
1
¡He estado buscando esto durante demasiado tiempo!
dasfima
7
Gracias. Dios mío, ¿por qué tienen que hacer algo tan trivial tan molesto?
TylerJames
1
Solo funciona con archivos de contenido. Consulte stackoverflow.com/a/25005243/6325722 para conocer el método completo
Johnny Five
17

Si lo quieres corto, esto debería funcionar.

Uri uri= data.getData();
File file= new File(uri.getPath());
file.getName();
Samuel
fuente
1
Recibo un nombre con file.getName () pero no es el nombre real
Tushar Thakur
1
Mi nombre de archivo es user.jpg pero recibo "6550" en respuesta
Tushar Thakur
1
No devuelve el nombre de archivo real. En realidad, devuelve la última parte de la URL de su recurso.
Emdadul Sawon
¡Esto no funciona como archivo! La mayoría de los URI deben ser decodificados por MediaStore hoy. Razón por la que otros y yo estamos obteniendo los números. Porque esos son los ID de esos archivos.
sud007
Esta fue una respuesta dada hace 4 años. Obviamente, Android está cambiando cada lanzamiento.
Samuel
17

Formas más fáciles de obtener el nombre del archivo:

val fileName = File(uri.path).name
// or
val fileName = uri.pathSegments.last()

Si no le dan el nombre correcto, debe usar:

fun Uri.getName(context: Context): String {
    val returnCursor = context.contentResolver.query(this, null, null, null, null)
    val nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
    returnCursor.moveToFirst()
    val fileName = returnCursor.getString(nameIndex)
    returnCursor.close()
    return fileName
}
Metu
fuente
7

Utilizo el siguiente código para obtener el nombre de archivo y el tamaño de archivo de Uri en mi proyecto.

/**
 * Used to get file detail from uri.
 * <p>
 * 1. Used to get file detail (name & size) from uri.
 * 2. Getting file details from uri is different for different uri scheme,
 * 2.a. For "File Uri Scheme" - We will get file from uri & then get its details.
 * 2.b. For "Content Uri Scheme" - We will get the file details by querying content resolver.
 *
 * @param uri Uri.
 * @return file detail.
 */
public static FileDetail getFileDetailFromUri(final Context context, final Uri uri) {
    FileDetail fileDetail = null;
    if (uri != null) {
        fileDetail = new FileDetail();
        // File Scheme.
        if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
            File file = new File(uri.getPath());
            fileDetail.fileName = file.getName();
            fileDetail.fileSize = file.length();
        }
        // 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);
                int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
                fileDetail.fileName = returnCursor.getString(nameIndex);
                fileDetail.fileSize = returnCursor.getLong(sizeIndex);
                returnCursor.close();
            }
        }
    }
    return fileDetail;
}

/**
 * File Detail.
 * <p>
 * 1. Model used to hold file details.
 */
public static class FileDetail {

    // fileSize.
    public String fileName;

    // fileSize in bytes.
    public long fileSize;

    /**
     * Constructor.
     */
    public FileDetail() {

    }
}
Vasanth
fuente
¡Ayudó a resolver mi problema con la carga de nombres de archivos desde datos externos o internos! Muy útil gracias!
Brandon
5

Para Kotlin, puede usar algo como esto:

object FileUtils {

   fun Context.getFileName(uri: Uri): String?
        = when (uri.scheme) {
            ContentResolver.SCHEME_FILE -> File(uri.path).name
            ContentResolver.SCHEME_CONTENT -> getCursorContent(uri)
            else -> null
        }

    private fun Context.getCursorContent(uri: Uri): String? 
        = try {
            contentResolver.query(uri, null, null, null, null)?.let { cursor ->
                cursor.run {
                    if (moveToFirst()) getString(getColumnIndex(OpenableColumns.DISPLAY_NAME))
                    else null
                }.also { cursor.close() }
            }
        } catch (e : Exception) { null }
Tamim Attafi
fuente
Buena respuesta, pero en mi opinión es mejor insertar estas funciones (Context.getFileName y Context.getCursorContent) en un archivo con el nombre 'Context', eliminar las palabras objeto FileUtils y usarlo como extensión, por ejemplo: val txtFileName = context.getFileName (uri)
Alexey Simchenko
@LumisD puedes importar estas extensiones en cualquier archivo, tu sugerencia es correcta pero odio cuando los métodos son globales. Quiero ver ciertos métodos solo si los importo y no siempre. Y a veces prefiero tener los mismos nombres de método y diferentes fuentes, así que importo los que necesito en el lugar correcto :)
Tamim Attafi
Puede importarlo de esta manera: importe com.example.FileUtils.getFileName o simplemente escriba context.getFileName (uri) y presione Alt + Enter y su IDE lo hará por usted :)
Tamim Attafi
5

La versión más condensada:

public String getNameFromURI(Uri uri) {
    Cursor c = getContentResolver().query(uri, null, null, null, null);
    c.moveToFirst();
    return c.getString(c.getColumnIndex(OpenableColumns.DISPLAY_NAME));
}
artdeell
fuente
1
no te olvides de cerrar el cursor :)
BekaBot
4
public String getFilename() 
{
/*  Intent intent = getIntent();
    String name = intent.getData().getLastPathSegment();
    return name;*/
    Uri uri=getIntent().getData();
    String fileName = null;
    Context context=getApplicationContext();
    String scheme = uri.getScheme();
    if (scheme.equals("file")) {
        fileName = uri.getLastPathSegment();
    }
    else if (scheme.equals("content")) {
        String[] proj = { MediaStore.Video.Media.TITLE };
        Uri contentUri = null;
        Cursor cursor = context.getContentResolver().query(uri, proj, null, null, null);
        if (cursor != null && cursor.getCount() != 0) {
            int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.TITLE);
            cursor.moveToFirst();
            fileName = cursor.getString(columnIndex);
        }
    }
    return fileName;
}
Sujith Manjavana
fuente
esto no funcionó en mi caso, pero la respuesta de Vasanth sí.
Nabzi
2
String Fpath = getPath(this, uri) ;
File file = new File(Fpath);
String filename = file.getName();
usuario5233139
fuente
4
¿Podrías explicar un poco qué hace tu código? De esta manera, su respuesta será más útil para otros usuarios que se encuentran con este problema. Gracias
wmk
por alguna razón, esto no funciona en marshmellow. Solo emite "audio y algunos números"
Alex
1

Mi versión de la respuesta es en realidad muy similar a la de @Stefan Haustein. Encontré la respuesta en la página del desarrollador de Android Recuperando información de archivo ; la información aquí está aún más condensada sobre este tema específico que en el sitio de la guía Storage Access Framework . En el resultado de la consulta, el índice de la columna que contiene el nombre del archivo es OpenableColumns.DISPLAY_NAME. Ninguna de las otras respuestas / soluciones para índices de columna funcionó para mí. A continuación se muestra la función de muestra:

 /**
 * @param uri uri of file.
 * @param contentResolver access to server app.
 * @return the name of the file.
 */
def extractFileName(uri: Uri, contentResolver: ContentResolver): Option[String] = {

    var fileName: Option[String] = None
    if (uri.getScheme.equals("file")) {

        fileName = Option(uri.getLastPathSegment)
    } else if (uri.getScheme.equals("content")) {

        var cursor: Cursor = null
        try {

            // Query the server app to get the file's display name and size.
            cursor = contentResolver.query(uri, null, null, null, null)

            // Get the column indexes of the data in the Cursor,
            // move to the first row in the Cursor, get the data.
            if (cursor != null && cursor.moveToFirst()) {

                val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
                fileName = Option(cursor.getString(nameIndex))
            }

        } finally {

            if (cursor != null) {
                cursor.close()
            }

        }

    }

    fileName
}
RenatoIvancic
fuente
1

Primero, debe convertir su URIobjeto en URLobjeto y luego usar el Fileobjeto para recuperar un nombre de archivo:

try
    {
        URL videoUrl = uri.toURL();
        File tempFile = new File(videoUrl.getFile());
        String fileName = tempFile.getName();
    }
    catch (Exception e)
    {

    }

Eso es, muy fácil.

Ayaz Alifov
fuente
1

Función de Stefan Haustein para xamarin / c #:

public string GetFilenameFromURI(Android.Net.Uri uri)
        {
            string result = null;
            if (uri.Scheme == "content")
            {
                using (var cursor = Application.Context.ContentResolver.Query(uri, null, null, null, null))
                {
                    try
                    {
                        if (cursor != null && cursor.MoveToFirst())
                        {
                            result = cursor.GetString(cursor.GetColumnIndex(OpenableColumns.DisplayName));
                        }
                    }
                    finally
                    {
                        cursor.Close();
                    }
                }
            }
            if (result == null)
            {
                result = uri.Path;
                int cut = result.LastIndexOf('/');
                if (cut != -1)
                {
                    result = result.Substring(cut + 1);
                }
            }
            return result;
        }
Stefan Michev
fuente
1

Si desea tener el nombre del archivo con extensión, utilizo esta función para obtenerlo. También funciona con selecciones de archivos de Google Drive

public static String getFileName(Uri uri) {
    String result;

    //if uri is content
    if (uri.getScheme() != null && uri.getScheme().equals("content")) {
        Cursor cursor = global.getInstance().context.getContentResolver().query(uri, null, null, null, null);
        try {
            if (cursor != null && cursor.moveToFirst()) {
                //local filesystem
                int index = cursor.getColumnIndex("_data");
                if(index == -1)
                    //google drive
                    index = cursor.getColumnIndex("_display_name");
                result = cursor.getString(index);
                if(result != null)
                    uri = Uri.parse(result);
                else
                    return null;
            }
        } finally {
            cursor.close();
        }
    }

    result = uri.getPath();

    //get filename + ext of path
    int cut = result.lastIndexOf('/');
    if (cut != -1)
        result = result.substring(cut + 1);
    return result;
}
save_jeff
fuente
1

Esto realmente funcionó para mí:

private String uri2filename() {

    String ret;
    String scheme = uri.getScheme();

    if (scheme.equals("file")) {
        ret = uri.getLastPathSegment();
    }
    else if (scheme.equals("content")) {
        Cursor cursor = getContentResolver().query(uri, null, null, null, null);
        if (cursor != null && cursor.moveToFirst()) {
            ret = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
        }
   }
   return ret;
}
José Manuel Gómez Álvarez
fuente
0

Por favor intente esto:

  private String displayName(Uri uri) {

             Cursor mCursor =
                     getApplicationContext().getContentResolver().query(uri, null, null, null, null);
             int indexedname = mCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
             mCursor.moveToFirst();
             String filename = mCursor.getString(indexedname);
             mCursor.close();
             return filename;
 }
Mamour Ndiaye
fuente
0

Combinación de todas las respuestas

Esto es a lo que he llegado después de leer todas las respuestas presentadas aquí, así como lo que algunos Airgram han hecho en sus SDK: una utilidad que he abierto en Github:

https://github.com/mankum93/UriUtilsAndroid/tree/master/app/src/main/java/com/androiduriutils

Uso

Tan simple como llamar UriUtils.getDisplayNameSize(). Proporciona tanto el nombre como el tamaño del contenido.

Nota: solo funciona con un contenido: // Uri

Aquí hay un vistazo al código:

/**
 * References:
 * - https://www.programcreek.com/java-api-examples/?code=MLNO/airgram/airgram-master/TMessagesProj/src/main/java/ir/hamzad/telegram/MediaController.java
 * - /programming/5568874/how-to-extract-the-file-name-from-uri-returned-from-intent-action-get-content
 *
 * @author [email protected]/2HjxA0C
 * Created on: 03-07-2020
 */
public final class UriUtils {


    public static final int CONTENT_SIZE_INVALID = -1;

    /**
     * @param context context
     * @param contentUri content Uri, i.e, of the scheme <code>content://</code>
     * @return The Display name and size for content. In case of non-determination, display name
     * would be null and content size would be {@link #CONTENT_SIZE_INVALID}
     */
    @NonNull
    public static DisplayNameAndSize getDisplayNameSize(@NonNull Context context, @NonNull Uri contentUri){

        final String scheme = contentUri.getScheme();
        if(scheme == null || !scheme.equals(ContentResolver.SCHEME_CONTENT)){
            throw new RuntimeException("Only scheme content:// is accepted");
        }

        final DisplayNameAndSize displayNameAndSize = new DisplayNameAndSize();
        displayNameAndSize.size = CONTENT_SIZE_INVALID;

        String[] projection = new String[]{MediaStore.Images.Media.DATA, OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE};
        Cursor cursor = context.getContentResolver().query(contentUri, projection, null, null, null);
        try {
            if (cursor != null && cursor.moveToFirst()) {

                // Try extracting content size

                int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
                if (sizeIndex != -1) {
                    displayNameAndSize.size = cursor.getLong(sizeIndex);
                }

                // Try extracting display name
                String name = null;

                // Strategy: The column name is NOT guaranteed to be indexed by DISPLAY_NAME
                // so, we try two methods
                int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
                if (nameIndex != -1) {
                    name = cursor.getString(nameIndex);
                }

                if (nameIndex == -1 || name == null) {
                    nameIndex = cursor.getColumnIndex(MediaStore.Images.Media.DATA);
                    if (nameIndex != -1) {
                        name = cursor.getString(nameIndex);
                    }
                }
                displayNameAndSize.displayName = name;
            }
        }
        finally {
            if(cursor != null){
                cursor.close();
            }
        }

        // We tried querying the ContentResolver...didn't work out
        // Try extracting the last path segment
        if(displayNameAndSize.displayName == null){
            displayNameAndSize.displayName = contentUri.getLastPathSegment();
        }

        return displayNameAndSize;
    }
}
Manish Kumar Sharma
fuente