Sui dispositivi con Android 4.4 (livello API 19) e versioni successive, la tua app può interagire con un fornitore di documenti, inclusi i volumi di archiviazione esterni e quelli basati su cloud, Framework di accesso. Questo framework consente agli utenti di interagire con un selettore di sistema per scegliere un fornitore di documenti e selezionare documenti e altri file specifici creare, aprire o modificare l'app.
Poiché l'utente è coinvolto nella selezione dei file o delle directory che la tua app possono accedere, questo meccanismo non richiede alcun sistema autorizzazioni, nonché controllo e privacy degli utenti viene migliorato. Inoltre, questi file, che sono archiviati al di fuori di un directory specifica dell'app e all'esterno del media store, rimangono sul dispositivo dopo la disinstallazione dell'app.
L'utilizzo del framework prevede i seguenti passaggi:
- Un'app richiama un intent che contiene un'azione relativa allo spazio di archiviazione. Questa azione corrisponde a uno specifico caso d'uso reso dal framework disponibili.
- L'utente vede un selettore di sistema che consente di sfogliare un fornitore di documenti e scegli una posizione o un documento in cui viene eseguita l'azione relativa allo spazio di archiviazione.
- L'app ottiene l'accesso in lettura e scrittura a un URI che rappresenta posizione o documento scelto. Utilizzando questo URI, l'app può eseguire operazioni la località scelta.
Per supportare l'accesso ai file multimediali sui dispositivi con Android 9 (livello API 28) o
dichiara il valore
READ_EXTERNAL_STORAGE
e imposta maxSdkVersion
su 28
.
Questa guida illustra i diversi casi d'uso supportati dal framework lavorare con file e altri documenti. Spiega inoltre come eseguire operazioni sulla località selezionata dall'utente.
Casi d'uso per l'accesso a documenti e altri file
Storage Access Framework supporta i seguenti casi d'uso per l'accesso file e altri documenti.
- Creare un nuovo file
- Il
ACTION_CREATE_DOCUMENT
azione intent consente agli utenti di salvare un file in una posizione specifica. - Aprire un documento o un file
- Il
ACTION_OPEN_DOCUMENT
azione intent consente agli utenti di selezionare un documento o un file specifico da aprire. - Concedere l'accesso ai contenuti di una directory
- Il
ACTION_OPEN_DOCUMENT_TREE
intent, disponibile su Android 5.0 (livello API 21) e versioni successive, che consente agli utenti di selezionare una directory specifica, concedendo alla tua applicazione l'accesso a tutti i file nelle sottodirectory di questa directory.
Le sezioni seguenti forniscono indicazioni su come configurare ogni caso d'uso.
Crea un nuovo file
Utilizza la
ACTION_CREATE_DOCUMENT
per intent per caricare il selettore file di sistema e consentire all'utente di scegliere un
posizione in cui scrivere i contenuti di un file. Questa procedura è simile alla
quella utilizzata nell'impostazione "Salva con nome" utilizzate da altri sistemi operativi.
Nota: ACTION_CREATE_DOCUMENT
non può sovrascrivere un
file esistente. Se la tua app tenta di salvare un file con lo stesso nome, il sistema
aggiunge un numero tra parentesi alla fine del nome del file.
Ad esempio, se la tua app tenta di salvare un file denominato
confirmation.pdf
in una directory in cui è già presente un file con questo
, il sistema salva il nuovo file con il nome
confirmation(1).pdf
.
Quando configuri l'intent, specifica il nome e il tipo MIME del file,
facoltativamente specificare l'URI del file o della directory che il selettore file deve
al momento del caricamento iniziale, utilizzando
EXTRA_INITIAL_URI
intent extra.
Il seguente snippet di codice mostra come creare e richiamare l'intent per per creare un file:
Kotlin
// Request code for creating a PDF document. const val CREATE_FILE = 1 private fun createFile(pickerInitialUri: Uri) { val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) type = "application/pdf" putExtra(Intent.EXTRA_TITLE, "invoice.pdf") // Optionally, specify a URI for the directory that should be opened in // the system file picker before your app creates the document. putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri) } startActivityForResult(intent, CREATE_FILE) }
Java
// Request code for creating a PDF document. private static final int CREATE_FILE = 1; private void createFile(Uri pickerInitialUri) { Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("application/pdf"); intent.putExtra(Intent.EXTRA_TITLE, "invoice.pdf"); // Optionally, specify a URI for the directory that should be opened in // the system file picker when your app creates the document. intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri); startActivityForResult(intent, CREATE_FILE); }
Apri un file
La tua app potrebbe utilizzare i documenti come unità di archiviazione in cui gli utenti inseriscono i dati che potrebbero voler condividere con i colleghi o importare in altri documenti. Diversi ad esempio quando un utente apre un documento di produttività o un libro come file EPUB.
In questi casi, consenti all'utente di scegliere il file da aprire richiamando il metodo
ACTION_OPEN_DOCUMENT
che apre l'app del selettore file del sistema. Per mostrare solo i tipi di
supportati dalla tua app, specifica un tipo MIME. Inoltre, puoi facoltativamente
specifica l'URI del file che il selettore file deve visualizzare alla prima volta
viene caricato utilizzando
EXTRA_INITIAL_URI
intent extra.
Il seguente snippet di codice mostra come creare e richiamare l'intent per l'apertura un documento PDF:
Kotlin
// Request code for selecting a PDF document. const val PICK_PDF_FILE = 2 fun openFile(pickerInitialUri: Uri) { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) type = "application/pdf" // Optionally, specify a URI for the file that should appear in the // system file picker when it loads. putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri) } startActivityForResult(intent, PICK_PDF_FILE) }
Java
// Request code for selecting a PDF document. private static final int PICK_PDF_FILE = 2; private void openFile(Uri pickerInitialUri) { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("application/pdf"); // Optionally, specify a URI for the file that should appear in the // system file picker when it loads. intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri); startActivityForResult(intent, PICK_PDF_FILE); }
Limitazioni di accesso
Su Android 11 (livello API 30) e versioni successive, non puoi utilizzare
ACTION_OPEN_DOCUMENT
azione di intent per richiedere all'utente di selezionare una persona
i file provenienti dalle seguenti directory:
- La directory
Android/data/
e tutte le sottodirectory. - La directory
Android/obb/
e tutte le sottodirectory.
Concedere l'accesso ai contenuti di una directory
Le app di gestione dei file e di creazione di contenuti multimediali in genere gestiscono gruppi di file in un
nella gerarchia delle directory. Per fornire questa funzionalità nella tua app, usa
ACTION_OPEN_DOCUMENT_TREE
azione intent, che consente all'utente di concedere l'accesso a un'intera directory
con alcune eccezioni a partire da Android 11 (livello API 30). La tua app può
quindi accedere a qualsiasi file nella directory selezionata e nelle sue sottodirectory.
Quando usi ACTION_OPEN_DOCUMENT_TREE
, la tua app ottiene l'accesso soltanto alle
file nella directory selezionata dall'utente. Non hai accesso ad altre
app che risiedono all'esterno di questa directory selezionata dall'utente. Questo
questo tipo di accesso consente agli utenti di scegliere esattamente quali contenuti
la condivisione con la tua app.
Facoltativamente, puoi specificare l'URI della directory che deve essere utilizzata dal selettore file
al momento del caricamento iniziale, utilizzando
EXTRA_INITIAL_URI
intent extra.
Il seguente snippet di codice mostra come creare e richiamare l'intent per l'apertura una directory:
Kotlin
fun openDirectory(pickerInitialUri: Uri) { // Choose a directory using the system's file picker. val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply { // Optionally, specify a URI for the directory that should be opened in // the system file picker when it loads. putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri) } startActivityForResult(intent, your-request-code) }
Java
public void openDirectory(Uri uriToLoad) { // Choose a directory using the system's file picker. Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); // Optionally, specify a URI for the directory that should be opened in // the system file picker when it loads. intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uriToLoad); startActivityForResult(intent, your-request-code); }
Limitazioni di accesso
Su Android 11 (livello API 30) e versioni successive, non puoi utilizzare
ACTION_OPEN_DOCUMENT_TREE
azione di intent per richiedere l'accesso a quanto segue
directory:
- La directory principale del volume della memoria interna.
- La directory principale di ogni volume della scheda SD che il produttore del dispositivo considera affidabile, indipendentemente dal fatto che la carta sia emulata o rimovibile. Un volume affidabile è un volume al quale un'app può accedere correttamente alla maggior parte dei l'ora.
- La directory
Download
.
Inoltre, su Android 11 (livello API 30) e versioni successive, non puoi utilizzare
ACTION_OPEN_DOCUMENT_TREE
azione di intent per richiedere all'utente di selezionare
singoli file dalle seguenti directory:
- La directory
Android/data/
e tutte le sottodirectory. - La directory
Android/obb/
e tutte le sottodirectory.
Esegui operazioni nella località scelta
Dopo che l'utente ha selezionato un file o una directory utilizzando il selettore file del sistema,
puoi recuperare l'URI dell'elemento selezionato utilizzando il seguente codice in
onActivityResult()
:
Kotlin
override fun onActivityResult( requestCode: Int, resultCode: Int, resultData: Intent?) { if (requestCode == your-request-code && resultCode == Activity.RESULT_OK) { // The result data contains a URI for the document or directory that // the user selected. resultData?.data?.also { uri -> // Perform operations on the document using its URI. } } }
Java
@Override public void onActivityResult(int requestCode, int resultCode, Intent resultData) { if (requestCode == your-request-code && resultCode == Activity.RESULT_OK) { // The result data contains a URI for the document or directory that // the user selected. Uri uri = null; if (resultData != null) { uri = resultData.getData(); // Perform operations on the document using its URI. } } }
Mediante un riferimento all'URI dell'elemento selezionato, l'app può eseguire diverse operazioni sull'elemento. Ad esempio, puoi accedere ai metadati dell'elemento, modificare l'elemento ed eliminarlo.
Le seguenti sezioni mostrano come completare azioni sui file che l'utente .
Determinare le operazioni supportate da un provider
Fornitori di contenuti diversi consentono lo svolgimento di operazioni differenti
documenti, come copiare o visualizzare la miniatura di un documento. A
determinare quali operazioni sono supportate da un determinato provider, verificare
Document.COLUMN_FLAGS
L'UI dell'app può quindi mostrare solo le opzioni supportate dal fornitore.
Mantieni autorizzazioni
Quando la tua app apre un file per la lettura o la scrittura, il sistema offre all'app Concessione dell'autorizzazione URI per il file, che durerà fino al momento in cui il dispositivo dell'utente si riavvia. Supponiamo, tuttavia, che la tua app sia un'app di modifica delle immagini e che tu voglia agli utenti di poter accedere direttamente alle 5 immagini che hanno modificato più di recente dalla tua app. Se il dispositivo dell'utente è stato riavviato, dovrai inviare all'utente torna al selettore di sistema per trovare i file.
Per mantenere l'accesso ai file tra i riavvii del dispositivo e creare un utente migliore la tua app può "prendere" l'autorizzazione URI permanente concede che di sistema, come mostrato nel seguente snippet di codice:
Kotlin
val contentResolver = applicationContext.contentResolver val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION // Check for the freshest data. contentResolver.takePersistableUriPermission(uri, takeFlags)
Java
final int takeFlags = intent.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); // Check for the freshest data. getContentResolver().takePersistableUriPermission(uri, takeFlags);
Esamina i metadati del documento
Quando hai l'URI di un documento, puoi accedere ai relativi metadati. Questo lo snippet recupera i metadati per un documento specificato dall'URI e li registra:
Kotlin
val contentResolver = applicationContext.contentResolver fun dumpImageMetaData(uri: Uri) { // The query, because it only applies to a single document, returns only // one row. There's no need to filter, sort, or select fields, // because we want all fields for one document. val cursor: Cursor? = contentResolver.query( uri, null, null, null, null, null) cursor?.use { // moveToFirst() returns false if the cursor has 0 rows. Very handy for // "if there's anything to look at, look at it" conditionals. if (it.moveToFirst()) { // Note it's called "Display Name". This is // provider-specific, and might not necessarily be the file name. val displayName: String = it.getString(it.getColumnIndex(OpenableColumns.DISPLAY_NAME)) Log.i(TAG, "Display Name: $displayName") val sizeIndex: Int = it.getColumnIndex(OpenableColumns.SIZE) // If the size is unknown, the value stored is null. But because an // int can't be null, the behavior is implementation-specific, // and unpredictable. So as // a rule, check if it's null before assigning to an int. This will // happen often: The storage API allows for remote files, whose // size might not be locally known. val size: String = if (!it.isNull(sizeIndex)) { // Technically the column stores an int, but cursor.getString() // will do the conversion automatically. it.getString(sizeIndex) } else { "Unknown" } Log.i(TAG, "Size: $size") } } }
Java
public void dumpImageMetaData(Uri uri) { // The query, because it only applies to a single document, returns only // one row. There's no need to filter, sort, or select fields, // because we want all fields for one document. Cursor cursor = getActivity().getContentResolver() .query(uri, null, null, null, null, null); try { // moveToFirst() returns false if the cursor has 0 rows. Very handy for // "if there's anything to look at, look at it" conditionals. if (cursor != null && cursor.moveToFirst()) { // Note it's called "Display Name". This is // provider-specific, and might not necessarily be the file name. String displayName = cursor.getString( cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); Log.i(TAG, "Display Name: " + displayName); int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE); // If the size is unknown, the value stored is null. But because an // int can't be null, the behavior is implementation-specific, // and unpredictable. So as // a rule, check if it's null before assigning to an int. This will // happen often: The storage API allows for remote files, whose // size might not be locally known. String size = null; if (!cursor.isNull(sizeIndex)) { // Technically the column stores an int, but cursor.getString() // will do the conversion automatically. size = cursor.getString(sizeIndex); } else { size = "Unknown"; } Log.i(TAG, "Size: " + size); } } finally { cursor.close(); } }
Apri un documento
Avendo un riferimento all'URI di un documento, puoi aprire un documento e l'elaborazione dei dati. Questa sezione mostra esempi di apertura di una bitmap e di un input flusso di dati.
Bitmap
Il seguente snippet di codice mostra come aprire un
Bitmap
file dato il suo URI:
Kotlin
val contentResolver = applicationContext.contentResolver @Throws(IOException::class) private fun getBitmapFromUri(uri: Uri): Bitmap { val parcelFileDescriptor: ParcelFileDescriptor = contentResolver.openFileDescriptor(uri, "r") val fileDescriptor: FileDescriptor = parcelFileDescriptor.fileDescriptor val image: Bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor) parcelFileDescriptor.close() return image }
Java
private Bitmap getBitmapFromUri(Uri uri) throws IOException { ParcelFileDescriptor parcelFileDescriptor = getContentResolver().openFileDescriptor(uri, "r"); FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor); parcelFileDescriptor.close(); return image; }
Dopo aver aperto la bitmap, puoi visualizzarla in una
ImageView
Stream di input
Il seguente snippet di codice mostra come aprire un oggetto InputStream dato il suo URI. In questo snippet, le righe del file vengono lette in una stringa:
Kotlin
val contentResolver = applicationContext.contentResolver @Throws(IOException::class) private fun readTextFromUri(uri: Uri): String { val stringBuilder = StringBuilder() contentResolver.openInputStream(uri)?.use { inputStream -> BufferedReader(InputStreamReader(inputStream)).use { reader -> var line: String? = reader.readLine() while (line != null) { stringBuilder.append(line) line = reader.readLine() } } } return stringBuilder.toString() }
Java
private String readTextFromUri(Uri uri) throws IOException { StringBuilder stringBuilder = new StringBuilder(); try (InputStream inputStream = getContentResolver().openInputStream(uri); BufferedReader reader = new BufferedReader( new InputStreamReader(Objects.requireNonNull(inputStream)))) { String line; while ((line = reader.readLine()) != null) { stringBuilder.append(line); } } return stringBuilder.toString(); }
Modificare un documento
Puoi utilizzare Storage Access Framework per modificare un documento di testo.
Il seguente snippet di codice sovrascrive i contenuti del documento rappresentato dall'URI fornito:
Kotlin
val contentResolver = applicationContext.contentResolver private fun alterDocument(uri: Uri) { try { contentResolver.openFileDescriptor(uri, "w")?.use { FileOutputStream(it.fileDescriptor).use { it.write( ("Overwritten at ${System.currentTimeMillis()}\n") .toByteArray() ) } } } catch (e: FileNotFoundException) { e.printStackTrace() } catch (e: IOException) { e.printStackTrace() } }
Java
private void alterDocument(Uri uri) { try { ParcelFileDescriptor pfd = getActivity().getContentResolver(). openFileDescriptor(uri, "w"); FileOutputStream fileOutputStream = new FileOutputStream(pfd.getFileDescriptor()); fileOutputStream.write(("Overwritten at " + System.currentTimeMillis() + "\n").getBytes()); // Let the document provider know you're done by closing the stream. fileOutputStream.close(); pfd.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }
Eliminare un documento
Se hai l'URI di un documento e la
Document.COLUMN_FLAGS
contiene
SUPPORTS_DELETE
,
puoi eliminarlo. Ad esempio:
Kotlin
DocumentsContract.deleteDocument(applicationContext.contentResolver, uri)
Java
DocumentsContract.deleteDocument(applicationContext.contentResolver, uri);
Recupera un URI multimediale equivalente
La
getMediaUri()
fornisce un URI del negozio multimediale equivalente ai documenti forniti
l'URI del provider. I due URI si riferiscono allo stesso elemento sottostante. Utilizzo dei contenuti multimediali
store URI, puoi accedere più facilmente ai file multimediali
spazio di archiviazione.
Il metodo getMediaUri()
supporta URI ExternalStorageProvider
. Attivato
Per Android 12 (livello API 31) e versioni successive, il metodo supporta anche
MediaDocumentsProvider
URI.
Aprire un file virtuale
Su Android 7.0 (livello API 25) e versioni successive, la tua app può usare file virtuali
messi a disposizione da Storage Access Framework. Anche se i file virtuali
non hanno una rappresentazione binaria, la tua app può aprire i contenuti
in un tipo di file diverso oppure visualizzandoli utilizzando
Intenzione di ACTION_VIEW
un'azione.
Per aprire i file virtuali, l'app client deve includere una logica speciale per gestire che li rappresentano. Se vuoi ottenere una rappresentazione in byte del file, per visualizzare l'anteprima del file, Ad esempio, dovrai richiedere un tipo MIME alternativo dai documenti o il provider di servizi di terze parti.
Dopo che l'utente ha effettuato la selezione, utilizza l'URI nei dati dei risultati per determinare se il file è virtuale, come mostrato nello snippet di codice seguente:
Kotlin
private fun isVirtualFile(uri: Uri): Boolean { if (!DocumentsContract.isDocumentUri(this, uri)) { return false } val cursor: Cursor? = contentResolver.query( uri, arrayOf(DocumentsContract.Document.COLUMN_FLAGS), null, null, null ) val flags: Int = cursor?.use { if (cursor.moveToFirst()) { cursor.getInt(0) } else { 0 } } ?: 0 return flags and DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT != 0 }
Java
private boolean isVirtualFile(Uri uri) { if (!DocumentsContract.isDocumentUri(this, uri)) { return false; } Cursor cursor = getContentResolver().query( uri, new String[] { DocumentsContract.Document.COLUMN_FLAGS }, null, null, null); int flags = 0; if (cursor.moveToFirst()) { flags = cursor.getInt(0); } cursor.close(); return (flags & DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT) != 0; }
Dopo aver verificato che il documento è un file virtuale, puoi forzare
in un tipo MIME alternativo, come "image/png"
. Il seguente codice
mostra come verificare se un file virtuale può essere rappresentato come un
e, in questo caso, riceve uno stream di input dal file virtuale:
Kotlin
@Throws(IOException::class) private fun getInputStreamForVirtualFile( uri: Uri, mimeTypeFilter: String): InputStream { val openableMimeTypes: Array<String>? = contentResolver.getStreamTypes(uri, mimeTypeFilter) return if (openableMimeTypes?.isNotEmpty() == true) { contentResolver .openTypedAssetFileDescriptor(uri, openableMimeTypes[0], null) .createInputStream() } else { throw FileNotFoundException() } }
Java
private InputStream getInputStreamForVirtualFile(Uri uri, String mimeTypeFilter) throws IOException { ContentResolver resolver = getContentResolver(); String[] openableMimeTypes = resolver.getStreamTypes(uri, mimeTypeFilter); if (openableMimeTypes == null || openableMimeTypes.length < 1) { throw new FileNotFoundException(); } return resolver .openTypedAssetFileDescriptor(uri, openableMimeTypes[0], null) .createInputStream(); }
Risorse aggiuntive
Per ulteriori informazioni su come archiviare e accedere a documenti e altri file, consulta le risorse che seguono.
Campioni
- ActionOpenDocument, disponibili su GitHub.
- ActionOpenDocumentTree, disponibili su GitHub.