Jak wyodrębnić nazwę pliku z URI zwróconego z Intent.ACTION_GET_CONTENT?

101

Korzystam z menedżera plików innej firmy, aby wybrać plik (w moim przypadku PDF) z systemu plików.

Tak rozpoczynam działanie:

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

Oto, co mam w 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);
            }
        }
    }
}

Fragment kodu jest zapożyczony z instrukcji Open Intents File Manager dostępnych tutaj:
http://www.openintents.org/en/node/829

Celem if-elsejest kompatybilność wsteczna. Zastanawiam się, czy to najlepszy sposób na uzyskanie nazwy pliku, ponieważ odkryłem, że inne menedżery plików zwracają wszelkiego rodzaju rzeczy.

Na przykład Documents ToGo zwracają coś podobnego do następującego:

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

na który getContentResolver().query()wraca null.

Żeby było ciekawiej, nienazwany menedżer plików (dostałem ten URI z dziennika klienta) zwrócił coś takiego:

/./sdcard/downloads/.bin


Czy istnieje preferowany sposób wyodrębnienia nazwy pliku z identyfikatora URI, czy też należy skorzystać z analizy ciągu?

Viktor Brešan
źródło
To samo pytanie z może lepszą odpowiedzią: stackoverflow.com/questions/8646246/ ...
Rémi

Odpowiedzi:

163

developer.android.com ma ładny przykładowy kod do tego: https://developer.android.com/guide/topics/providers/document-provider.html

Wersja skrócona, aby po prostu wyodrębnić nazwę pliku (zakładając, że „to” jest działaniem):

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
źródło
w przypadku, gdy spróbujesz odczytać mimeType lub fileName pliku z kamery, najpierw musisz powiadomić MediaScanner, który skonwertuje URI właśnie utworzonego pliku z file://na content://w onScanCompleted(String path, Uri uri)metodzie stackoverflow.com/a/5815005/2163045
murt
10
Dodanie new String[]{OpenableColumns.DISPLAY_NAME}drugiego parametru do zapytania spowoduje odfiltrowanie kolumn w celu uzyskania bardziej wydajnego żądania.
JM Lord
OpenableColumns.DISPLAY_NAMEnie działa dla mnie, MediaStore.Files.FileColumns.TITLEzamiast tego użyłem .
Dmitry Kopytov
Spowoduje to awarię filmów podczas tworzenia kursora z java.lang.IllegalArgumentException: Invalid column latitudeniestety. Działa jednak idealnie do zdjęć!
Lucas P.
45

Używam czegoś takiego:

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
źródło
4
Przeprowadziłem kilka testów na Twoim kodzie. W przypadku identyfikatora URI zwróconego przez Menedżera plików OI wyjątek IllegalArgumentException jest generowany, ponieważ kolumna „tytuł” ​​nie istnieje. W przypadku URI przywróconego przez Documents ToGo, kursor jest pusty. W przypadku URI zwróconego przez nieznany schemat menedżera plików jest (oczywiście) zerowy.
Viktor Brešan,
2
Hmm interesujące. Tak, testowanie schematu! = Null to dobry pomysł. Właściwie nie sądzę, żeby TITLE był tym, czego chcesz. Używałem tego, ponieważ niektóre typy mediów w Androidzie (takie jak piosenki wybrane za pomocą selektora muzyki) mają identyfikatory URI, takie jak content: // media / external / audio / media / 78 i chciałem wyświetlić coś bardziej odpowiedniego niż numer identyfikacyjny. Możesz po prostu użyć uri.getLastPathSegment () tak, jak mój kod używa dla identyfikatorów file: // URI, jeśli masz identyfikator URI, taki jak content: //...somefile.pdf
Ken Fehling
1
uri.getLastPathSegment (); - uratowałeś mi dzień :)
Lumis
@Ken Fehling: To nie działa dla mnie. Działa, gdy klikam plik w przeglądarce plików, ale kiedy klikam załącznik do wiadomości e-mail, nadal otrzymuję treść: // ... rzecz. Wypróbowałem wszystkie sugestie tutaj bez powodzenia. Każdy pomysł, dlaczego?
Luis A. Florit
1
Cześć, dlaczego cursornie jest close()d?
fikr4n
34

Zaczerpnięte z pobierania informacji o pliku | Programiści Androida

Pobieranie nazwy pliku.

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
źródło
1
Szukałem tego zbyt długo!
dasfima
7
Dziękuję Ci. Dobry Boże, dlaczego muszą robić tak banalną rzecz tak irytującą?
TylerJames
1
Działa tylko z plikami zawartości. Zobacz stackoverflow.com/a/25005243/6325722, aby zapoznać się z pełną metodą
Johnny Five
18

Najłatwiejsze sposoby uzyskania nazwy pliku:

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

Jeśli nie podają ci właściwej nazwy, powinieneś użyć:

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
źródło
Tylko File(uri.path).namedziała, dziękuję!
Sam Chen
17

Jeśli chcesz, żeby był krótki, to powinno działać.

Uri uri= data.getData();
File file= new File(uri.getPath());
file.getName();
Samuel
źródło
1
Otrzymuję nazwę z file.getName (), ale nie jest to rzeczywista nazwa
Tushar Thakur
1
Nazwa mojego pliku to user.jpg, ale w odpowiedzi otrzymuję „6550”
Tushar Thakur,
1
Nie zwraca rzeczywistej nazwy pliku. Właściwie zwraca ostatnią część adresu URL zasobu.
Emdadul Sawon
To nie działa jako plik! Obecnie MediaStore musi dekodować głównie identyfikatory URI. Powód, dla którego ja i inni otrzymujemy liczby. Ponieważ to są identyfikatory tych plików.
sud007
To była odpowiedź udzielona 4 lata temu. Android oczywiście zmienia każdą wersję.
Samuel
7

Używam poniższego kodu, aby uzyskać nazwę pliku i rozmiar pliku z Uri w moim projekcie.

/**
 * 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
źródło
Pomogłem rozwiązać mój problem z ładowaniem nazw plików z danych zewnętrznych lub wewnętrznych! Bardzo przydatne, dziękuję!
Brandon,
6

Najbardziej skondensowana wersja:

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
źródło
1
nie zapomnij zamknąć kursora :)
BekaBot
5

W przypadku Kotlina możesz użyć czegoś takiego:

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
źródło
Dobra odpowiedź, ale moim zdaniem lepiej jest wstawić te funki (Context.getFileName i Context.getCursorContent) do pliku o nazwie 'Context', usunąć obiekt słów FileUtils i użyć go jako rozszerzenia, na przykład: val txtFileName = context.getFileName (uri)
Alexey Simchenko
@LumisD możesz zaimportować te rozszerzenia w dowolnym pliku, twoja sugestia jest poprawna, ale po prostu nienawidzę, gdy metody są globalne. Chcę zobaczyć pewne metody tylko wtedy, gdy je importuję, a nie zawsze. A czasami wolę mieć te same nazwy metod i różne źródła, więc importuję te, których potrzebuję we właściwym miejscu :)
Tamim Attafi
Możesz go zaimportować w ten sposób: zaimportuj com.example.FileUtils.getFileName lub po prostu napisz context.getFileName (uri) i naciśnij Alt + Enter, a Twoje IDE zrobi to za Ciebie :)
Tamim Attafi
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
źródło
to nie zadziałało w moim przypadku, ale odpowiedź Vasantha tak.
Nabzi
2
String Fpath = getPath(this, uri) ;
File file = new File(Fpath);
String filename = file.getName();
user5233139
źródło
4
Czy mógłbyś trochę wyjaśnić, co robi twój kod? W ten sposób Twoja odpowiedź będzie bardziej przydatna dla innych użytkowników, którzy natkną się na ten problem. Dzięki
wmk
z jakiegoś powodu to nie działa na piankę. Po prostu wyprowadza „dźwięk i pewną liczbę”
Alex,
1

Moja wersja odpowiedzi jest w rzeczywistości bardzo podobna do @Stefan Haustein. Znalazłem odpowiedź na stronie programistów Androida Pobieranie informacji o pliku ; informacje tutaj są jeszcze bardziej skondensowane na ten konkretny temat niż w witrynie przewodnika Storage Access Framework . W wyniku zapytania indeks kolumny zawierający nazwę pliku to OpenableColumns.DISPLAY_NAME. Żadne inne odpowiedzi / rozwiązania dotyczące indeksów kolumn nie zadziałały dla mnie. Poniżej znajduje się przykładowa funkcja:

 /**
 * @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
źródło
1

Najpierw musisz przekonwertować URIobiekt na URLobiekt, a następnie użyć Fileobiektu do pobrania nazwy pliku:

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

    }

To wszystko, bardzo łatwe.

Ayaz Alifov
źródło
1

Funkcja Stefana Hausteina dla 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
źródło
1

Jeśli chcesz mieć nazwę pliku z rozszerzeniem, używam tej funkcji, aby ją pobrać. Działa również z wyborem plików z dysku 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
źródło
1

To faktycznie zadziałało dla mnie:

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
źródło
0

Spróbuj tego:

  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
źródło
0

Połączenie wszystkich odpowiedzi

Oto, do czego doszedłem po przeczytaniu wszystkich przedstawionych tutaj odpowiedzi, a także co zrobili niektórzy Airgram w swoich SDK - narzędzie, które otworzyłem na Github:

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

Stosowanie

Tak proste, jak dzwoni UriUtils.getDisplayNameSize(). Zawiera zarówno nazwę, jak i rozmiar treści.

Uwaga: działa tylko z content: // Uri

Oto rzut oka na kod:

/**
 * 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
źródło