Jeśli tworzysz aplikację, która udostępnia usługi przechowywania plików (np. usłudze Cloud Save możesz udostępniać pliki w Storage Access Framework (SAF) przez wpisanie niestandardowego dostawcy dokumentów. Na tej stronie dowiesz się, jak utworzyć niestandardowego dostawcę dokumentów.
Więcej informacji o tym, jak działa Storage Access Framework, znajdziesz w dokumentacji Omówienie platformy Storage Access Framework.
Plik manifestu
Aby wdrożyć dostawcę dokumentów niestandardowych, dodaj do swojej aplikacji ten kod plik manifestu:
- Cel API na poziomie 19 lub wyższym.
- Element <provider>deklarujący Twoją niestandardową pamięć masową dostawcy usług.
- 
    Atrybut android:namezostał ustawiony na nazwę Twojego PodklasaDocumentsProvider, czyli nazwa jego klasy, w tym nazwa pakietu:com.example.android.storageprovider.MyCloudProvider.
- 
    Atrybut android:authority, czyli nazwa pakietu (w tym przykładziecom.example.android.storageprovider). oraz typ dostawcy treści, (documents).
- Atrybut android:exportedma wartość"true". Dostawca musisz wyeksportować, aby były widoczne dla innych aplikacji.
- Ustawiono atrybut android:grantUriPermissionsna"true"To ustawienie pozwala systemowi na udzielanie dostępu innym aplikacjom do treści w Twojej usłudze. Podczas rozmowy o tym, jak inne aplikacje mogą zachować dostęp do treści od dostawcy, zobacz Utrzymywanie .
- Uprawnienie MANAGE_DOCUMENTS. Domyślnie dostępny jest usługodawca. do wszystkich. Dodanie tego uprawnienia ogranicza dostawcę do systemu. To ograniczenie ma duże znaczenie ze względu na bezpieczeństwo.
- Filtr intencji, który zawiera parametr
android.content.action.DOCUMENTS_PROVIDER, dzięki czemu dostawca pojawia się w selektorze, gdy system wyszukuje dostawców.
Oto fragmenty przykładowego pliku manifestu zawierającego dostawcę:
<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>
Obsługa urządzeń z Androidem 4.3 lub starszym
Intencja ACTION_OPEN_DOCUMENT jest dostępna tylko
na urządzeniach z Androidem 4.4 lub nowszym.
Jeśli chcesz, aby Twoja aplikacja obsługiwała ACTION_GET_CONTENT
aby działała na urządzeniach z Androidem 4.3 lub starszym,
wyłącz filtr intencji ACTION_GET_CONTENT w
pliku manifestu dla urządzeń z Androidem 4.4 lub nowszym. O
dostawcy dokumentu i ACTION_GET_CONTENT należy wziąć pod uwagę
 wzajemnie się wykluczają. Jeśli obie są obsługiwane jednocześnie, aplikacja
pojawia się dwukrotnie w interfejsie selektora systemowego, oferując 2 różne sposoby
przechowywane dane. Jest to mylące dla użytkowników.
Zalecany sposób wyłączania
ACTION_GET_CONTENT filtr intencji dla urządzeń
z Androidem 4.4 lub nowszym:
- W pliku zasobów bool.xmlw katalogures/values/dodaj ten wiersz:<bool name="atMostJellyBeanMR2">true</bool>
- W pliku zasobów bool.xmlw katalogures/values-v19/dodaj ten wiersz:<bool name="atMostJellyBeanMR2">false</bool>
- Dodaj
aktywność
alias, aby wyłączyć intencję ACTION_GET_CONTENT. filtr wersji 4.4 (poziom interfejsu API 19) i nowszych. Na przykład:<!-- 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> 
Umowy
Zwykle podczas pisania niestandardowego dostawcy treści jednym z zadań jest
wdrażania klas umów, jak opisano w
Przewodnik dla programistów dotyczący dostawców treści. Klasa umowy to klasa public final
który zawiera stałe definicje identyfikatorów URI, nazw kolumn, typów MIME oraz
inne metadane odnoszące się do dostawcy. SAF
udostępnia te klasy kontraktów, dzięki czemu nie musisz pisać
własny:
Na przykład oto kolumny, które możesz zwrócić po najechaniu kursorem, Twój dostawca dokumentów otrzymuje zapytanie o dokumenty lub katalog główny:
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,};
Kursor katalogu głównego musi zawierać pewne wymagane kolumny. Te kolumny to:
Kursor dokumentów musi zawierać te wymagane kolumny:
- COLUMN_DOCUMENT_ID
- COLUMN_DISPLAY_NAME
- COLUMN_MIME_TYPE
- COLUMN_FLAGS
- COLUMN_SIZE
- COLUMN_LAST_MODIFIED
Tworzenie podklasy klasy DocumentsProvider
Następnym krokiem podczas tworzenia niestandardowego dostawcy dokumentu jest podklasyfikacja
klasa abstrakcyjna DocumentsProvider. Wymagane jest
zastosuj te metody:
To jedyne metody, które musisz wdrożyć, ale pamiętaj,
jest o wiele więcej. Zobacz DocumentsProvider
.
Zdefiniuj pierwiastek
Implementacja funkcji queryRoots() musi zwrócić element Cursor wskazujący wszystkie
katalogu głównego dostawcy dokumentu, przy użyciu kolumn zdefiniowanych w
DocumentsContract.Root
W tym fragmencie kodu parametr projection reprezentuje wartość
do określonych pól, do których dostęp chce zwrócić. Fragment kodu tworzy nowy kursor
i dodaje do niego jeden wiersz – jeden katalog główny, katalog najwyższego poziomu,
Pobrane pliki lub Obrazy.  Większość dostawców ma tylko jeden poziom główny. Możesz mieć ich więcej,
na przykład w przypadku wielu kont użytkowników. W takim przypadku wystarczy dodać
na drugi wiersz kursora.
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; }
  Jeśli dostawca dokumentów łączy się z dynamicznym zbiorem pierwiastków – na przykład przez USB
  urządzenie, które może być odłączone, lub konto, z którego użytkownik może się wylogować –
  może zaktualizować interfejs dokumentu, aby śledzić
zmiany za pomocą
  ContentResolver.notifyChange() zgodnie z tym fragmentem kodu.
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);
Wyświetlenie listy dokumentów u dostawcy
Twoja implementacja
queryChildDocuments()
musi zwracać wartość Cursor wskazującą wszystkie pliki w
wskazanego katalogu, z użyciem kolumn zdefiniowanych w argumencie
DocumentsContract.Document
  Ta metoda jest wywoływana, gdy użytkownik wybierze poziom główny w interfejsie selektora.
  Ta metoda pobiera elementy podrzędne identyfikatora dokumentu określonego przez funkcję
  COLUMN_DOCUMENT_ID
  Następnie system wywołuje tę metodę za każdym razem, gdy użytkownik wybierze
  w podkatalogu Twojego dostawcy dokumentów.
Ten fragment kodu tworzy nowy kursor z żądanymi kolumnami, a następnie dodaje informacje o każdym najbliższym elemencie podrzędnym w katalogu nadrzędnym. Elementem podrzędnym może być obraz, inny katalog – dowolny plik:
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; }
Uzyskaj informacje o dokumencie
Twoja implementacja
queryDocument()
musi zwrócić Cursor wskazujący określony plik,
za pomocą kolumn zdefiniowanych w DocumentsContract.Document.
queryDocument()
zwraca te same informacje, które zostały przekazane
queryChildDocuments(),
ale w konkretnym pliku:
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; }
  Dostawca dokumentu może też dostarczyć miniatury dokumentu poprzez:
  zastępując
  DocumentsProvider.openDocumentThumbnail() i dodanie funkcji
  FLAG_SUPPORTS_THUMBNAIL
  do obsługiwanych plików.
  Poniższy fragment kodu zawiera przykładowy sposób implementacji
  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); }
Uwaga:
  Dostawca dokumentu nie powinien zwracać obrazów miniatur więcej niż podwójnej liczby
  rozmiar określony przez parametr sizeHint.
Otwieranie dokumentu
Musisz zaimplementować funkcję openDocument(), aby zwrócić element ParcelFileDescriptor reprezentujący wartość
określonego pliku. Inne aplikacje mogą używać zwróconego ParcelFileDescriptor
aby przesyłać strumieniowo dane. System wywołuje tę metodę, gdy użytkownik wybierze plik.
a aplikacja kliencka prosi o dostęp przez wywołanie
openFileDescriptor()
Na przykład:
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); } }
  Jeśli dostawca dokumentów przesyła strumieniowo pliki lub obsługuje skomplikowane
  struktury danych, rozważ wdrożenie
  createReliablePipe()
  lub
  createReliableSocketPair() metody.
  Metody te pozwalają utworzyć parę
  ParcelFileDescriptor obiektów, do których można zwrócić 1 obiekt
  i wysyłać drugie za pomocą
  ParcelFileDescriptor.AutoCloseOutputStream
  lub
  ParcelFileDescriptor.AutoCloseInputStream
Obsługa ostatnich dokumentów i wyszukiwania
  Listę ostatnio zmodyfikowanych dokumentów można umieścić w katalogu głównym
  dostawcy dokumentu, zastępując
  Metoda queryRecentDocuments() i pobieranie
  FLAG_SUPPORTS_RECENTS,
  Ten fragment kodu pokazuje przykładową implementację tagu
  queryRecentDocuments() metod.
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; }
Pełny kod dla powyższego fragmentu znajdziesz, pobierając Dostawca miejsca na dane przykładowego kodu.
Pomoc w tworzeniu dokumentów
  Możesz zezwolić aplikacjom klienckim na tworzenie plików u dostawcy dokumentów.
  Jeśli aplikacja kliencka wysyła ACTION_CREATE_DOCUMENT
  intencja, dostawca dokumentu może umożliwić tej aplikacji klienckiej utworzenie
  nowe dokumenty u dostawcy dokumentu.
  Aby można było tworzyć dokumenty, w katalogu głównym musi znajdować się
  flaga FLAG_SUPPORTS_CREATE.
  Katalogi, które pozwalają na tworzenie nowych plików, muszą mieć
  FLAG_DIR_SUPPORTS_CREATE
  flaga.
  Dostawca dokumentu musi również zaimplementować
  Metoda createDocument(). Gdy użytkownik wybierze katalog w Twojej
  dostawcy dokumentów w celu zapisania nowego pliku, otrzyma on wywołanie
  createDocument() W ramach implementacji
  createDocument(), zwracasz nową
  COLUMN_DOCUMENT_ID dla
  . Aplikacja kliencka może następnie użyć tego identyfikatora, aby uzyskać nick dla pliku
  i w końcu
  openDocument(), aby zapisać zmiany w nowym pliku.
Fragment kodu poniżej pokazuje, jak utworzyć nowy plik w od dostawcy dokumentu.
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); }
Pełny kod dla powyższego fragmentu znajdziesz, pobierając Dostawca miejsca na dane przykładowego kodu.
Obsługa funkcji zarządzania dokumentami
  Oprócz otwierania, tworzenia i wyświetlania plików dostawca dokumentu
  mogą też zezwalać aplikacjom klienckim na zmienianie nazw, kopiowanie, przenoszenie i usuwanie
  . Aby dodać funkcję zarządzania dokumentami do
  dostawcy dokumentu, dodaj flagę do pola
  COLUMN_FLAGS kolumna
  aby wskazać
obsługiwaną funkcję. Musisz też zaimplementować
  odpowiednią metodę funkcji DocumentsProvider
  zajęcia.
  Tabela poniżej zawiera
  Flaga COLUMN_FLAGS
  i DocumentsProvider, które potwierdzają, że dokumenty
  który dostawca musi wdrożyć, aby udostępnić określone funkcje.
| Funkcja | Zgłoś | Metoda | 
|---|---|---|
| Usuwanie pliku | FLAG_SUPPORTS_DELETE | deleteDocument() | 
| Zmienianie nazwy pliku | FLAG_SUPPORTS_RENAME | renameDocument() | 
| Skopiuj plik do nowego katalogu nadrzędnego u dostawcy dokumentu | FLAG_SUPPORTS_COPY | copyDocument() | 
| Przenoszenie pliku z jednego katalogu do innego u dostawcy dokumentu | FLAG_SUPPORTS_MOVE | moveDocument() | 
| Usuwanie pliku z katalogu nadrzędnego | FLAG_SUPPORTS_REMOVE | removeDocument() | 
Obsługa plików wirtualnych i alternatywnych formatów plików
Pliki wirtualne, funkcja wprowadzona w Androidzie 7.0 (poziom interfejsu API 24) pozwala dostawcom dokumentów aby umożliwiać wyświetlanie plików, które nie mają bezpośrednią reprezentację kodu bajtowego. Aby umożliwić innym aplikacjom wyświetlanie plików wirtualnych: dostawca dokumentu musi utworzyć alternatywny plik, który można otworzyć dla plików wirtualnych.
  Wyobraź sobie na przykład, że dostawca dokumentu zawiera plik
  format, którego inne aplikacje nie mogą bezpośrednio otworzyć, czyli plik wirtualny.
  Gdy aplikacja kliencka wysyła intencję ACTION_VIEW
  bez kategorii CATEGORY_OPENABLE,
  użytkownicy mogą wybrać te pliki wirtualne u dostawcy dokumentu
  do oglądania. Następnie dostawca dokumentu zwraca plik wirtualny.
  w innym, lecz otwartym formacie
takim jak zdjęcie.
  Aplikacja kliencka może następnie otworzyć plik wirtualny, który użytkownik może wyświetlić.
  Aby zadeklarować, że dokument u dostawcy jest wirtualny, musisz dodać atrybut
  FLAG_VIRTUAL_DOCUMENT
  do pliku zwróconego przez
  queryDocument()
  . Ta flaga powiadamia aplikacje klienckie, że plik nie ma bezpośredniego
  zawiera kod bajtowy i nie można go otworzyć bezpośrednio.
  Jeśli zadeklarujesz, że plik u dostawcy dokumentu jest wirtualny,
  zdecydowanie zalecamy udostępnienie go w innym
  Typ MIME, np. obraz lub plik PDF. Dostawca dokumentu
  deklaruje alternatywne typy MIME,
  obsługuje wyświetlanie pliku wirtualnego przez zastąpienie
  getDocumentStreamTypes()
  . Gdy aplikacje klienckie wywołują metodę
  getStreamTypes(android.net.Uri, java.lang.String)
  system wywołuje metodę
  getDocumentStreamTypes()
  u dostawcy dokumentu. 
  getDocumentStreamTypes()
  zwraca tablicę alternatywnych typów MIME, które
  obsługiwanych przez dostawcę dokumentów dla tego pliku.
  Gdy klient ustali,
  że dostawca dokumentu może wygenerować dokument w pliku, który można wyświetlić.
  , aplikacja kliencka wywołuje metodę
  openTypedAssetFileDescriptor()
  która wywołuje wewnętrznie funkcję dostawcy dokumentu
  openTypedDocument()
  . Dostawca dokumentu zwraca plik do aplikacji klienckiej w
  w żądanym formacie pliku.
  Fragment kodu poniżej pokazuje prostą implementację
  getDocumentStreamTypes()
  oraz
  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(); }
Bezpieczeństwo
Załóżmy, że dostawcą dokumentów jest usługa przechowywania w chmurze chronionej hasłem.
i chcesz się upewnić, że użytkownicy są zalogowani, zanim zaczniesz udostępniać ich pliki.
Co powinna zrobić aplikacja, jeśli użytkownik nie jest zalogowany?  Rozwiązaniem jest zwrócenie
żadnych pierwiastków w implementacji queryRoots(). Czyli pusty kursor główny:
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; }
Następnie musisz wywołać funkcję getContentResolver().notifyChange().
Pamiętasz to miejsce (DocumentsContract)?  Wykorzystujemy je,
ten identyfikator URI. Ten fragment kodu informuje system, że ma wysłać zapytanie do elementów głównych Twojej
dostawcy dokumentu przy każdej zmianie stanu logowania użytkownika. Jeśli użytkownik nie jest
jest zalogowany, wywołanie queryRoots() zwraca
pusty kursor, jak pokazano powyżej. Dzięki temu dokumenty dostawcy są
dostępne, jeśli użytkownik jest zalogowany u dostawcy.
Kotlin
private fun onLoginButtonClick() { loginOrLogout() getContentResolver().notifyChange( DocumentsContract.buildRootsUri(AUTHORITY), null ) }
Java
private void onLoginButtonClick() { loginOrLogout(); getContentResolver().notifyChange(DocumentsContract .buildRootsUri(AUTHORITY), null); }
Przykładowy kod związany z tą stroną znajdziesz tutaj:
Filmy powiązane z tą stroną znajdziesz tutaj:
- DevBytes: platforma dostępu do pamięci masowej na Androidzie 4.4: dostawca
- Platforma dostępu do pamięci masowej: tworzenie komponentu DocumentsProvider
- Pliki wirtualne w ramach platformy Storage Access Framework
Dodatkowe informacje znajdziesz tutaj:
- Tworzenie dostawcy DocumentsProvider
- Otwieranie plików za pomocą platformy Storage Access Framework
- Podstawowe informacje o dostawcach treści
