Android: ottenere un URI di file da un URI di contenuto?


133

Nella mia app l'utente deve selezionare un file audio che l'app quindi gestisce. Il problema è che, affinché l'app faccia quello che voglio che faccia con i file audio, ho bisogno che l'URI sia in formato file. Quando utilizzo il lettore musicale nativo di Android per cercare il file audio nell'app, l'URI è un URI contenuto, che assomiglia a questo:

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

Tuttavia, usando la popolare applicazione di file manager Astro, ottengo quanto segue:

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

Quest'ultimo è molto più accessibile per me con cui lavorare, ma ovviamente voglio che l'app abbia funzionalità con il file audio che l'utente sceglie indipendentemente dal programma che usano per sfogliare la loro collezione. Quindi la mia domanda è: c'è un modo per convertire l' content://URI di stile in un file://URI? Altrimenti, cosa mi consiglieresti di risolvere questo problema? Ecco il codice che richiama il selettore, come riferimento:

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

Faccio quanto segue con l'URI del contenuto:

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

Quindi fai alcune cose FileInputStream con detto file.


1
Quali chiamate stai utilizzando a cui non piacciono gli URI dei contenuti?
Phil Lello,

1
Ci sono molti contenuti di Uris in cui non è possibile ottenere il percorso del file, perché non tutti i contenuti di Uris hanno percorsi di file . Non utilizzare percorsi di file.
Mooing Duck,

Risposte:


153

Basta usare getContentResolver().openInputStream(uri)per ottenere un InputStreamda un URI.

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


12
Controlla lo schema dell'URI che ti è stato restituito dall'attività di selezione. Se if uri.getScheme.equals ("contenuto"), aprilo con un risolutore di contenuti. Se uri.Scheme.equals ("file"), aprilo usando i normali metodi di file. Ad ogni modo, finirai con un InputStream che puoi elaborare usando un codice comune.
Jason LeBrun,

16
In realtà, ho appena riletto i documenti per getContentResolver (). OpenInputStream (), e funziona automaticamente per schemi di "contenuto" o "file", quindi non è necessario controllare lo schema ... se puoi tranquillamente supponiamo che sarà sempre contenuto: // o file: // quindi openInputStream () funzionerà sempre.
Jason LeBrun,

57
Esiste un modo per ottenere il file anziché InputStream (dal contenuto: ...)?
AlikElzin-Kilaka

4
@kilaka Puoi ottenere il percorso del file ma è doloroso. Vedi stackoverflow.com/a/20559418/294855
Danyal Aytekin

7
Questa risposta non è sufficiente per chi sta utilizzando un'API di origine chiusa che si basa su File anziché su FileStreams, ma desidera comunque utilizzare il sistema operativo per consentire all'utente di selezionare il file. La risposta a cui fa riferimento @DanyalAytekin era esattamente ciò di cui avevo bisogno (e in effetti, sono stato in grado di tagliare molto del grasso perché so esattamente con quali tipi di file sto lavorando).
monkey0506,

45

Questa è una vecchia risposta con il modo deprecato e confuso di superare alcuni punti di dolore risolutore di contenuti specifici. Prendilo con alcuni grossi granelli di sale e usa l'API openInputStream corretta, se possibile.

Puoi utilizzare Content Resolver per ottenere un file://percorso content://dall'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);

1
Grazie, ha funzionato perfettamente. Non ho potuto usare un InputStream come suggerisce la risposta accettata.
ldam

4
Funziona solo con i file locali, ad es. Non funziona con Google Drive
mentre il

1
A volte funziona, a volte restituisce file: /// storage / emulato / 0 / ... che non esiste.
Reza Mohammadi,

1
La colonna "_data" ( android.provider.MediaStore.Images.ImageColumns.DATA) è sempre garantita se lo schema è content://?
Edward Falk

2
Questo è un importante anti-pattern. Alcuni ContentProvider forniscono quella colonna, ma non si ha la garanzia di avere accesso in lettura / scrittura Filequando si tenta di ignorare ContentResolver. Utilizza i metodi ContentResolver per operare content://sull'uride, questo è l'approccio ufficiale, incoraggiato dagli ingegneri di Google.
user1643723

9

Se disponi di un Uri di contenuto content://com.externalstorage..., puoi utilizzare questo metodo per ottenere il percorso assoluto di una cartella o di un file su Android 19 o versioni successive .

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

Puoi controllare che ogni parte di Uri stia usando println. I valori restituiti per la mia scheda SD e la memoria principale del dispositivo sono elencati di seguito. È possibile accedere ed eliminare se il file è in memoria ma non sono stato in grado di eliminare il file dalla scheda SD utilizzando questo metodo, solo leggere o aprire l'immagine usando questo percorso assoluto. Se hai trovato una soluzione da eliminare usando questo metodo, ti preghiamo di condividere. SCHEDA 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

MEMORIA PRINCIPALE

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

Se desideri ottenere Uri con file:///dopo aver utilizzato il percorso

DocumentFile documentFile = DocumentFile.fromFile(new File(path));
documentFile.getUri() // will return a Uri with file Uri

non penso che sia il modo giusto di farlo in un'applicazione. purtroppo lo sto usando in un progetto
veloce

@Bondax Sì, dovresti lavorare con Content Uris invece dei percorsi dei file o File Uris. In questo modo viene introdotto il framework di accesso allo storage. Ma, se si desidera ottenere file uri, questo è il modo più corretto di altre risposte poiché si utilizza la classe DocumentsContract. Se dai un'occhiata agli esempi di Google in Github, vedrai che usano anche questa classe per ottenere sottocartelle di una cartella.
Tracia

E la classe DocumentFile include anche una nuova aggiunta in api 19 e il modo in cui usi l'uride di SAF. Il modo corretto è utilizzare un percorso standard per la tua app e chiedere all'utente di autorizzare una cartella tramite l'interfaccia utente SAF, salvare la stringa Uri nelle preferenze condivise e quando è necessario l'accesso alla cartella con oggetti DocumentFile
Thracian,

7

Cercare di gestire l'URI con content: // schema chiamando ContentResolver.query()non è una buona soluzione. Su HTC Desire in esecuzione 4.2.2 è possibile ottenere NULL come risultato della query.

Perché non usare ContentResolver invece? https://stackoverflow.com/a/29141800/3205334


Ma a volte abbiamo solo bisogno del percorso. Non è necessario caricare il file in memoria.
Kimi Chiu,

2
"Il percorso" è inutile se non si dispone dei diritti di accesso per esso. Ad esempio, se un'applicazione ti dà un content://uri, corrispondente al file nella sua directory interna privata, non sarai in grado di usare quell'URI con le FileAPI nelle nuove versioni di Android. ContentResolver è progettato per superare questo tipo di limiti di sicurezza. Se hai ottenuto l'uri da ContentResolver, puoi aspettarti che funzioni.
user1643723

5

Le risposte ispirate sono Jason LaBrun e Darth Raven . Provare approcci a cui ho già risposto mi ha portato alla soluzione di seguito che può riguardare principalmente casi null del cursore e conversione dal contenuto: // al file: //

Per convertire il file, leggi e scrivi il file dall'uri guadagnato

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

Per ottenere l'uso del nome file, coprirà il caso null del cursore

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

Esiste una buona opzione per la conversione dal tipo MIME all'estensione del file

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

Cursore per ottenere il nome del file

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

Grazie, lo guardo da una settimana. Non mi piace dover copiare un file per questo, ma funziona.
justdan0227,

3

Bene, sono in ritardo per rispondere, ma il mio codice è stato testato

controlla schema da 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);
        }

Nella risposta sopra ho convertito il video uri in array di byte, ma non è correlato alla domanda, ho appena copiato il mio codice completo per mostrare l'utilizzo FileInputStreame InputStreampoiché entrambi funzionano allo stesso modo nel mio codice.

Ho usato il contesto variabile che è getActivity () nel mio frammento e in Activity è semplicemente ActivityName.this

context=getActivity(); // in Frammento

context=ActivityName.this;// in attività


So che non è correlato alla domanda, ma come useresti byte[] videoBytes;? La maggior parte delle risposte mostra solo come utilizzare InputStreamun'immagine.
KRK
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.