Buluta kaydetme hizmeti gibi dosyalar için depolama hizmetleri sağlayan bir uygulama geliştiriyorsanız özel bir belge sağlayıcı yazarak dosyalarınızı Depolama Erişim Çerçevesi (SAF) üzerinden kullanıma sunabilirsiniz. Bu sayfada, özel doküman sağlayıcının nasıl oluşturulacağı açıklanmaktadır.
Depolama Erişim Çerçevesi'nin işleyiş şekli hakkında daha fazla bilgi için Depolama Erişim Çerçevesi'ne genel bakış sayfasını inceleyin.
Manifest
Özel bir belge sağlayıcı uygulamak için uygulamanızın manifest dosyasına aşağıdakileri ekleyin:
- API düzeyi 19 veya üstü bir hedef.
- Özel depolama alanı sağlayıcınızı tanımlayan bir
<provider>
öğesi. -
android:name
özelliği,DocumentsProvider
alt sınıfınızın adına ayarlandı. Bu alt sınıf, paket adı da dahil olmak üzere bu alt sınıfın sınıf adıdır:com.example.android.storageprovider.MyCloudProvider
. -
Paketinizin adı (bu örnekte
com.example.android.storageprovider
) ve içerik sağlayıcının türü olanandroid:authority
özelliği (documents
). android:exported
özelliği"true"
olarak ayarlandı. Diğer uygulamaların görebilmesi için sağlayıcınızı dışa aktarmanız gerekir.android:grantUriPermissions
özelliği"true"
olarak ayarlandı. Bu ayar, sistemin, diğer uygulamaların sağlayıcınızdaki içeriğe erişmesine izin vermesine olanak tanır. Bu diğer uygulamaların, sağlayıcınızdan gelen içeriğe erişmeye nasıl devam edebileceğini öğrenmek için İzinleri sürdürme bölümüne bakın.MANAGE_DOCUMENTS
izni. Varsayılan olarak bir sağlayıcı herkes tarafından kullanılabilir. Bu iznin eklenmesi sağlayıcınızı sistemle kısıtlar. Bu kısıtlama, güvenlik açısından önemlidir.- Sistem sağlayıcı aradığında sağlayıcınızın seçicide görünmesini sağlayan,
android.content.action.DOCUMENTS_PROVIDER
işlemini içeren bir amaç filtresi.
Bir sağlayıcı içeren örnek bir manifestten alıntıları burada bulabilirsiniz:
<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>
Android 4.3 ve önceki sürümleri çalıştıran cihazları destekleme
ACTION_OPEN_DOCUMENT
amacı yalnızca Android 4.4 ve sonraki sürümleri çalıştıran cihazlarda kullanılabilir.
Uygulamanızın, Android 4.3 ve önceki sürümleri çalıştıran cihazları barındırmak için ACTION_GET_CONTENT
desteklemesini istiyorsanız Android 4.4 veya sonraki sürümleri çalıştıran cihazlar için manifest dosyanızda ACTION_GET_CONTENT
intent filtresini devre dışı bırakmanız gerekir. Bir belge sağlayıcı ile ACTION_GET_CONTENT
karşılıklı olarak birbirini dışlamalıdır. İkisini de aynı anda destekliyorsanız uygulamanız sistem seçici kullanıcı arayüzünde iki kez görünür ve depolanan verilerinize iki farklı şekilde erişme imkanı sunar. Bu durum, kullanıcıların kafasını karıştırabilir.
Android 4.4 veya sonraki sürümleri çalıştıran cihazlarda ACTION_GET_CONTENT
intent filtresini devre dışı bırakmak için önerilen yöntem aşağıda açıklanmıştır:
res/values/
altındakibool.xml
kaynakları dosyanıza şu satırı ekleyin:<bool name="atMostJellyBeanMR2">true</bool>
res/values-v19/
altındakibool.xml
kaynakları dosyanıza şu satırı ekleyin:<bool name="atMostJellyBeanMR2">false</bool>
- 4.4 (API düzeyi 19) ve sonraki sürümler için
ACTION_GET_CONTENT
amaç filtresini devre dışı bırakmak üzere bir etkinlik takma adı ekleyin. Örneğin:<!-- 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>
Sözleşmeler
Özel bir içerik sağlayıcı yazdığınızda genellikle görevlerden biri,
İçerik sağlayıcılar geliştirici kılavuzunda açıklandığı gibi sözleşme sınıflarını uygulamaktır. Sözleşme sınıfı URI'ler, sütun adları, MIME türleri ve sağlayıcıyla ilgili diğer meta veriler için sabit tanımlar içeren bir public final
sınıfıdır. SAF, sizin için aşağıdaki sözleşme sınıflarını sağlar. Bu nedenle kendi bilgilerinizi yazmanıza gerek yoktur:
Örneğin, belge sağlayıcınız belge veya kök sorgulandığında imleç içinde döndürebileceğiniz sütunlar şunlardır:
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,};
Kök imlecinin, gerekli bazı sütunları içermesi gerekir. Bu sütunlar şunlardır:
Dokümanlara ilişkin imlecin aşağıdaki gerekli sütunları içermesi gerekir:
COLUMN_DOCUMENT_ID
COLUMN_DISPLAY_NAME
COLUMN_MIME_TYPE
COLUMN_FLAGS
COLUMN_SIZE
COLUMN_LAST_MODIFIED
DocumentsProvider alt sınıfı oluşturma
Özel belge sağlayıcı yazmanın bir sonraki adımı DocumentsProvider
soyut sınıfını alt sınıflandırmaktır. En azından aşağıdaki yöntemleri uygulamanız gerekir:
Bunlar kesinlikle uygulamanız gereken yegâne yöntemlerdir, ancak uygulamak isteyebileceğiniz daha pek çok yöntem vardır. Ayrıntılar için DocumentsProvider
sayfasını ziyaret edin.
Bir kök tanımlayın
queryRoots()
uygulamanız, DocumentsContract.Root
içinde tanımlanan sütunları kullanarak belge sağlayıcınızın tüm kök dizinlerine işaret eden bir Cursor
döndürmelidir.
Aşağıdaki snippet'te projection
parametresi, çağrıyı yapanın geri almak istediği belirli alanları temsil eder. Snippet yeni bir imleç oluşturur ve ona bir satır ekler: Bir kök dizin ya da İndirilenler veya Görseller gibi üst düzey bir dizin. Çoğu sağlayıcının yalnızca bir kökü vardır. Birden fazla hesabınız olabilir.
Örneğin, birden fazla kullanıcı hesabı söz konusuysa. Bu durumda, imlece
ikinci bir satır eklemeniz yeterlidir.
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; }
Doküman sağlayıcınız dinamik bir kök grubuna (örneğin, bağlantısı kesilmiş bir USB cihaza veya kullanıcının oturumunu kapatabileceği bir hesaba) bağlanırsa doküman kullanıcı arayüzünü, aşağıdaki kod snippet'inde gösterildiği gibi ContentResolver.notifyChange()
yöntemini kullanarak bu değişikliklerle senkronize olacak şekilde güncelleyebilirsiniz.
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);
Sağlayıcıdaki dokümanları listeleme
queryChildDocuments()
uygulamanız, DocumentsContract.Document
içinde tanımlanan sütunları kullanarak belirtilen dizindeki tüm dosyaları işaret eden bir Cursor
döndürmelidir.
Bu yöntem, kullanıcı seçici kullanıcı arayüzünde kök yapınızı seçtiğinde çağrılır.
Bu yöntem, COLUMN_DOCUMENT_ID
tarafından belirtilen belge kimliğinin alt öğelerini alır.
Daha sonra kullanıcı, belge sağlayıcınızın içinden bir alt dizin seçtiğinde sistem bu yöntemi çağırır.
Bu snippet, istenen sütunlarla yeni bir imleç oluşturur, ardından üst dizindeki her bir alt öğe hakkında imlece bilgi ekler. Alt öğe bir resim, başka bir dizin ve herhangi bir dosya olabilir.
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; }
Belge bilgilerini alma
queryDocument()
uygulamanız, DocumentsContract.Document
içinde tanımlanan sütunlar kullanılarak belirtilen dosyaya işaret eden bir Cursor
döndürmelidir.
queryDocument()
yöntemi, queryChildDocuments()
içinde iletilen bilgilerin aynısını döndürür, ancak belirli bir dosya için:
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; }
Belge sağlayıcınız, DocumentsProvider.openDocumentThumbnail()
yöntemini geçersiz kılarak ve desteklenen dosyalara FLAG_SUPPORTS_THUMBNAIL
işaretini ekleyerek belge için küçük resimler de sağlayabilir.
Aşağıdaki kod snippet'i, DocumentsProvider.openDocumentThumbnail()
öğesinin nasıl uygulanacağına dair bir örnek sunar.
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); }
Dikkat: Bir belge sağlayıcı, sizeHint
parametresiyle belirtilen boyutun iki katından daha fazla küçük resim görüntüsü döndürmemelidir.
Doküman açın
Belirtilen dosyayı temsil eden bir ParcelFileDescriptor
döndürmek için openDocument()
uygulamanız gerekir. Diğer uygulamalar, veri akışı için döndürülen ParcelFileDescriptor
özelliğini kullanabilir. Kullanıcı bir dosya seçtikten sonra sistem bu yöntemi çağırır ve istemci uygulaması openFileDescriptor()
yöntemini çağırarak dosyaya erişim isteğinde bulunur.
Örneğin:
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); } }
Belge sağlayıcınız dosya akışı sağlıyorsa veya karmaşık veri yapılarını işliyorsa createReliablePipe()
veya createReliableSocketPair()
yöntemlerini uygulamayı düşünün.
Bu yöntemler, ParcelFileDescriptor
nesne çifti oluşturmanıza olanak tanır. Böylece birini döndürüp diğerini ParcelFileDescriptor.AutoCloseOutputStream
veya ParcelFileDescriptor.AutoCloseInputStream
aracılığıyla gönderebilirsiniz.
Son dokümanları ve aramayı destekle
queryRecentDocuments()
yöntemini geçersiz kılarak ve FLAG_SUPPORTS_RECENTS
değerini döndürerek, belge sağlayıcınızın kök dizininde son değiştirilen dokümanların listesini sağlayabilirsiniz. Aşağıdaki kod snippet'inde queryRecentDocuments()
yöntemlerinin nasıl uygulanacağına dair bir örnek gösterilmektedir.
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; }
StorageProvider kod örneğini indirerek yukarıdaki snippet'in tam kodunu edinebilirsiniz.
Doküman oluşturma işlemini destekleyin
İstemci uygulamalarının, doküman sağlayıcınızın içinde dosya oluşturmasına izin verebilirsiniz.
Bir istemci uygulaması ACTION_CREATE_DOCUMENT
amacı gönderirse belge sağlayıcınız, ilgili istemci uygulamasının belge sağlayıcı içerisinde yeni dokümanlar oluşturmasına izin verebilir.
Belge oluşturmayı desteklemek için kök dosyanızda FLAG_SUPPORTS_CREATE
işareti bulunmalıdır.
İçlerinde yeni dosyaların oluşturulmasına izin veren dizinler FLAG_DIR_SUPPORTS_CREATE
işaretinin olması gerekir.
Belge sağlayıcınızın da createDocument()
yöntemini uygulaması gerekir. Bir kullanıcı, yeni dosya kaydetmek için doküman sağlayıcınızın içinden bir dizin seçtiğinde doküman sağlayıcıya createDocument()
çağrısı gönderilir. createDocument()
yönteminin uygulanması sonucunda, dosya için yeni bir COLUMN_DOCUMENT_ID
döndürüyorsunuz. Daha sonra istemci uygulaması, bu kimliği kullanarak dosya için bir herkese açık kullanıcı adı alabilir ve sonuç olarak, yeni dosyaya yazması için openDocument()
yöntemini çağırabilir.
Aşağıdaki kod snippet'inde, belge sağlayıcıda yeni bir dosyanın nasıl oluşturulacağı gösterilmektedir.
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); }
StorageProvider kod örneğini indirerek yukarıdaki snippet'in tam kodunu edinebilirsiniz.
Doküman yönetimi özelliklerini destekleme
Belge sağlayıcınız; dosya açma, oluşturma ve görüntülemenin yanı sıra istemci uygulamalarına dosyaları yeniden adlandırma, kopyalama, taşıma ve silme izni de verebilir. Doküman sağlayıcınıza doküman yönetimi işlevi eklemek için dokümanın COLUMN_FLAGS
sütununa, desteklenen işlevi belirten bir işaret ekleyin. Ayrıca, DocumentsProvider
sınıfının ilgili yöntemini de uygulamanız gerekir.
Aşağıdaki tabloda, belge sağlayıcının belirli özellikleri açığa çıkarmak için uygulaması gereken COLUMN_FLAGS
işareti ve DocumentsProvider
yöntemi gösterilmektedir.
Öne Çıkarın | İşaretle | Yöntem |
---|---|---|
Dosya silme |
FLAG_SUPPORTS_DELETE
|
deleteDocument()
|
Dosyaları yeniden adlandırma |
FLAG_SUPPORTS_RENAME
|
renameDocument()
|
Bir dosyayı doküman sağlayıcıdaki yeni bir üst dizine kopyalama |
FLAG_SUPPORTS_COPY
|
copyDocument()
|
Doküman sağlayıcıda bir dizinden diğerine dosya taşıma |
FLAG_SUPPORTS_MOVE
|
moveDocument()
|
Bir dosyayı üst dizininden kaldırma |
FLAG_SUPPORTS_REMOVE
|
removeDocument()
|
Sanal dosyaları ve alternatif dosya biçimlerini destekleme
Android 7.0'da (API düzeyi 24) kullanıma sunulan bir özellik olan sanal dosyalar, belge sağlayıcıların doğrudan bayt kodu temsili olmayan dosyalara görüntüleme erişimi sağlamalarına olanak tanır. Diğer uygulamaların sanal dosyaları görüntüleyebilmesi için belge sağlayıcınızın, sanal dosyalar için alternatif olarak açılabilir bir dosya temsili oluşturması gerekir.
Örneğin, bir belge sağlayıcının, diğer uygulamaların doğrudan açamadığı bir dosya biçimi (yani sanal bir dosya) içerdiğini düşünün.
Bir istemci uygulaması, CATEGORY_OPENABLE
kategorisi olmadan bir ACTION_VIEW
amacı gönderdiğinde kullanıcılar görüntüleme için belge sağlayıcıda bu sanal dosyaları seçebilir. Ardından belge sağlayıcı, sanal dosyayı resim gibi farklı ancak açılabilir bir dosya biçiminde döndürür.
Böylece istemci uygulaması, kullanıcının görüntülemesi için sanal dosyayı açabilir.
Sağlayıcıdaki bir dokümanın sanal olduğunu beyan etmek için queryDocument()
yöntemiyle döndürülen dosyaya FLAG_VIRTUAL_DOCUMENT
işaretini eklemeniz gerekir. Bu işaret, istemci uygulamalarına dosyanın doğrudan bayt kodu temsilinin olmadığı ve doğrudan açılamadığı konusunda uyarı verir.
Doküman sağlayıcınızdaki bir dosyanın sanal olduğunu belirtirseniz bu dosyayı resim veya PDF gibi başka bir MIME türünde kullanılabilir hale getirmeniz kesinlikle önerilir. Belge sağlayıcı, sanal dosyayı görüntülemek için desteklediği alternatif MIME türlerini getDocumentStreamTypes()
yöntemini geçersiz kılarak tanımlar. İstemci uygulamaları getStreamTypes(android.net.Uri, java.lang.String)
yöntemini çağırdığında sistem, belge sağlayıcının getDocumentStreamTypes()
yöntemini çağırır. Daha sonra getDocumentStreamTypes()
yöntemi, belge sağlayıcının dosya için desteklediği alternatif MIME türleri dizisi döndürür.
İstemci, belge sağlayıcının belgeyi görüntülenebilir bir dosya biçiminde oluşturabileceğini belirledikten sonra, istemci uygulaması openTypedAssetFileDescriptor()
yöntemini çağırır. Bu yöntem, belge sağlayıcının openTypedDocument()
yöntemini dahili olarak çağırır. Belge sağlayıcı, dosyayı istemci uygulamasına istenen dosya biçiminde döndürür.
Aşağıdaki kod snippet'inde getDocumentStreamTypes()
ve openTypedDocument()
yöntemlerinin basit bir şekilde uygulanması gösterilmektedir.
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(); }
Güvenlik
Belge sağlayıcınızın şifre korumalı bir bulut depolama hizmeti olduğunu ve siz dosyalarını paylaşmaya başlamadan önce kullanıcıların giriş yaptığından emin olmak istediğinizi varsayalım.
Kullanıcı giriş yapmamışsa uygulamanız ne yapmalıdır? Çözüm, queryRoots()
uygulamanızda sıfır kök döndürmektir. Yani boş bir kök imleç:
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; }
Diğer adım, getContentResolver().notifyChange()
yöntemini çağırmaktır.
DocumentsContract
etkinliğini hatırlıyor musunuz? URI'yı yapmak için
kullanıyoruz. Aşağıdaki snippet, kullanıcının giriş durumu her değiştiğinde sisteme belge sağlayıcınızın köklerini sorgulamasını söyler. Kullanıcı giriş yapmamışsa queryRoots()
çağrısı, yukarıda gösterildiği gibi boş bir imleç döndürür. Bu şekilde, sağlayıcının belgeleri yalnızca, kullanıcı sağlayıcıya giriş yapmışsa kullanılabilir.
Kotlin
private fun onLoginButtonClick() { loginOrLogout() getContentResolver().notifyChange( DocumentsContract.buildRootsUri(AUTHORITY), null ) }
Java
private void onLoginButtonClick() { loginOrLogout(); getContentResolver().notifyChange(DocumentsContract .buildRootsUri(AUTHORITY), null); }
Bu sayfayla ilgili örnek kod için şuraya bakın:
Bu sayfayla ilgili videolar için aşağıdaki kaynaklara göz atın:
- DevBytes: Android 4.4 Depolama Erişim Çerçevesi: Sağlayıcı
- Depolama Erişim Çerçevesi: DocumentsProvider Oluşturma
- Depolama Erişim Çerçevesi'ndeki Sanal Dosyalar
İlgili daha fazla bilgi için aşağıdaki konulara bakın:
- DocumentsProvider oluşturma
- Depolama Erişim Çerçevesi'ni kullanarak Files'ı açma
- İçerik Sağlayıcılarla İlgili Temel Bilgiler