Как извлечь имя файла из 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));
}
Artdeell
источник
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();
пользователь5233139
источник
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;
}
save_jeff
источник
Спасибо за ответ, мне это помогло.
Сахават Хосейн
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;
    }
}
Маниш Кумар Шарма
источник