Como extrair o nome do arquivo do URI retornado de Intent.ACTION_GET_CONTENT?

98

Estou usando o gerenciador de arquivos de terceiros para escolher um arquivo (PDF, no meu caso) do sistema de arquivos.

É assim que eu inicio a atividade:

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

Isso é o que eu tenho em 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);
            }
        }
    }
}

O snippet de código é emprestado das instruções do Open Intents File Manager disponíveis aqui:
http://www.openintents.org/en/node/829

O objetivo if-elseé compatibilidade com versões anteriores. Eu me pergunto se essa é a melhor maneira de obter o nome do arquivo, pois descobri que outros gerenciadores de arquivos retornam todos os tipos de coisas.

Por exemplo, Documents ToGo retorna algo como o seguinte:

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

em que getContentResolver().query()retorna null.

Para tornar as coisas mais interessantes, o gerenciador de arquivos sem nome (obtive este URI do log do cliente) retornou algo como:

/./sdcard/downloads/.bin


Existe uma maneira preferida de extrair o nome do arquivo do URI ou deve-se recorrer à análise de string?

Viktor Brešan
fonte
Mesma pergunta com talvez uma resposta melhor: stackoverflow.com/questions/8646246/…
Rémi

Respostas:

159

developer.android.com tem um bom exemplo de código para isso: https://developer.android.com/guide/topics/providers/document-provider.html

Uma versão condensada para apenas extrair o nome do arquivo (assumindo que "isto" é uma atividade):

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
fonte
no caso quando você tenta ler mimeType ou nome do arquivo do arquivo da câmera, em primeiro lugar você tem que notificar MediaScanner que irá converter URI do seu arquivo recém-criado a partir file://de content://no onScanCompleted(String path, Uri uri)método stackoverflow.com/a/5815005/2163045
murt
10
Adicionar new String[]{OpenableColumns.DISPLAY_NAME}como um segundo parâmetro à consulta filtrará as colunas para uma solicitação mais eficiente.
JM Lord
OpenableColumns.DISPLAY_NAMEnão funcionou para mim, eu usei em seu MediaStore.Files.FileColumns.TITLElugar.
Dmitry Kopytov
Isso irá travar para vídeos ao criar o cursor com um, java.lang.IllegalArgumentException: Invalid column latitudeinfelizmente. Funciona perfeitamente para fotos!
Lucas P.
44

Estou usando algo assim:

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
fonte
4
Eu executei alguns testes em seu código. No URI retornado pelo Gerenciador de Arquivos OI, IllegalArgumentException é lançada porque a coluna 'título' não existe. On URI returened por Documents ToGo cursor is null. No URI retornado pelo esquema do gerenciador de arquivos desconhecido é (obviamente) nulo.
Viktor Brešan
2
Hmm interessante. Sim, testar o esquema! = Null é uma boa ideia. Na verdade, não acho que TITLE seja o que você deseja. Eu estava usando isso porque certos tipos de mídia no Android (como músicas escolhidas por meio do seletor de música) têm URIs como content: // media / external / audio / media / 78 e eu queria exibir algo mais relevante do que o número de ID. Você pode simplesmente usar uri.getLastPathSegment () como meu código usa para file: // URIs se você tiver um URI como content: //...somefile.pdf
Ken Fehling
1
uri.getLastPathSegment (); - você salvou meu dia :)
Lumis
@Ken Fehling: Isso não funcionou para mim. Funciona quando clico em um arquivo em um navegador de arquivos, mas quando clico em um anexo de e-mail, ainda recebo o conteúdo: // ... coisa. Tentei todas as sugestões aqui sem sorte. Alguma ideia do porquê?
Luis A. Florit
1
Oi, por que cursornão é close()d?
fikr4n
33

Retirado das informações do arquivo de recuperação | Desenvolvedores Android

Recuperando o nome de um arquivo.

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
fonte
1
Eu estive procurando por isso por muito tempo!
dasfima
7
Obrigado. Meu Deus, por que eles têm que tornar uma coisa tão trivial tão irritante?
TylerJames
1
Só funciona com arquivos de conteúdo. Consulte stackoverflow.com/a/25005243/6325722 para o método completo
Johnny Five
17

Se você quiser que seja curto, isso deve funcionar.

Uri uri= data.getData();
File file= new File(uri.getPath());
file.getName();
Samuel
fonte
1
Estou recebendo um nome com file.getName (), mas não é o nome real
Tushar Thakur
1
Meu nome de arquivo é user.jpg, mas estou recebendo "6550" em resposta
Tushar Thakur
1
Ele não retorna o nome do arquivo real. Na verdade, ele retorna a última parte do URL do seu recurso.
Emdadul Sawon
Isso não funciona como um arquivo! Principalmente os URIs precisam ser decodificados pela MediaStore hoje. Motivo pelo qual eu e outros estamos recebendo os números. Porque esses são os IDs desses arquivos.
sud007
Esta foi uma resposta dada há 4 anos. O Android está obviamente mudando a cada versão.
Samuel
17

Maneiras mais fáceis de obter o nome do arquivo:

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

Se eles não derem o nome correto, você deve 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
fonte
7

Eu uso o código abaixo para obter o nome e o tamanho do arquivo do Uri no meu projeto.

/**
 * 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
fonte
Ajudou a resolver meu problema com o carregamento de nomes de arquivos de dados externos ou internos! Muito útil, obrigado!
Brandon,
5

A versão mais 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
fonte
1
não se esqueça de fechar o 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
fonte
isso não funcionou no meu caso, mas a resposta de Vasanth sim.
Nabzi
4

Para Kotlin, você pode usar algo assim:

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
fonte
Boa resposta, mas na minha opinião é melhor inserir estes funs (Context.getFileName e Context.getCursorContent) em um arquivo com o nome 'Context', excluir palavras do objeto FileUtils e usá-lo como extensão, por exemplo: val txtFileName = context.getFileName (uri)
Alexey Simchenko
@LumisD você pode importar essas extensões em qualquer arquivo, sua sugestão está correta, mas eu odeio quando os métodos são globais. Quero ver determinados métodos apenas se os importar e nem sempre. E às vezes eu prefiro ter os mesmos nomes de método e fontes diferentes, então eu importo os que preciso no lugar certo :)
Tamim Attafi
Você pode importá-lo desta forma: import com.example.FileUtils.getFileName ou apenas escreva context.getFileName (uri) e pressione Alt + Enter e seu IDE fará isso por você :)
Tamim Attafi
2
String Fpath = getPath(this, uri) ;
File file = new File(Fpath);
String filename = file.getName();
user5233139
fonte
4
Você poderia explicar um pouco o que seu código faz? Dessa forma, sua resposta será mais útil para outros usuários que estão se deparando com esse problema. Obrigado
wmk
por algum motivo, isso não funciona no marshmellow. Ele apenas exibe "áudio e alguns números"
Alex,
1

Minha versão da resposta é muito semelhante à de @Stefan Haustein. Eu encontrei a resposta na página do desenvolvedor Android, Recuperando informações de arquivos ; as informações aqui são ainda mais condensadas neste tópico específico do que no site do guia Estrutura de acesso ao armazenamento . No resultado da consulta, o índice da coluna que contém o nome do arquivo é OpenableColumns.DISPLAY_NAME. Nenhuma das outras respostas / soluções para índices de coluna funcionou para mim. Abaixo está o exemplo de função:

 /**
 * @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
}
Renato Ivancic
fonte
1

Primeiro, você precisa converter seu URIobjeto em URLobjeto e, em seguida, usar Fileobjeto para recuperar um nome de arquivo:

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

    }

É isso, muito fácil.

Ayaz Alifov
fonte
1

Função 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
fonte
1

Se você quiser ter o nome do arquivo com extensão, uso esta função para obtê-lo. Também funciona com opções de arquivos do 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
fonte
1

Isso realmente funcionou para mim:

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;
}
Jose Manuel Gomez Alvarez
fonte
0

Por favor, tente isto:

  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
fonte
0

Combinação de todas as respostas

Aqui está o que eu cheguei depois de uma leitura de todas as respostas apresentadas aqui, bem como o que alguns Airgram fizeram em seus SDKs - um utilitário que abri no Github:

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

Uso

Tão simples quanto ligar UriUtils.getDisplayNameSize(),. Ele fornece o nome e o tamanho do conteúdo.

Nota: Funciona apenas com um conteúdo: // Uri

Aqui está um vislumbre do 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
fonte