Benutzerdefinierten Dokumentanbieter erstellen

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 Ihres DocumentsProvider 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 Beispiel com.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:

  1. Fügen Sie in Ihrer bool.xml-Ressourcendatei unter res/values/ Folgendes hinzu: diese Zeile:
    <bool name="atMostJellyBeanMR2">true</bool>
  2. Fügen Sie in Ihrer bool.xml-Ressourcendatei unter res/values-v19/ Folgendes hinzu: diese Zeile:
    <bool name="atMostJellyBeanMR2">false</bool>
  3. 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:

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&lt;&gt;();

    // Iterate over the list of supported mime types to find a match.
    for (int i=0; i &lt; 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:

Weitere Informationen zu Videos im Zusammenhang mit dieser Seite findest du hier:

Weitere Informationen finden Sie hier: