Як витягти ім'я файлу з URI, що повертається з Intent.ACTION_GET_CONTENT?


98

Я використовую сторонній менеджер файлів, щоб вибрати файл (у моєму випадку 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/…
Ремі

Відповіді:


160

developer.android.com має хороший приклад коду для цього: https://developer.android.com/guide/topics/providers/document-provider.html

Скорочена версія, щоб просто витягти ім'я файлу (припускаючи, що "це" є Діяльністю):

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 або fileName файлу з камери, по-перше, ви повинні повідомити MediaScanner, який перетворить URI вашого щойно створеного файлу з file://на content://в onScanCompleted(String path, Uri uri)метод stackoverflow.com/a/5815005/2163045
murt

10
Додавання new String[]{OpenableColumns.DISPLAY_NAME}як другого параметра до запиту відфільтрує стовпці для більш ефективного запиту.
Лорд Дж. М.

OpenableColumns.DISPLAY_NAMEне працював у мене, я використовував MediaStore.Files.FileColumns.TITLEзамість цього.
Дмитро Копитов

Це призведе до збою для відео при створенні курсора з на java.lang.IllegalArgumentException: Invalid column latitudeжаль. Хоча чудово працює для фотографій!
Лукас П.

44

Я використовую щось подібне:

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, оскільки стовпець 'title' не існує. На URI, що повертається Documents ToGo, курсор нульовий. На URI, що повертається невідомим файловим менеджером, схема (очевидно) є нульовою.
Віктор Брешан,

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

1
uri.getLastPathSegment (); - Ти врятував мій день :)
Люміс,

@Ken Fehling: Це мені не вдалося. Це працює, коли я натискаю файл у браузері файлів, але коли я натискаю вкладення електронної пошти, я все одно отримую вміст: // ... річ. Я спробував усі пропозиції тут без удачі. Будь-яка ідея чому?
Луїс А. Флоріт,

1
Привіт, чому cursorце не close()d?
fikr4n

33

Взято з отримання інформації про файл | Розробники 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
Дякую. Господи, чому вони мусять робити такі дріб’язкові речі настільки прикрими?
Тайлер Джеймс,

1
Він працює лише з файлами вмісту. Дивіться stackoverflow.com/a/25005243/6325722 для повного методу
Johnny Five

17

Якщо ви хочете коротше, це має спрацювати.

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

1
Я отримую ім’я за допомогою file.getName (), але це не справжнє ім’я
Tushar Thakur

1
Моє ім'я файлу - user.jpg, але я отримую у відповідь "6550"
Тушар Тхакур,

1
Він не повертає фактичне ім'я файлу. Насправді він повертає останню частину URL-адреси вашого ресурсу.
Emdadul Sawon

Це не працює як файл! Сьогодні MediaStore здебільшого має декодувати URI. Причина, чому я та інші отримуємо цифри. Тому що це ідентифікатори цих файлів.
sud007

Це була відповідь, дана 4 роки тому. Android, очевидно, змінює кожен випуск.
Самуїл

17

Найпростіші способи отримати ім'я файлу:

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
}

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

    }
}

Допоміг вирішити мою проблему із завантаженням імен файлів із зовнішніх або внутрішніх даних! Дуже корисно дякую!
Брендон,

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 }

Хороша відповідь, але, на мій погляд, краще вставити ці funs (Context.getFileName та Context.getCursorContent) у файл з ім'ям "Context", видалити об'єкт слова FileUtils і використовувати його як розширення, наприклад: val txtFileName = context.getFileName (uri)
Олексій Сімченко

@LumisD, ви можете імпортувати ці розширення в будь-який файл, ваша пропозиція правильна, але я просто ненавиджу, коли методи є глобальними. Я хочу бачити певні методи, лише якщо я їх імпортую, і не завжди. І іноді я вважаю за краще мати однакові назви методів та різні джерела, тому імпортую потрібні в потрібному місці :)
Tamim Attafi

Ви можете імпортувати його таким чином: імпортуйте com.example.FileUtils.getFileName або просто напишіть context.getFileName (uri) та натисніть Alt + Enter, і ваша IDE зробить це за вас :)
Tamim Attafi

5

Найзгущеніша версія:

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

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

чомусь це не працює на зефір. Він просто виводить "аудіо та деяке число"
Олексій,

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

Функція Стефана Хауштейна для ксамарину / 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

Якщо ви хочете мати ім'я файлу з розширенням, я використовую цю функцію для його отримання. Він також працює з файлами вибору файлів 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;
}

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(). Він надає як назву, так і розмір вмісту.

Примітка: Працює лише з вмістом: // 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.