Android: Pobieranie identyfikatora URI pliku z identyfikatora URI treści?

137

W mojej aplikacji użytkownik wybiera plik audio, który aplikacja obsługuje. Problem polega na tym, że aby aplikacja robiła to, co chcę, aby robiła z plikami audio, potrzebuję, aby identyfikator URI był w formacie pliku. Kiedy używam natywnego odtwarzacza muzyki Androida do przeglądania pliku audio w aplikacji, URI to identyfikator URI treści, który wygląda następująco:

content://media/external/audio/media/710

Jednak korzystając z popularnej aplikacji do zarządzania plikami Astro, otrzymuję:

file:///sdcard/media/audio/ringtones/GetupGetOut.mp3

Ta ostatnia jest dla mnie znacznie bardziej dostępna do pracy, ale oczywiście chcę, aby aplikacja miała funkcjonalność z plikiem audio, który wybiera użytkownik, niezależnie od programu, którego używa do przeglądania swojej kolekcji. Więc moje pytanie brzmi: czy istnieje sposób na przekonwertowanie content://stylu URI na file://URI? W przeciwnym razie, co byś mi polecił, aby rozwiązać ten problem? Oto kod, który wywołuje selektor, w celach informacyjnych:

Intent ringIntent = new Intent();
ringIntent.setType("audio/mp3");
ringIntent.setAction(Intent.ACTION_GET_CONTENT);
ringIntent.addCategory(Intent.CATEGORY_OPENABLE);
startActivityForResult(Intent.createChooser(ringIntent, "Select Ringtone"), SELECT_RINGTONE);

Wykonuję następujące czynności z identyfikatorem URI treści:

m_ringerPath = m_ringtoneUri.getPath();
File file = new File(m_ringerPath);

Następnie zrób coś FileInputStream z tym plikiem.

JMRboosties
źródło
1
Jakich połączeń używasz, które nie lubią identyfikatorów URI treści?
Phil Lello,
1
Istnieje wiele Uris zawartości, w których nie można uzyskać ścieżki pliku, ponieważ nie wszystkie Uris treści mają ścieżki do plików . Nie używaj ścieżek plików.
Mooing Duck

Odpowiedzi:

159

Po prostu użyj, getContentResolver().openInputStream(uri)aby pobrać InputStreamz URI.

http://developer.android.com/reference/android/content/ContentResolver.html#openInputStream(android.net.Uri)

Jason LeBrun
źródło
13
Sprawdź schemat identyfikatora URI zwrócony z działania wybieracza. Jeśli jeśli uri.getScheme.equals ("content"), otwórz go za pomocą programu do rozpoznawania treści. Jeśli plik uri.Scheme.equals („plik”), otwórz go przy użyciu normalnych metod plików. Tak czy inaczej, otrzymasz InputStream, który możesz przetworzyć za pomocą wspólnego kodu.
Jason LeBrun,
17
Właściwie to właśnie ponownie przeczytałem dokumentację dla metody getContentResolver (). OpenInputStream () i działa ona automatycznie dla schematów „zawartości” lub „pliku”, więc nie musisz sprawdzać schematu ... jeśli możesz bezpiecznie załóżmy, że zawsze będzie to content: // lub file: //, wtedy openInputStream () zawsze będzie działać.
Jason LeBrun,
58
Czy istnieje sposób, aby uzyskać File zamiast InputStream (z treści: ...)?
AlikElzin-kilaka
4
@kilaka Możesz uzyskać ścieżkę do pliku, ale jest to bolesne. Zobacz stackoverflow.com/a/20559418/294855
Danyal Aytekin
7
Ta odpowiedź jest niewystarczająca dla kogoś, kto używa interfejsu API o zamkniętym źródle, który opiera się na plikach, a nie na FileStreams, ale chce użyć systemu operacyjnego, aby umożliwić użytkownikowi wybranie pliku. Odpowiedź, do której odnosiłam się @DanyalAytekin, była dokładnie tym, czego potrzebowałem (i tak naprawdę udało mi się przyciąć dużo tłuszczu, ponieważ dokładnie wiem, z jakimi rodzajami plików pracuję).
monkey0506
46

To stara odpowiedź z przestarzałym i nieuczciwym sposobem na przezwyciężenie niektórych problemów z rozpoznawaniem treści. Weź to z ogromnymi ziarnami soli i użyj odpowiedniego interfejsu API openInputStream, jeśli to w ogóle możliwe.

Możesz użyć narzędzia Content Resolver, aby uzyskać file://ścieżkę z content://identyfikatora URI:

String filePath = null;
Uri _uri = data.getData();
Log.d("","URI = "+ _uri);                                       
if (_uri != null && "content".equals(_uri.getScheme())) {
    Cursor cursor = this.getContentResolver().query(_uri, new String[] { android.provider.MediaStore.Images.ImageColumns.DATA }, null, null, null);
    cursor.moveToFirst();   
    filePath = cursor.getString(0);
    cursor.close();
} else {
    filePath = _uri.getPath();
}
Log.d("","Chosen path = "+ filePath);
Rafael Nobre
źródło
1
Dzięki, to zadziałało idealnie. Nie mogłem użyć InputStream, jak sugeruje zaakceptowana odpowiedź.
ldam
5
Działa to tylko w przypadku plików lokalnych, np. Nie działa na Dysku Google
paulgavrikov
1
Czasami działa, czasami zwraca file: /// storage / emulated / 0 / ... który nie istnieje.
Reza Mohammadi
1
Czy kolumna „_data” ( android.provider.MediaStore.Images.ImageColumns.DATA) zawsze istnieje, jeśli schemat tak jest content://?
Edward Falk
2
To jest główny anty-wzór. Niektórzy dostawcy treści zapewniają tę kolumnę, ale nie ma gwarancji, że będziesz mieć dostęp do odczytu / zapisu Filepodczas próby ominięcia ContentResolver. Używaj metod ContentResolver do operowania na content://URIS, jest to oficjalne podejście, do którego zachęcają inżynierowie Google.
user1643723
10

Jeśli masz z Uri treści content://com.externalstorage..., możesz użyć tej metody, aby uzyskać bezwzględną ścieżkę folderu lub pliku w systemie Android 19 lub nowszym .

public static String getPath(final Context context, final Uri uri) {
    final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;

    // DocumentProvider
    if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
        System.out.println("getPath() uri: " + uri.toString());
        System.out.println("getPath() uri authority: " + uri.getAuthority());
        System.out.println("getPath() uri path: " + uri.getPath());

        // ExternalStorageProvider
        if ("com.android.externalstorage.documents".equals(uri.getAuthority())) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];
            System.out.println("getPath() docId: " + docId + ", split: " + split.length + ", type: " + type);

            // This is for checking Main Memory
            if ("primary".equalsIgnoreCase(type)) {
                if (split.length > 1) {
                    return Environment.getExternalStorageDirectory() + "/" + split[1] + "/";
                } else {
                    return Environment.getExternalStorageDirectory() + "/";
                }
                // This is for checking SD Card
            } else {
                return "storage" + "/" + docId.replace(":", "/");
            }

        }
    }
    return null;
}

Możesz sprawdzić, czy każda część Uri używa println. Zwracane wartości dla mojej karty SD i pamięci głównej urządzenia są wymienione poniżej. Możesz uzyskać dostęp i usunąć plik, jeśli plik znajduje się w pamięci, ale nie byłem w stanie usunąć pliku z karty SD za pomocą tej metody, tylko odczytaj lub otwórz obraz, używając tej bezwzględnej ścieżki. Jeśli znajdziesz rozwiązanie do usunięcia za pomocą tej metody, udostępnij. KARTA SD

getPath() uri: content://com.android.externalstorage.documents/tree/612E-B7BF%3A/document/612E-B7BF%3A
getPath() uri authority: com.android.externalstorage.documents
getPath() uri path: /tree/612E-B7BF:/document/612E-B7BF:
getPath() docId: 612E-B7BF:, split: 1, type: 612E-B7BF

PAMIĘĆ GŁÓWNA

getPath() uri: content://com.android.externalstorage.documents/tree/primary%3A/document/primary%3A
getPath() uri authority: com.android.externalstorage.documents
getPath() uri path: /tree/primary:/document/primary:
getPath() docId: primary:, split: 1, type: primary

Jeśli chcesz uzyskać Uri file:///po uzyskaniu użycia ścieżki

DocumentFile documentFile = DocumentFile.fromFile(new File(path));
documentFile.getUri() // will return a Uri with file Uri
Tracki
źródło
nie sądzę, żeby to był właściwy sposób na zrobienie tego w aplikacji. niestety używam go w
szybkim
@Bondax Tak, powinieneś pracować z Content Uris zamiast ze ścieżkami plików lub File Uris. W ten sposób zostaje wprowadzona struktura dostępu do pamięci masowej. Ale jeśli chcesz uzyskać plik uri, jest to najbardziej poprawny sposób na inne odpowiedzi, ponieważ używasz klasy DocumentsContract. Jeśli sprawdzisz próbki Google na Github, zobaczysz, że używają one również tej klasy do pobierania podfolderów folderu.
Tracki
A klasa DocumentFile także nowy dodatek w API 19 i sposób korzystania z uris z SAF. Prawidłowym sposobem jest użycie standardowej ścieżki dla aplikacji i poproszenie użytkownika o udzielenie pozwolenia na folder za pomocą interfejsu SAF, zapisanie ciągu Uri we wspólnych preferencjach, a gdy jest to potrzebne, dostęp do folderu z obiektami DocumentFile
Tracki,
7

Próba obsłużenia identyfikatora URI ze schematem content: // przez wywołanie ContentResolver.query() nie jest dobrym rozwiązaniem. W HTC Desire z systemem 4.2.2 w wyniku zapytania można uzyskać NULL.

Dlaczego zamiast tego nie użyć ContentResolver? https://stackoverflow.com/a/29141800/3205334

Darth Raven
źródło
Ale czasami potrzebujemy tylko ścieżki. Naprawdę nie musimy ładować pliku do pamięci.
Kimi Chiu
3
„Ścieżka” jest bezużyteczna, jeśli nie masz do niej praw dostępu. Na przykład, jeśli aplikacja poda content://identyfikator URI, odpowiadający plikowi w jej prywatnym katalogu wewnętrznym, nie będzie można używać tego URI z Fileinterfejsami API w nowych wersjach Androida. ContentResolver został zaprojektowany, aby przezwyciężyć tego rodzaju ograniczenia bezpieczeństwa. Jeśli masz uri z ContentResolver, możesz oczekiwać, że po prostu zadziała.
user1643723
7

Inspirujące odpowiedzi to Jason LaBrun i Darth Raven . Próbowanie już odpowiedzi na pytania doprowadziło mnie do poniższego rozwiązania, które może głównie obejmować przypadki zerowe kursora i konwersję z content: // do file: //

Aby przekonwertować plik, przeczytaj i zapisz plik z uzyskanego URI

public static Uri getFilePathFromUri(Uri uri) throws IOException {
    String fileName = getFileName(uri);
    File file = new File(myContext.getExternalCacheDir(), fileName);
    file.createNewFile();
    try (OutputStream outputStream = new FileOutputStream(file);
         InputStream inputStream = myContext.getContentResolver().openInputStream(uri)) {
        FileUtil.copyStream(inputStream, outputStream); //Simply reads input to output stream
        outputStream.flush();
    }
    return Uri.fromFile(file);
}

Aby uzyskać użycie nazwy pliku, zakryje ona wielkość liter kursora

public static String getFileName(Uri uri) {
    String fileName = getFileNameFromCursor(uri);
    if (fileName == null) {
        String fileExtension = getFileExtension(uri);
        fileName = "temp_file" + (fileExtension != null ? "." + fileExtension : "");
    } else if (!fileName.contains(".")) {
        String fileExtension = getFileExtension(uri);
        fileName = fileName + "." + fileExtension;
    }
    return fileName;
}

Istnieje dobra opcja konwersji z typu MIME na rozszerzenie pliku

 public static String getFileExtension(Uri uri) {
    String fileType = myContext.getContentResolver().getType(uri);
    return MimeTypeMap.getSingleton().getExtensionFromMimeType(fileType);
}

Kursor do uzyskania nazwy pliku

public static String getFileNameFromCursor(Uri uri) {
    Cursor fileCursor = myContext.getContentResolver().query(uri, new String[]{OpenableColumns.DISPLAY_NAME}, null, null, null);
    String fileName = null;
    if (fileCursor != null && fileCursor.moveToFirst()) {
        int cIndex = fileCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
        if (cIndex != -1) {
            fileName = fileCursor.getString(cIndex);
        }
    }
    return fileName;
}
Muhammed Yalçın Kuru
źródło
Dzięki, przyglądam się temu przez tydzień. Nie lubię kopiować w tym celu pliku, ale to działa.
justdan0227
3

Cóż, spóźniłem się na odpowiedź, ale mój kod jest testowany

sprawdź schemat z uri:

 byte[] videoBytes;

if (uri.getScheme().equals("content")){
        InputStream iStream =   context.getContentResolver().openInputStream(uri);
            videoBytes = getBytes(iStream);
        }else{
            File file = new File(uri.getPath());
            FileInputStream fileInputStream = new FileInputStream(file);     
            videoBytes = getBytes(fileInputStream);
        }

W powyższej odpowiedzi przekonwertowałem wideo uri na tablicę bajtów, ale to nie jest związane z pytaniem, po prostu skopiowałem mój pełny kod, aby pokazać użycie FileInputStreamiInputStream ponieważ oba działają tak samo w moim kodzie.

Użyłem kontekstu zmiennej, którym jest getActivity () w moim fragmencie, aw działaniu jest to po prostu nazwa działania. To

context=getActivity(); // we fragmencie

context=ActivityName.this;// w działaniu

Umar Ata
źródło
Wiem, że nie jest to związane z pytaniem, ale jak byś wtedy użył byte[] videoBytes;? Większość odpowiedzi pokazuje tylko, jak używać InputStreamz obrazem.
KRK
0

Spróbuj tego....

pobierz plik z uri zawartości

fun fileFromContentUri(context: Context, contentUri: Uri): File {
    // Preparing Temp file name
    val fileExtension = getFileExtension(context, contentUri)
    val fileName = "temp_file" + if (fileExtension != null) ".$fileExtension" else ""

    // Creating Temp file
    val tempFile = File(context.cacheDir, fileName)
    tempFile.createNewFile()

    try {
        val oStream = FileOutputStream(tempFile)
        val inputStream = context.contentResolver.openInputStream(contentUri)

        inputStream?.let {
            copy(inputStream, oStream)
        }

        oStream.flush()
    } catch (e: Exception) {
        e.printStackTrace()
    }

    return tempFile
}

private fun getFileExtension(context: Context, uri: Uri): String? {
    val fileType: String? = context.contentResolver.getType(uri)
    return MimeTypeMap.getSingleton().getExtensionFromMimeType(fileType)
}

@Throws(IOException::class)
private fun copy(source: InputStream, target: OutputStream) {
    val buf = ByteArray(8192)
    var length: Int
    while (source.read(buf).also { length = it } > 0) {
        target.write(buf, 0, length)
    }
}
Shaon
źródło