Android: Abrufen einer Datei-URI von einer Inhalts-URI?

131

In meiner App muss der Benutzer eine Audiodatei auswählen, die die App dann verarbeitet. Das Problem ist, dass die URI im Dateiformat vorliegen muss, damit die App mit den Audiodateien das tun kann, was ich möchte. Wenn ich den nativen Musik-Player von Android verwende, um in der App nach der Audiodatei zu suchen, ist der URI ein Inhalts-URI, der folgendermaßen aussieht:

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

Mit der beliebten Dateimanager-Anwendung Astro erhalte ich jedoch Folgendes:

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

Letzteres ist für mich viel zugänglicher, aber ich möchte natürlich, dass die App über Funktionen für die Audiodatei verfügt, die der Benutzer auswählt, unabhängig davon, mit welchem ​​Programm er seine Sammlung durchsucht. Meine Frage ist also, gibt es eine Möglichkeit, den content://Stil-URI in einen file://URI zu konvertieren ? Was würden Sie mir sonst empfehlen, um dieses Problem zu lösen? Hier ist der Code, der die Auswahl als Referenz aufruft:

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

Ich mache Folgendes mit der Inhalts-URI:

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

Dann mache ein paar FileInputStream-Sachen mit dieser Datei.

JMRboosties
quelle
1
Welche Aufrufe verwenden Sie, die keine Inhalts-URIs mögen?
Phil Lello
1
Es gibt eine Menge von Inhalten Uris , wo man nicht den Dateipfad bekommen, weil nicht alle Inhalte Uris haben Dateipfade. Verwenden Sie keine Dateipfade.
Mooing Duck

Antworten:

151

Verwenden Sie einfach getContentResolver().openInputStream(uri), um eine InputStreamvon einer URI zu erhalten.

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

Jason LeBrun
quelle
12
Überprüfen Sie das Schema der URI, die Sie von der Auswahlaktivität erhalten haben. Wenn uri.getScheme.equals ("content"), öffnen Sie es mit einem Content Resolver. Wenn uri.Scheme.equals ("Datei"), öffnen Sie es mit normalen Dateimethoden. In beiden Fällen erhalten Sie einen InputStream, den Sie mit allgemeinem Code verarbeiten können.
Jason LeBrun
16
Eigentlich habe ich gerade die Dokumente für getContentResolver (). OpenInputStream () erneut gelesen und es funktioniert automatisch für Schemata mit "Inhalt" oder "Datei", sodass Sie das Schema nicht überprüfen müssen ... wenn Sie sicher können Angenommen, es wird immer content: // oder file: // sein, dann funktioniert openInputStream () immer.
Jason LeBrun
57
Gibt es eine Möglichkeit, die Datei anstelle des InputStreams abzurufen (aus dem Inhalt: ...)?
AlikElzin-Kilaka
4
@kilaka Sie können den Dateipfad erhalten, aber es ist schmerzhaft. Siehe stackoverflow.com/a/20559418/294855
Danyal Aytekin
7
Diese Antwort ist nicht ausreichend für jemanden, der eine Closed-Source-API verwendet, die sich auf Dateien anstatt auf FileStreams stützt, aber dennoch das Betriebssystem verwenden möchte, damit der Benutzer die Datei auswählen kann. Die Antwort, auf die @DanyalAytekin verwies, war genau das, was ich brauchte (und tatsächlich konnte ich viel Fett abschneiden, weil ich genau weiß, mit welchen Arten von Dateien ich arbeite).
monkey0506
45

Dies ist eine alte Antwort mit veralteter und hackiger Methode, um einige spezifische Schwachstellen von Content Resolver zu überwinden. Nehmen Sie es mit einigen riesigen Salzkörnern und verwenden Sie nach Möglichkeit die richtige openInputStream-API.

Mit dem Content Resolver können Sie einen file://Pfad vom content://URI abrufen:

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
quelle
1
Danke, das hat perfekt funktioniert. Ich konnte keinen InputStream verwenden, wie es die akzeptierte Antwort nahelegt.
ldam
4
Dies funktioniert nur für lokale Dateien, z. B. nicht für Google Drive
Bluewhile
1
Manchmal funktioniert, manchmal wird die Datei: /// storage / emulated / 0 / ... zurückgegeben, die nicht vorhanden ist.
Reza Mohammadi
1
Ist die Spalte "_data" ( android.provider.MediaStore.Images.ImageColumns.DATA) immer garantiert, wenn das Schema vorhanden ist content://?
Edward Falk
2
Dies ist ein wichtiges Anti-Muster. Einige ContentProvider bieten diese Spalte an, aber es ist nicht garantiert, dass Sie Lese- / Schreibzugriff haben, Filewenn Sie versuchen, ContentResolver zu umgehen. Verwenden Sie ContentResolver-Methoden, um mit Uris zu arbeiten content://. Dies ist der offizielle Ansatz, der von Google-Ingenieuren empfohlen wird.
user1643723
9

Wenn Sie einen Inhalts-Uri haben content://com.externalstorage..., können Sie diese Methode verwenden, um den absoluten Pfad eines Ordners oder einer Datei unter Android 19 oder höher abzurufen .

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

Sie können überprüfen, ob jeder Teil von Uri println verwendet. Die zurückgegebenen Werte für meine SD-Karte und den Hauptspeicher des Geräts sind unten aufgeführt. Sie können darauf zugreifen und löschen, wenn sich die Datei im Speicher befindet, aber ich konnte die Datei mit dieser Methode nicht von der SD-Karte löschen, sondern nur das Bild über diesen absoluten Pfad lesen oder öffnen. Wenn Sie eine Lösung zum Löschen mit dieser Methode gefunden haben, teilen Sie diese bitte mit. SD-KARTE

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

HAUPTERINNERUNG

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

Wenn Sie Uri erhalten möchten, file:///nachdem Sie den Pfad verwendet haben

DocumentFile documentFile = DocumentFile.fromFile(new File(path));
documentFile.getUri() // will return a Uri with file Uri
Thraker
quelle
Ich denke nicht, dass dies der richtige Weg ist, dies in einer Anwendung zu tun. Leider
benutze
@Bondax Ja, Sie sollten mit Content Uris anstelle von Dateipfaden oder File Uris arbeiten. Auf diese Weise wird das Storage Access Framework eingeführt. Wenn Sie jedoch die Datei uri erhalten möchten, ist dies die korrekteste Methode für andere Antworten, da Sie die DocumentsContract-Klasse verwenden. Wenn Sie Google-Beispiele in Github auschecken, werden Sie feststellen, dass sie auch für diese Klasse verwendet werden, um Unterordner eines Ordners abzurufen.
Thracian
Und DocumentFile-Klasse auch neu in API 19 und wie Sie Uris von SAF verwenden. Der richtige Weg ist, einen Standardpfad für Ihre App zu verwenden und den Benutzer zu bitten, die Berechtigung für einen Ordner über die SAF-Benutzeroberfläche zu erteilen, die Uri-Zeichenfolge in den freigegebenen Einstellungen zu speichern und bei Bedarf auf einen Ordner mit DocumentFile-Objekten zuzugreifen
Thracian
7

Der Versuch, den URI mit dem Schema content: // durch Aufrufen zu behandeln, ContentResolver.query()ist keine gute Lösung. Unter HTC Desire unter 4.2.2 kann NULL als Abfrageergebnis angezeigt werden.

Warum nicht stattdessen ContentResolver verwenden? https://stackoverflow.com/a/29141800/3205334

Darth Raven
quelle
Aber manchmal brauchen wir nur den Weg. Wir müssen die Datei nicht wirklich in den Speicher laden.
Kimi Chiu
2
"Der Pfad" ist nutzlos, wenn Sie keine Zugriffsrechte dafür haben. Wenn Sie beispielsweise von einer Anwendung eine content://URL erhalten, die der Datei in ihrem privaten internen Verzeichnis entspricht, können Sie diese URL Filein neuen Android-Versionen nicht mit APIs verwenden. ContentResolver wurde entwickelt, um diese Art von Sicherheitsbeschränkungen zu überwinden. Wenn Sie die URL von ContentResolver erhalten haben, können Sie davon ausgehen, dass sie nur funktioniert.
user1643723
5

Inspirierte Antworten sind Jason LaBrun & Darth Raven . Das Ausprobieren bereits beantworteter Ansätze führte mich zu der folgenden Lösung, die hauptsächlich Cursor-Null-Fälle und die Konvertierung von Inhalt: // in Datei: // abdeckt.

Um eine Datei zu konvertieren, lesen und schreiben Sie die Datei von gewonnenem 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);
}

Um die Verwendung des Dateinamens zu erhalten, wird der Cursor-Null-Fall behandelt

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

Es gibt eine gute Option zum Konvertieren vom MIME-Typ in die Dateierweiterung

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

Cursor, um den Namen der Datei zu erhalten

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
quelle
Danke, ich habe mir das eine Woche lang angesehen. Ich mag es nicht, eine Datei dafür kopieren zu müssen, aber es funktioniert.
justdan0227
3

Nun, ich bin etwas spät dran zu antworten, aber mein Code wird getestet

Prüfschema von 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);
        }

In der obigen Antwort habe ich die Video-URL in ein Byte-Array konvertiert, aber das hat nichts mit der Frage zu tun. Ich habe nur meinen vollständigen Code kopiert, um die Verwendung von zu zeigen, FileInputStreamund InputStreamda beide in meinem Code gleich funktionieren.

Ich habe den variablen Kontext getActivity () in meinem Fragment verwendet und in Activity ist es einfach ActivityName.this

context=getActivity(); // im Fragment

context=ActivityName.this;// in Aktivität

Umar Ata
quelle
Ich weiß, dass es nicht mit der Frage zusammenhängt, aber wie würden Sie dann die verwenden byte[] videoBytes;? Die meisten Antworten zeigen nur die Verwendung InputStreammit einem Bild.
KRK