Как извлечь имя файла из URI, возвращенного из Intent.ACTION_GET_CONTENT?


102

Я использую сторонний файловый менеджер, чтобы выбрать файл (в моем случае PDF) из файловой системы.

Вот как я запускаю деятельность:

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

Вот что у меня есть 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);
            }
        }
    }
}

Фрагмент кода заимствован из инструкций диспетчера файлов Open Intents, доступных здесь:
http://www.openintents.org/en/node/829

Цель if-else- обратная совместимость. Интересно, лучший ли это способ получить имя файла, поскольку я обнаружил, что другие файловые менеджеры возвращают все, что угодно.

Например, Documents ToGo возвращает что-то вроде следующего:

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

на который getContentResolver().query()возвращается null.

Чтобы сделать вещи более интересными, безымянный файловый менеджер (я получил этот URI из клиентского журнала) возвращал что-то вроде:

/./sdcard/downloads/.bin


Есть ли предпочтительный способ извлечения имени файла из URI или следует прибегнуть к синтаксическому анализу строк?


Тот же вопрос с, возможно, лучшим ответом: stackoverflow.com/questions/8646246/…
— Реми,

Ответы:


164

На сайте developer.android.com есть хороший пример кода: https://developer.android.com/guide/topics/providers/document-provider.html.

Сжатая версия для простого извлечения имени файла (при условии, что «this» является действием):

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

в случае , когда вы пытаетесь прочитать MIMETYPE или имя файла с камеры, во - первых , вы должны уведомить MediaScanner , который будет преобразовывать URI вашего только что созданного файла с file://к content://в onScanCompleted(String path, Uri uri)метод stackoverflow.com/a/5815005/2163045
— murt

10
Добавление new String[]{OpenableColumns.DISPLAY_NAME}в качестве второго параметра запроса отфильтрует столбцы для более эффективного запроса.
— JM Lord

OpenableColumns.DISPLAY_NAMEу меня не сработало, я использовал MediaStore.Files.FileColumns.TITLE.
— Дмитрий Копытов

Это приведет к сбою для видео при создании курсора с java.lang.IllegalArgumentException: Invalid column latitudeфайлом. Хотя отлично работает для фотографий!
— Лукас П.

45

Я использую что-то вроде этого:

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

4
Я провел несколько тестов на вашем коде. В URI, возвращаемом файловым менеджером OI, генерируется исключение IllegalArgumentException, поскольку заголовок столбца не существует. На URI, восстановленном документами ToGo, курсор равен нулю. URI, возвращаемый неизвестной схемой файлового менеджера, (очевидно) равен нулю.
— Виктор Брешан

2
Хм, интересно. Да, тестирование схемы! = Null - хорошая идея. На самом деле я не думаю, что TITLE - это то, что вам нужно. Я использовал это, потому что определенные типы мультимедиа в Android (например, песни, выбранные с помощью средства выбора музыки) имеют URI, такие как content: // media / external / audio / media / 78, и я хотел отобразить что-то более актуальное, чем идентификационный номер. Вы можете просто использовать uri.getLastPathSegment (), как в моем коде, для URI file: //, если у вас есть URI вроде content: //...somefile.pdf
— Кен Фелинг

1
uri.getLastPathSegment (); - вы спасли мне день :)
— Лумис

@ Кен Фелинг: У меня это не сработало. Он работает, когда я щелкаю файл в файловом браузере, но когда я нажимаю на вложение электронной почты, я все равно получаю сообщение content: // ... Я попробовал все предложения здесь, но безуспешно. Есть идеи, почему?
— Луис А. Флорит

1
Привет, а почему cursorнет close()?
— fikr4n

35

Взято из получения информации о файле | Разработчики Android

Получение имени файла.

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

1
Я слишком долго это искал!
— dasfima

7
Спасибо. Господи, почему они делают такую ​​банальную вещь такой раздражающей?
— TylerJames 02

1
Он работает только с файлами содержимого. См. Stackoverflow.com/a/25005243/6325722 для полного метода
— Johnny Five

18

Самые простые способы получить имя файла:

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

Если они не дают вам правильного имени, вы должны использовать:

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
}

Только File(uri.path).nameработает, спасибо!
— Сэм Чен,

17

Если вы хотите, чтобы это было коротко, это должно сработать.

Uri uri= data.getData();
File file= new File(uri.getPath());
file.getName();

1
Я получаю имя с помощью file.getName (), но это не настоящее имя
— Тушар Такур

1
Мое имя файла - user.jpg, но я получаю в ответ «6550»
— Тушар Такур

1
Он не возвращает фактическое имя файла. На самом деле он возвращает последнюю часть URL-адреса вашего ресурса.
— Эмдадул Савон

Это не работает как файл! Сегодня в MediaStore необходимо декодировать URI. Причина, по которой я и другие получаем числа. Потому что это идентификаторы этих файлов.
— sud007

Это был ответ 4 года назад. Очевидно, что Android меняет каждую версию.
— Самуэль

7

Я использую приведенный ниже код, чтобы получить имя файла и размер файла из Uri в моем проекте.

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

    }
}

Помогли решить мою проблему с загрузкой имен файлов из внешних или внутренних данных! Очень полезное спасибо!
— Брэндон,

6

Самая сжатая версия:

public String getNameFromURI(Uri uri) {
    Cursor c = getContentResolver().query(uri, null, null, null, null);
    c.moveToFirst();
    return c.getString(c.getColumnIndex(OpenableColumns.DISPLAY_NAME));
}

1
не забудьте закрыть курсор :)
— BekaBot

5

Для Kotlin вы можете использовать что-то вроде этого:

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 }

Хороший ответ, но, на мой взгляд, эти забавы (Context.getFileName и Context.getCursorContent) лучше вставить в файл с именем Context, удалить объект слов FileUtils и использовать его как расширение, например: val txtFileName = context.getFileName (uri)
— Алексей Симченко

@LumisD, вы можете импортировать эти расширения в любой файл, ваше предложение правильное, но я просто ненавижу, когда методы являются глобальными. Я хочу видеть определенные методы только в том случае, если я их импортирую, а не всегда. И иногда я предпочитаю иметь одинаковые имена методов и разные источники, поэтому я импортирую те, которые мне нужны в нужном месте :)
— Тамим Аттафи

Вы можете импортировать его следующим образом: import com.example.FileUtils.getFileName или просто напишите context.getFileName (uri) и нажмите Alt + Enter, и ваша IDE сделает это за вас :)
— Тамим Аттафи,

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

в моем случае это не сработало, но ответ Васанта сработал.
— Набзи

2
String Fpath = getPath(this, uri) ;
File file = new File(Fpath);
String filename = file.getName();

4
Не могли бы вы немного объяснить, что делает ваш код? Таким образом, ваш ответ будет более полезным для других пользователей, которые сталкиваются с этой проблемой. Спасибо
— wmk

по какой-то причине это не работает с маршмеллоу. Он просто выводит «аудио и некоторое число»
— Alex

2

Если вы хотите иметь имя файла с расширением, я использую эту функцию, чтобы получить его. Он также работает с выбором файлов на Google Диске

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

Спасибо за ответ, мне это помогло.
— Сахават Хосейн

1

Моя версия ответа на самом деле очень похожа на @Stefan Haustein. Я нашел ответ на странице разработчика Android Получение информации о файле ; информация здесь по этой конкретной теме даже более сжатая, чем на сайте руководства Storage Access Framework . В результате запроса индекс столбца, содержащий имя файла OpenableColumns.DISPLAY_NAME. Ни один из других ответов / решений для индексов столбцов у меня не работал. Ниже приведен пример функции:

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

1

Сначала вам нужно преобразовать ваш URIобъект в URLобъект, а затем использовать Fileобъект для получения имени файла:

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

    }

Это очень просто.


1

Функция Стефана Хаустейна для 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;
        }

1

Это действительно сработало для меня:

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

0

Пожалуйста, попробуйте это:

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

0

Комбинация всех ответов

Вот к чему я пришел после прочтения всех представленных здесь ответов, а также того, что некоторые Airgram сделали в своих SDK - утилита, исходный код которой я открыл на Github:

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

использование

Как просто , как вызов, UriUtils.getDisplayNameSize(). Он предоставляет как имя, так и размер содержимого.

Примечание: Работает только с content: // Uri.

Вот краткий обзор кода:

/**
 * 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 Manish@bit.ly/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;
    }
}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.