Wenn Sie eine App entwickeln, die Speicherdienste für Dateien (z. B. Cloud-Speicherdienst), können Sie Ihre Dateien über das Storage Access Framework (SAF) durch Schreiben eines benutzerdefinierten Dokumentanbieters. Auf dieser Seite wird beschrieben, wie Sie einen benutzerdefinierten Dokumentanbieter erstellen.
Weitere Informationen zum Storage Access Framework finden Sie in der Übersicht über das Storage Access Framework
Manifest
Fügen Sie Folgendes zum Code Ihrer Anwendung hinzu, um einen benutzerdefinierten Dokumentanbieter zu implementieren Manifest:
- Ziel-API-Level 19 oder höher.
- Ein
<provider>
-Element, das Ihren benutzerdefinierten Speicher deklariert Dienstanbieter. -
Das Attribut
android:name
, festgelegt auf den Namen IhresDocumentsProvider
abgeleitete Klasse, Dies ist der Klassenname, einschließlich des Paketnamens:com.example.android.storageprovider.MyCloudProvider
. -
das Attribut
android:authority
Das ist der Name Ihres Pakets (in diesem Beispielcom.example.android.storageprovider
) sowie die Art des Contentanbieters, (documents
) - Das Attribut
android:exported
wurde auf"true"
festgelegt. Du musst deinen Anbieter exportieren, damit andere Apps ihn sehen können. - Das Attribut
android:grantUriPermissions
ist auf"true"
. Mit dieser Einstellung kann das System anderen Apps Zugriff gewähren mit Inhalten bei Ihrem Anbieter. Ich würde gerne mit Ihnen darüber sprechen, ihren Zugriff auf Inhalte von Ihrem Anbieter dauerhaft beibehalten möchten, finden Sie unter Beibehalten Berechtigungen - Die Berechtigung
MANAGE_DOCUMENTS
. Standardmäßig ist ein Anbieter verfügbar für alle. Wenn Sie diese Berechtigung hinzufügen, wird Ihr Anbieter auf das System eingeschränkt. Diese Einschränkung ist aus Sicherheitsgründen wichtig. - Einen Intent-Filter, der das Ereignis
android.content.action.DOCUMENTS_PROVIDER
Aktion, sodass Ihr Anbieter erscheint in der Auswahl, wenn das System nach Anbietern sucht.
Hier sind Auszüge aus einem Beispielmanifest, das einen Anbieter enthält:
<manifest... > ... <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="19" /> .... <provider android:name="com.example.android.storageprovider.MyCloudProvider" android:authorities="com.example.android.storageprovider.documents" android:grantUriPermissions="true" android:exported="true" android:permission="android.permission.MANAGE_DOCUMENTS"> <intent-filter> <action android:name="android.content.action.DOCUMENTS_PROVIDER" /> </intent-filter> </provider> </application> </manifest>
Unterstützt Geräte mit Android 4.3 und niedriger
Die
Intent ACTION_OPEN_DOCUMENT
ist nur verfügbar
auf Geräten mit Android 4.4 und höher.
Wenn Sie möchten, dass Ihre Anwendung ACTION_GET_CONTENT
unterstützt
bis Android 4.3 ausgeführt werden soll,
Intent-Filter ACTION_GET_CONTENT
deaktivieren in
Manifest für Geräte mit Android 4.4 oder höher. A
Dokumentanbieter und ACTION_GET_CONTENT
sollten berücksichtigt werden.
sich gegenseitig ausschließen. Wenn Sie beide gleichzeitig unterstützen,
erscheint zweimal in der Benutzeroberfläche der Systemauswahl und bietet zwei verschiedene Möglichkeiten, auf
Ihre gespeicherten Daten. Das ist verwirrend für die Nutzer.
Mit der folgenden empfohlenen Methode
ACTION_GET_CONTENT
Intent-Filter für Geräte
mit Android-Version 4.4 oder höher:
- Fügen Sie in Ihrer
bool.xml
-Ressourcendatei unterres/values/
Folgendes hinzu: diese Zeile:<bool name="atMostJellyBeanMR2">true</bool>
- Fügen Sie in Ihrer
bool.xml
-Ressourcendatei unterres/values-v19/
Folgendes hinzu: diese Zeile:<bool name="atMostJellyBeanMR2">false</bool>
- Hinzufügen eines
Aktivität
Alias, um den Intent
ACTION_GET_CONTENT
zu deaktivieren nach Version 4.4 (API-Level 19) und höher filtern. Hier einige Beispiele:<!-- This activity alias is added so that GET_CONTENT intent-filter can be disabled for builds on API level 19 and higher. --> <activity-alias android:name="com.android.example.app.MyPicker" android:targetActivity="com.android.example.app.MyActivity" ... android:enabled="@bool/atMostJellyBeanMR2"> <intent-filter> <action android:name="android.intent.action.GET_CONTENT" /> <category android:name="android.intent.category.OPENABLE" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="image/*" /> <data android:mimeType="video/*" /> </intent-filter> </activity-alias>
Verträge
Wenn Sie einen benutzerdefinierten Contentanbieter schreiben, ist eine der Aufgaben
Implementierung von Vertragsklassen, wie in den
<ph type="x-smartling-placeholder"></ph>
Entwicklerleitfaden für Contentanbieter. Eine Vertragsklasse ist eine public final
-Klasse.
die konstante Definitionen für die URIs, Spaltennamen, MIME-Typen und
weitere Metadaten enthalten,
die sich auf den Anbieter beziehen. Die SAF
stellt diese Vertragsklassen für Sie bereit, sodass Sie Ihre
Eigentümer:
Hier sind zum Beispiel die Spalten, die Sie in einem Cursor zurückgeben können, werden Dokumente oder das Stammverzeichnis Ihres Dokumentenanbieters abgefragt:
Kotlin
private val DEFAULT_ROOT_PROJECTION: Array<String> = arrayOf( DocumentsContract.Root.COLUMN_ROOT_ID, DocumentsContract.Root.COLUMN_MIME_TYPES, DocumentsContract.Root.COLUMN_FLAGS, DocumentsContract.Root.COLUMN_ICON, DocumentsContract.Root.COLUMN_TITLE, DocumentsContract.Root.COLUMN_SUMMARY, DocumentsContract.Root.COLUMN_DOCUMENT_ID, DocumentsContract.Root.COLUMN_AVAILABLE_BYTES ) private val DEFAULT_DOCUMENT_PROJECTION: Array<String> = arrayOf( DocumentsContract.Document.COLUMN_DOCUMENT_ID, DocumentsContract.Document.COLUMN_MIME_TYPE, DocumentsContract.Document.COLUMN_DISPLAY_NAME, DocumentsContract.Document.COLUMN_LAST_MODIFIED, DocumentsContract.Document.COLUMN_FLAGS, DocumentsContract.Document.COLUMN_SIZE )
Java
private static final String[] DEFAULT_ROOT_PROJECTION = new String[]{Root.COLUMN_ROOT_ID, Root.COLUMN_MIME_TYPES, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE, Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES,}; private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[]{Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE,};
Der Cursor für das Stammverzeichnis muss bestimmte erforderliche Spalten enthalten. Diese Spalten sind:
Der Cursor für Dokumente muss die folgenden erforderlichen Spalten enthalten:
COLUMN_DOCUMENT_ID
COLUMN_DISPLAY_NAME
COLUMN_MIME_TYPE
COLUMN_FLAGS
COLUMN_SIZE
COLUMN_LAST_MODIFIED
Unterklasse von DocumentsProvider erstellen
Der nächste Schritt beim Schreiben eines benutzerdefinierten Dokumentanbieters besteht darin,
abstrakte Klasse DocumentsProvider
. Sie müssen mindestens
die folgenden Methoden zu implementieren:
Dies sind die einzigen Methoden, die Sie unbedingt implementieren müssen,
und es gibt noch viele weitere. Weitere Informationen: DocumentsProvider
.
Stamm definieren
Ihre Implementierung von queryRoots()
muss einen Cursor
zurückgeben, der auf alle verweist,
Stammverzeichnis Ihres Dokumentenanbieters, indem Sie die in den
DocumentsContract.Root
Im folgenden Snippet stellt der Parameter projection
den Parameter
bestimmte Felder zurück,
die der Aufrufer zurückgeben möchte. Mit dem Snippet wird ein neuer Cursor erstellt,
und fügt eine Zeile hinzu – ein Stammverzeichnis, ein Verzeichnis der obersten Ebene wie
Downloads oder Bilder. Die meisten Anbieter haben nur eine Root. Vielleicht haben Sie mehr als eine,
z. B. bei mehreren Nutzerkonten. Fügen Sie in diesem Fall einfach ein
in die zweite Zeile verschieben.
Kotlin
override fun queryRoots(projection: Array<out String>?): Cursor { // Use a MatrixCursor to build a cursor // with either the requested fields, or the default // projection if "projection" is null. val result = MatrixCursor(resolveRootProjection(projection)) // If user is not logged in, return an empty root cursor. This removes our // provider from the list entirely. if (!isUserLoggedIn()) { return result } // It's possible to have multiple roots (e.g. for multiple accounts in the // same app) -- just add multiple cursor rows. result.newRow().apply { add(DocumentsContract.Root.COLUMN_ROOT_ID, ROOT) // You can provide an optional summary, which helps distinguish roots // with the same title. You can also use this field for displaying an // user account name. add(DocumentsContract.Root.COLUMN_SUMMARY, context.getString(R.string.root_summary)) // FLAG_SUPPORTS_CREATE means at least one directory under the root supports // creating documents. FLAG_SUPPORTS_RECENTS means your application's most // recently used documents will show up in the "Recents" category. // FLAG_SUPPORTS_SEARCH allows users to search all documents the application // shares. add( DocumentsContract.Root.COLUMN_FLAGS, DocumentsContract.Root.FLAG_SUPPORTS_CREATE or DocumentsContract.Root.FLAG_SUPPORTS_RECENTS or DocumentsContract.Root.FLAG_SUPPORTS_SEARCH ) // COLUMN_TITLE is the root title (e.g. Gallery, Drive). add(DocumentsContract.Root.COLUMN_TITLE, context.getString(R.string.title)) // This document id cannot change after it's shared. add(DocumentsContract.Root.COLUMN_DOCUMENT_ID, getDocIdForFile(baseDir)) // The child MIME types are used to filter the roots and only present to the // user those roots that contain the desired type somewhere in their file hierarchy. add(DocumentsContract.Root.COLUMN_MIME_TYPES, getChildMimeTypes(baseDir)) add(DocumentsContract.Root.COLUMN_AVAILABLE_BYTES, baseDir.freeSpace) add(DocumentsContract.Root.COLUMN_ICON, R.drawable.ic_launcher) } return result }
Java
@Override public Cursor queryRoots(String[] projection) throws FileNotFoundException { // Use a MatrixCursor to build a cursor // with either the requested fields, or the default // projection if "projection" is null. final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection)); // If user is not logged in, return an empty root cursor. This removes our // provider from the list entirely. if (!isUserLoggedIn()) { return result; } // It's possible to have multiple roots (e.g. for multiple accounts in the // same app) -- just add multiple cursor rows. final MatrixCursor.RowBuilder row = result.newRow(); row.add(Root.COLUMN_ROOT_ID, ROOT); // You can provide an optional summary, which helps distinguish roots // with the same title. You can also use this field for displaying an // user account name. row.add(Root.COLUMN_SUMMARY, getContext().getString(R.string.root_summary)); // FLAG_SUPPORTS_CREATE means at least one directory under the root supports // creating documents. FLAG_SUPPORTS_RECENTS means your application's most // recently used documents will show up in the "Recents" category. // FLAG_SUPPORTS_SEARCH allows users to search all documents the application // shares. row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | Root.FLAG_SUPPORTS_RECENTS | Root.FLAG_SUPPORTS_SEARCH); // COLUMN_TITLE is the root title (e.g. Gallery, Drive). row.add(Root.COLUMN_TITLE, getContext().getString(R.string.title)); // This document id cannot change after it's shared. row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(baseDir)); // The child MIME types are used to filter the roots and only present to the // user those roots that contain the desired type somewhere in their file hierarchy. row.add(Root.COLUMN_MIME_TYPES, getChildMimeTypes(baseDir)); row.add(Root.COLUMN_AVAILABLE_BYTES, baseDir.getFreeSpace()); row.add(Root.COLUMN_ICON, R.drawable.ic_launcher); return result; }
Wenn Ihr Dokumentanbieter eine Verbindung zu einem dynamischen Satz von Root-Dateien herstellt, zum Beispiel zu einem USB-
Gerät, das möglicherweise nicht verbunden ist, oder von einem Konto, von dem sich der Nutzer abmelden kann – Sie
können Sie die Benutzeroberfläche des Dokuments mithilfe der
ContentResolver.notifyChange()
, wie im folgenden Code-Snippet gezeigt.
Kotlin
val rootsUri: Uri = DocumentsContract.buildRootsUri(BuildConfig.DOCUMENTS_AUTHORITY) context.contentResolver.notifyChange(rootsUri, null)
Java
Uri rootsUri = DocumentsContract.buildRootsUri(BuildConfig.DOCUMENTS_AUTHORITY); context.getContentResolver().notifyChange(rootsUri, null);
Dokumente im Anbieter auflisten
Ihre Implementierung von
queryChildDocuments()
muss ein Cursor
zurückgeben, das auf alle Dateien in
das angegebene Verzeichnis. Dabei werden die in
DocumentsContract.Document
Diese Methode wird aufgerufen, wenn der Nutzer Ihr Stammverzeichnis auf der Auswahl-Benutzeroberfläche auswählt.
Die -Methode ruft die untergeordneten Elemente der Dokument-ID ab, die durch
COLUMN_DOCUMENT_ID
Das System ruft diese Methode dann jedes Mal auf, wenn der Nutzer einen
im Unterverzeichnis Ihres Dokumentenanbieters.
Dieses Snippet erstellt einen neuen Cursor mit den angeforderten Spalten und fügt Informationen über jedes unmittelbar untergeordnete Element im übergeordneten Verzeichnis an den Cursor. Ein untergeordnetes Element kann ein Bild, ein anderes Verzeichnis oder eine beliebige Datei sein:
Kotlin
override fun queryChildDocuments( parentDocumentId: String?, projection: Array<out String>?, sortOrder: String? ): Cursor { return MatrixCursor(resolveDocumentProjection(projection)).apply { val parent: File = getFileForDocId(parentDocumentId) parent.listFiles() .forEach { file -> includeFile(this, null, file) } } }
Java
@Override public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException { final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); final File parent = getFileForDocId(parentDocumentId); for (File file : parent.listFiles()) { // Adds the file's display name, MIME type, size, and so on. includeFile(result, null, file); } return result; }
Dokumentinformationen abrufen
Ihre Implementierung von
queryDocument()
muss einen Cursor
-Wert zurückgeben, der auf die angegebene Datei verweist.
mit den in DocumentsContract.Document
definierten Spalten.
Das queryDocument()
gibt dieselben Informationen zurück, die in
queryChildDocuments()
,
aber für eine bestimmte Datei:
Kotlin
override fun queryDocument(documentId: String?, projection: Array<out String>?): Cursor { // Create a cursor with the requested projection, or the default projection. return MatrixCursor(resolveDocumentProjection(projection)).apply { includeFile(this, documentId, null) } }
Java
@Override public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException { // Create a cursor with the requested projection, or the default projection. final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); includeFile(result, documentId, null); return result; }
Ihr Dokumentanbieter kann auch Miniaturansichten für ein Dokument anzeigen, indem Sie
Überschreiben des
DocumentsProvider.openDocumentThumbnail()
und fügen Sie den Parameter
FLAG_SUPPORTS_THUMBNAIL
an die unterstützten Dateien übergeben.
Das folgende Code-Snippet zeigt ein Beispiel für die Implementierung des
DocumentsProvider.openDocumentThumbnail()
Kotlin
override fun openDocumentThumbnail( documentId: String?, sizeHint: Point?, signal: CancellationSignal? ): AssetFileDescriptor { val file = getThumbnailFileForDocId(documentId) val pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY) return AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH) }
Java
@Override public AssetFileDescriptor openDocumentThumbnail(String documentId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException { final File file = getThumbnailFileForDocId(documentId); final ParcelFileDescriptor pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH); }
Achtung:
Ein Dokumentanbieter sollte Miniaturansichten nicht mehr als doppelt anzeigen.
Die durch den sizeHint
-Parameter angegebene Größe.
Dokument öffnen
Sie müssen openDocument()
implementieren, um eine ParcelFileDescriptor
zurückzugeben, die für
in der angegebenen Datei. Andere Apps können die zurückgegebene ParcelFileDescriptor
verwenden
um Daten zu streamen. Das System ruft diese Methode auf,
nachdem der Nutzer eine Datei ausgewählt hat,
und die Client-App fordert Zugriff darauf an, indem sie
openFileDescriptor()
Beispiel:
Kotlin
override fun openDocument( documentId: String, mode: String, signal: CancellationSignal ): ParcelFileDescriptor { Log.v(TAG, "openDocument, mode: $mode") // It's OK to do network operations in this method to download the document, // as long as you periodically check the CancellationSignal. If you have an // extremely large file to transfer from the network, a better solution may // be pipes or sockets (see ParcelFileDescriptor for helper methods). val file: File = getFileForDocId(documentId) val accessMode: Int = ParcelFileDescriptor.parseMode(mode) val isWrite: Boolean = mode.contains("w") return if (isWrite) { val handler = Handler(context.mainLooper) // Attach a close listener if the document is opened in write mode. try { ParcelFileDescriptor.open(file, accessMode, handler) { // Update the file with the cloud server. The client is done writing. Log.i(TAG, "A file with id $documentId has been closed! Time to update the server.") } } catch (e: IOException) { throw FileNotFoundException( "Failed to open document with id $documentId and mode $mode" ) } } else { ParcelFileDescriptor.open(file, accessMode) } }
Java
@Override public ParcelFileDescriptor openDocument(final String documentId, final String mode, CancellationSignal signal) throws FileNotFoundException { Log.v(TAG, "openDocument, mode: " + mode); // It's OK to do network operations in this method to download the document, // as long as you periodically check the CancellationSignal. If you have an // extremely large file to transfer from the network, a better solution may // be pipes or sockets (see ParcelFileDescriptor for helper methods). final File file = getFileForDocId(documentId); final int accessMode = ParcelFileDescriptor.parseMode(mode); final boolean isWrite = (mode.indexOf('w') != -1); if(isWrite) { // Attach a close listener if the document is opened in write mode. try { Handler handler = new Handler(getContext().getMainLooper()); return ParcelFileDescriptor.open(file, accessMode, handler, new ParcelFileDescriptor.OnCloseListener() { @Override public void onClose(IOException e) { // Update the file with the cloud server. The client is done // writing. Log.i(TAG, "A file with id " + documentId + " has been closed! Time to " + "update the server."); } }); } catch (IOException e) { throw new FileNotFoundException("Failed to open document with id" + documentId + " and mode " + mode); } } else { return ParcelFileDescriptor.open(file, accessMode); } }
Wenn Ihr Dokumentanbieter
Dateien streamt oder komplizierte
Datenstrukturen verwenden, sollten Sie die Implementierung
createReliablePipe()
oder
createReliableSocketPair()
-Methoden.
Mit diesen Methoden können Sie ein Paar aus
ParcelFileDescriptor
-Objekte, wo Sie eines zurückgeben können
und die andere über eine
ParcelFileDescriptor.AutoCloseOutputStream
oder
ParcelFileDescriptor.AutoCloseInputStream
Letzte Dokumente und Suche unterstützen
Sie können eine Liste der zuletzt geänderten Dokumente im Stammverzeichnis Ihres
des Dokumentanbieters durch Überschreiben des
Methode queryRecentDocuments()
und Rückgabe
FLAG_SUPPORTS_RECENTS
,
Das folgende Code-Snippet zeigt ein Beispiel für die Implementierung des
queryRecentDocuments()
-Methoden.
Kotlin
override fun queryRecentDocuments(rootId: String?, projection: Array<out String>?): Cursor { // This example implementation walks a // local file structure to find the most recently // modified files. Other implementations might // include making a network call to query a // server. // Create a cursor with the requested projection, or the default projection. val result = MatrixCursor(resolveDocumentProjection(projection)) val parent: File = getFileForDocId(rootId) // Create a queue to store the most recent documents, // which orders by last modified. val lastModifiedFiles = PriorityQueue( 5, Comparator<File> { i, j -> Long.compare(i.lastModified(), j.lastModified()) } ) // Iterate through all files and directories // in the file structure under the root. If // the file is more recent than the least // recently modified, add it to the queue, // limiting the number of results. val pending : MutableList<File> = mutableListOf() // Start by adding the parent to the list of files to be processed pending.add(parent) // Do while we still have unexamined files while (pending.isNotEmpty()) { // Take a file from the list of unprocessed files val file: File = pending.removeAt(0) if (file.isDirectory) { // If it's a directory, add all its children to the unprocessed list pending += file.listFiles() } else { // If it's a file, add it to the ordered queue. lastModifiedFiles.add(file) } } // Add the most recent files to the cursor, // not exceeding the max number of results. for (i in 0 until Math.min(MAX_LAST_MODIFIED + 1, lastModifiedFiles.size)) { val file: File = lastModifiedFiles.remove() includeFile(result, null, file) } return result }
Java
@Override public Cursor queryRecentDocuments(String rootId, String[] projection) throws FileNotFoundException { // This example implementation walks a // local file structure to find the most recently // modified files. Other implementations might // include making a network call to query a // server. // Create a cursor with the requested projection, or the default projection. final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); final File parent = getFileForDocId(rootId); // Create a queue to store the most recent documents, // which orders by last modified. PriorityQueue lastModifiedFiles = new PriorityQueue(5, new Comparator() { public int compare(File i, File j) { return Long.compare(i.lastModified(), j.lastModified()); } }); // Iterate through all files and directories // in the file structure under the root. If // the file is more recent than the least // recently modified, add it to the queue, // limiting the number of results. final LinkedList pending = new LinkedList(); // Start by adding the parent to the list of files to be processed pending.add(parent); // Do while we still have unexamined files while (!pending.isEmpty()) { // Take a file from the list of unprocessed files final File file = pending.removeFirst(); if (file.isDirectory()) { // If it's a directory, add all its children to the unprocessed list Collections.addAll(pending, file.listFiles()); } else { // If it's a file, add it to the ordered queue. lastModifiedFiles.add(file); } } // Add the most recent files to the cursor, // not exceeding the max number of results. for (int i = 0; i < Math.min(MAX_LAST_MODIFIED + 1, lastModifiedFiles.size()); i++) { final File file = lastModifiedFiles.remove(); includeFile(result, null, file); } return result; }
Den vollständigen Code für das obige Snippet erhalten Sie, indem Sie die Storage-Anbieter Codebeispiel.
Erstellung von Dokumenten unterstützen
Sie können Client-Apps erlauben, Dateien bei Ihrem Dokumentanbieter zu erstellen.
Wenn eine Client-App eine ACTION_CREATE_DOCUMENT
sendet
kann Ihr Dokumentanbieter
der Client-App erlauben,
neue Dokumente beim Dokumentanbieter.
Um das Erstellen von Dokumenten zu unterstützen, muss Ihr Stammverzeichnis über die
Flag FLAG_SUPPORTS_CREATE
.
Verzeichnisse, in denen neue Dateien erstellt werden können, müssen über die
FLAG_DIR_SUPPORTS_CREATE
.
Ihr Dokumentanbieter muss außerdem die
createDocument()
-Methode. Wenn ein Nutzer ein Verzeichnis in Ihrem
wenn sie eine neue Datei speichern möchten, erhält der Anbieter einen Aufruf an
createDocument()
Informationen zur Implementierung des
createDocument()
-Methode eine neue
COLUMN_DOCUMENT_ID
für den
-Datei. Die Client-App kann dann mit dieser ID einen Handle für die Datei abrufen.
und letztendlich
openDocument()
, um in die neue Datei zu schreiben.
Das folgende Code-Snippet zeigt, wie Sie in Dokumentanbieter.
Kotlin
override fun createDocument(documentId: String?, mimeType: String?, displayName: String?): String { val parent: File = getFileForDocId(documentId) val file: File = try { File(parent.path, displayName).apply { createNewFile() setWritable(true) setReadable(true) } } catch (e: IOException) { throw FileNotFoundException( "Failed to create document with name $displayName and documentId $documentId" ) } return getDocIdForFile(file) }
Java
@Override public String createDocument(String documentId, String mimeType, String displayName) throws FileNotFoundException { File parent = getFileForDocId(documentId); File file = new File(parent.getPath(), displayName); try { file.createNewFile(); file.setWritable(true); file.setReadable(true); } catch (IOException e) { throw new FileNotFoundException("Failed to create document with name " + displayName +" and documentId " + documentId); } return getDocIdForFile(file); }
Den vollständigen Code für das obige Snippet erhalten Sie, indem Sie die Storage-Anbieter Codebeispiel.
Unterstützung von Funktionen zur Dokumentverwaltung
Ihr Dokumentanbieter kann nicht nur Dateien öffnen, erstellen und ansehen,
kann Client-Apps auch das Umbenennen, Kopieren, Verschieben und Löschen gestatten,
Dateien. Um Funktionen zur Dokumentverwaltung
Ihrem Dokumentanbieter eine Kennzeichnung
Spalte „COLUMN_FLAGS
“
um auf die unterstützte Funktion hinzuweisen. Außerdem müssen Sie
die entsprechende Methode von DocumentsProvider
.
Die folgende Tabelle enthält die
COLUMN_FLAGS
-Flag
und DocumentsProvider
, die ein Dokument
die der Anbieter implementieren muss,
um bestimmte Funktionen verfügbar zu machen.
Funktion | Melden | Method |
---|---|---|
Dateien löschen |
FLAG_SUPPORTS_DELETE
|
deleteDocument()
|
Dateien umbenennen |
FLAG_SUPPORTS_RENAME
|
renameDocument()
|
Datei in ein neues übergeordnetes Verzeichnis innerhalb des Dokumentanbieters kopieren |
FLAG_SUPPORTS_COPY
|
copyDocument()
|
Dateien innerhalb des Dokumentanbieters von einem Verzeichnis in ein anderes verschieben |
FLAG_SUPPORTS_MOVE
|
moveDocument()
|
Datei aus dem übergeordneten Verzeichnis entfernen |
FLAG_SUPPORTS_REMOVE
|
removeDocument()
|
Unterstützung virtueller Dateien und alternativer Dateiformate
Virtuelle Dateien eine in Android 7.0 (API-Level 24) eingeführte Funktion, mit der Dokumentanbieter um Dateien anzuzeigen, für die kein eine direkte Bytecode-Darstellung. So ermöglichen Sie anderen Apps, virtuelle Dateien anzusehen: muss Ihr Dokumentanbieter eine alternative öffnebare Datei erstellen, Darstellung der virtuellen Dateien.
Angenommen, ein Dokumentanbieter
enthält eine Datei,
das andere Apps nicht direkt öffnen können, also eine virtuelle Datei.
Wenn eine Client-App den Intent ACTION_VIEW
sendet
ohne die Kategorie CATEGORY_OPENABLE
,
können Nutzende diese virtuellen Dateien
innerhalb des Dokumentenanbieters auswählen,
zur Ansicht verfügbar. Der Dokumentanbieter gibt dann die virtuelle Datei zurück.
in einem anderen, aber aufrufbaren Dateiformat, z. B. einem Bild.
Die Client-App kann dann die virtuelle Datei öffnen, damit der Nutzer sie sich ansehen kann.
Wenn Sie erklären möchten, dass ein Dokument beim Anbieter virtuell ist, müssen Sie den
FLAG_VIRTUAL_DOCUMENT
für die Datei, die vom
queryDocument()
. Dieses Flag informiert Client-Apps darüber, dass die Datei keine direkte
Bytecode-Darstellung und kann nicht direkt geöffnet werden.
Wenn Sie bei Ihrem Dokumentanbieter
deklarieren, dass eine Datei virtuell ist,
wird dringend empfohlen, sie in einer anderen
MIME-Typ wie ein Bild oder PDF. Der Dokumentanbieter
deklariert die alternativen MIME-Typen,
die Anzeige einer virtuellen Datei durch Überschreiben des
getDocumentStreamTypes()
. Wenn Client-Apps die
getStreamTypes(android.net.Uri, java.lang.String)
ruft das System die Methode
getDocumentStreamTypes()
des Dokumentanbieters. Die
getDocumentStreamTypes()
gibt dann ein Array alternativer MIME-Typen zurück,
der Dokumentanbieter für die Datei unterstützt.
Nachdem der Kunde bestimmt
dass der Dokumentanbieter das Dokument in einer anzeigbaren Datei erstellen kann,
verwendet die Client-App die
openTypedAssetFileDescriptor()
mit der intern die Methode
openTypedDocument()
. Der Dokumentanbieter gibt die Datei an die Client-App in
das angeforderte Dateiformat.
Das folgende Code-Snippet zeigt eine einfache Implementierung des
getDocumentStreamTypes()
und
openTypedDocument()
.
Kotlin
var SUPPORTED_MIME_TYPES : Array<String> = arrayOf("image/png", "image/jpg") override fun openTypedDocument( documentId: String?, mimeTypeFilter: String, opts: Bundle?, signal: CancellationSignal? ): AssetFileDescriptor? { return try { // Determine which supported MIME type the client app requested. when(mimeTypeFilter) { "image/jpg" -> openJpgDocument(documentId) "image/png", "image/*", "*/*" -> openPngDocument(documentId) else -> throw IllegalArgumentException("Invalid mimeTypeFilter $mimeTypeFilter") } } catch (ex: Exception) { Log.e(TAG, ex.message) null } } override fun getDocumentStreamTypes(documentId: String, mimeTypeFilter: String): Array<String> { return when (mimeTypeFilter) { "*/*", "image/*" -> { // Return all supported MIME types if the client app // passes in '*/*' or 'image/*'. SUPPORTED_MIME_TYPES } else -> { // Filter the list of supported mime types to find a match. SUPPORTED_MIME_TYPES.filter { it == mimeTypeFilter }.toTypedArray() } } }
Java
public static String[] SUPPORTED_MIME_TYPES = {"image/png", "image/jpg"}; @Override public AssetFileDescriptor openTypedDocument(String documentId, String mimeTypeFilter, Bundle opts, CancellationSignal signal) { try { // Determine which supported MIME type the client app requested. if ("image/png".equals(mimeTypeFilter) || "image/*".equals(mimeTypeFilter) || "*/*".equals(mimeTypeFilter)) { // Return the file in the specified format. return openPngDocument(documentId); } else if ("image/jpg".equals(mimeTypeFilter)) { return openJpgDocument(documentId); } else { throw new IllegalArgumentException("Invalid mimeTypeFilter " + mimeTypeFilter); } } catch (Exception ex) { Log.e(TAG, ex.getMessage()); } finally { return null; } } @Override public String[] getDocumentStreamTypes(String documentId, String mimeTypeFilter) { // Return all supported MIME tyupes if the client app // passes in '*/*' or 'image/*'. if ("*/*".equals(mimeTypeFilter) || "image/*".equals(mimeTypeFilter)) { return SUPPORTED_MIME_TYPES; } ArrayList requestedMimeTypes = new ArrayList<>(); // Iterate over the list of supported mime types to find a match. for (int i=0; i < SUPPORTED_MIME_TYPES.length; i++) { if (SUPPORTED_MIME_TYPES[i].equals(mimeTypeFilter)) { requestedMimeTypes.add(SUPPORTED_MIME_TYPES[i]); } } return (String[])requestedMimeTypes.toArray(); }
Sicherheit
Angenommen, Ihr Dokumentenanbieter ist ein passwortgeschützter Cloud-Speicherdienst.
und Sie möchten sicherstellen, dass die Nutzer angemeldet sind, bevor Sie mit der Freigabe ihrer Dateien beginnen.
Wie sollte sich meine App verhalten, wenn der Nutzer nicht angemeldet ist? Die Lösung besteht darin,
Null Wurzeln in Ihrer Implementierung von queryRoots()
. Das heißt, ein leerer Stamm-Cursor:
Kotlin
override fun queryRoots(projection: Array<out String>): Cursor { ... // If user is not logged in, return an empty root cursor. This removes our // provider from the list entirely. if (!isUserLoggedIn()) { return result }
Java
public Cursor queryRoots(String[] projection) throws FileNotFoundException { ... // If user is not logged in, return an empty root cursor. This removes our // provider from the list entirely. if (!isUserLoggedIn()) { return result; }
Der andere Schritt besteht darin, getContentResolver().notifyChange()
aufzurufen.
Erinnern Sie sich an die DocumentsContract
? Wir nutzen es, um
diesen URI. Mit dem folgenden Snippet wird das System angewiesen, die Stammdateien
Dokumentanbieter, wenn sich der Anmeldestatus des Nutzers ändert. Wenn die Nutzenden nicht
angemeldet ist, gibt ein Aufruf von queryRoots()
den Fehlercode
leeren Cursor, wie oben gezeigt. So wird sichergestellt, dass die Dokumente
wenn der Nutzer beim Anbieter angemeldet ist.
Kotlin
private fun onLoginButtonClick() { loginOrLogout() getContentResolver().notifyChange( DocumentsContract.buildRootsUri(AUTHORITY), null ) }
Java
private void onLoginButtonClick() { loginOrLogout(); getContentResolver().notifyChange(DocumentsContract .buildRootsUri(AUTHORITY), null); }
Beispielcode für diese Seite finden Sie unter:
- <ph type="x-smartling-placeholder"></ph> Speicheranbieter
- <ph type="x-smartling-placeholder"></ph> StorageClient
Weitere Informationen zu Videos im Zusammenhang mit dieser Seite findest du hier:
- <ph type="x-smartling-placeholder"></ph> DevBytes: Storage Access Framework von Android 4.4: Anbieter
- <ph type="x-smartling-placeholder"></ph> Storage Access Framework: DocumentsProvider erstellen
- <ph type="x-smartling-placeholder"></ph> Virtuelle Dateien im Storage Access Framework
Weitere Informationen finden Sie hier:
- <ph type="x-smartling-placeholder"></ph> DocumentsProvider erstellen
- <ph type="x-smartling-placeholder"></ph> Dateien mit Storage Access Framework öffnen
- <ph type="x-smartling-placeholder"></ph> Grundlagen zu Contentanbietern