Özel doküman sağlayıcı oluşturma

Dosyalar için depolama hizmetleri sağlayan bir uygulama (ör. buluta kaydetme hizmetinizle birlikte) dosyalarınızı Özel bir doküman sağlayıcı yazarak Depolama Erişim Çerçevesi (SAF) Bu sayfada, özel doküman sağlayıcısı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ış

Manifest

Özel bir doküman sağlayıcı uygulamak için aşağıdakileri ekleyin: manifesto:

  • API düzeyi 19 veya üstünü hedefleyen bir hedef.
  • Özel depolama alanınızı tanımlayan bir <provider> öğesi sağlar.
  • android:name özelliği DocumentsProvider alt sınıf, sınıfının adıdır. Bu paket adı, paket adını da içerir:

    com.example.android.storageprovider.MyCloudProvider.

  • android:authority özelliği, yani paketinizin adıdır (bu örnekte, com.example.android.storageprovider) ve içerik sağlayıcının türünü (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". Bu ayar, sistemin diğer uygulamalara erişim izni vermesine olanak tanır daha fazla bilgi edineceksiniz. Bu diğer uygulamaların daha fazla bilgi edinmek için Kalıcı izinleriyle ilgili daha fazla bilgi edinin.
  • MANAGE_DOCUMENTS izni. Varsayılan olarak bir sağlayıcı mevcuttur herkese açık hale getiriyoruz. Bu iznin eklenmesi, sağlayıcınızın sistemle kısıtlanmasını sağlar. Bu kısıtlama, güvenlik açısından önemlidir.
  • Şunu içeren bir intent filtresi: android.content.action.DOCUMENTS_PROVIDER işlemi uygulayarak sağlayıcınızın Sistem, sağlayıcı aradığında seçicide görünür.

Aşağıda, bir sağlayıcı içeren örnek bir manifest'ten alıntılar verilmiştir:

<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ı destekleyen cihazlar

İlgili içeriği oluşturmak için kullanılan ACTION_OPEN_DOCUMENT intent yalnızca kullanılabilir Android 4.4 ve sonraki sürümleri çalıştıran cihazlarda. Uygulamanızın ACTION_GET_CONTENT özelliğini desteklemesini istiyorsanız Android 4.3 ve önceki sürümleri çalıştıran cihazları kullanılabilmesi için şurada ACTION_GET_CONTENT intent filtresini devre dışı bırakın: manifest dosyanız. CEVAP belge sağlayıcı ve ACTION_GET_CONTENT göz önünde bulundurulmalıdır birlikte kullanılamaz. Her ikisini de aynı anda destekliyorsanız uygulamanız sistem seçici kullanıcı arayüzünde iki kez görünür ve iki farklı erişim yolu sunar. saklı tutar. Bu durum kullanıcıların kafasını karıştırabilir.

Cihazlar için ACTION_GET_CONTENT intent filtresi Android 4.4 veya daha yeni bir sürüm kullanıyorsanız:

  1. res/values/ altındaki bool.xml kaynakları dosyanıza bu satırda:
    <bool name="atMostJellyBeanMR2">true</bool>
  2. res/values-v19/ altındaki bool.xml kaynakları dosyanıza bu satırda:
    <bool name="atMostJellyBeanMR2">false</bool>
  3. Bir etkinlik takma adı ile ACTION_GET_CONTENT amacını devre dışı bırakın 4.4 (API düzeyi 19) ve sonraki sürümler için filtre uygulayın. Örnek:
    <!-- 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 (bkz. İçerik Sağlayıcılar geliştirici kılavuzu. Sözleşmeli sınıf, public final sınıftır sütun adları, MIME türleri ve meta verileri de ekleyebilirsiniz. SAF bu sözleşme derslerini size sunar. Böylece, sahibi:

Örneğin, Belge sağlayıcınızın dokümanlar veya kök dizin için sorgulanması:

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 dizininizin bazı gerekli sütunları içermesi gerekir. Bu sütunlar şunlardır:

Dokümanlar imlecinin aşağıdaki gerekli sütunları içermesi gerekir:

DocumentsProvider alt sınıfı oluşturma

Özel bir doküman sağlayıcısı yazmanın bir sonraki adımı, soyut sınıf DocumentsProvider. En azından aşağıdaki yöntemleri uygulayın:

Mutlaka uygulamanız gereken yöntemler bunlardır ancak arama yapabilirsiniz. Bkz. DocumentsProvider inceleyebilirsiniz.

Kök tanımlayın

queryRoots() uygulamanızın, tümCursor içinde tanımlanan sütunları kullanarak doküman sağlayıcınızın kök dizinlerini DocumentsContract.Root.

Aşağıdaki snippet'te projection parametresi arayanın geri dönmek istediği belirli alanlar vardır. Snippet yeni bir imleç oluşturuyor ve ona bir satır ekler: Bir kök, bir üst düzey dizin, İndirilenler veya Resimler. Çoğu sağlayıcının yalnızca bir kökü vardır. Birden fazla birden fazla kullanıcı hesabı olduğunda. Bu durumda, imleçten ikinci satırı seçin.

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;
}

Belge sağlayıcınız dinamik bir kök kümesine (ör. bir USB kablosuna) bağlanıyorsa bağlantısı kesilmiş olabilecek bir cihaz veya kullanıcının oturumunu kapatabileceği bir hesap dokümanın kullanıcı arayüzünü, ContentResolver.notifyChange() yöntemini çağırın.

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(). içindeki tüm dosyalara işaret eden bir Cursor döndürmelidir şunda tanımlanan sütunları kullanarak belirtilen dizin: DocumentsContract.Document.

Bu yöntem, kullanıcı seçici arayüzünde kökünüzü seçtiğinde çağrılır. Yöntem, COLUMN_DOCUMENT_ID Kullanıcı bir öğeyi seçtiğinde sistem bu yöntemi alt dizine ekleyin.

Bu snippet, istenen sütunlarla yeni bir imleç oluşturur ve bilgileri içerir. 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 al

queryDocument(). belirtilen dosyaya işaret eden bir Cursor döndürmesi gerekir, (DocumentsContract.Document adresinde tanımlanan sütunlar kullanılıyor).

queryDocument() yönteminde, queryChildDocuments(), 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 ayrıca, geçersiz kılma DocumentsProvider.openDocumentThumbnail() yöntemini kullanarak FLAG_SUPPORTS_THUMBNAIL flag'i desteklenen dosyalara ekleyin. Aşağıdaki kod snippet'i, 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);
}

Dikkat: Bir doküman sağlayıcı, iki kattan daha fazla küçük resim döndürmemelidir sizeHint parametresiyle belirtilen boyut.

Doküman açma

Şunu temsil eden bir ParcelFileDescriptor döndürmek için openDocument() uygulamanız gerekir: belirtilen dosya. Diğer uygulamalar döndürülen ParcelFileDescriptor kullanabilir veri akışı gerçekleştirebilirsiniz. Kullanıcı bir dosya seçtikten sonra sistem bu yöntemi çağırır. Ardından istemci uygulaması, openFileDescriptor() Örnek:

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ışı gerçekleştiriyorsa veya karmaşık dosya işlemleri yapıyorsa veri yapılarını kullanıyorsanız createReliablePipe(). veya createReliableSocketPair() yöntem. Bu yöntemler bir çift ParcelFileDescriptor nesne (birini döndürebilirsiniz) ve diğerini ParcelFileDescriptor.AutoCloseOutputStream veya ParcelFileDescriptor.AutoCloseInputStream.

Son dokümanları ve aramayı destekleyin

Web sitenizin kök dizininde, yakın zamanda değiştirilen dokümanların listesini geçersiz kılarak doküman sağlayıcıyı queryRecentDocuments() yöntemi ve geri dönüyor FLAG_SUPPORTS_RECENTS, Aşağıdaki kod snippet'i, queryRecentDocuments() yöntem.

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;
}

Yukarıdaki snippet'e ilişkin kodun tamamını StorageProvider kod örneğidir.

Doküman oluşturmayı destekleyin

İstemci uygulamalarının doküman sağlayıcınızda dosya oluşturmasına izin verebilirsiniz. Bir istemci uygulaması ACTION_CREATE_DOCUMENT gönderirse böyle bir isteğiniz varsa, doküman sağlayıcınız, o istemci uygulamasının yeni dokümanlar ekleyebilirsiniz.

Belge oluşturma işleminin desteklenmesi için kök biriminizin FLAG_SUPPORTS_CREATE işareti. Kendi içinde yeni dosyalar oluşturulmasına izin veren dizinlerin, FLAG_DIR_SUPPORTS_CREATE. tıklayın.

Belge sağlayıcınızın ayrıca createDocument() yöntemini çağırın. Bir kullanıcı yeni bir dosya kaydetmesi için doküman sağlayıcısına bir çağrı alır. createDocument() createDocument() yöntemi, yeni bir yöntem döndürüyorsunuz Şu tarih için COLUMN_DOCUMENT_ID: dosyası olarak kaydedebilirsiniz. Ardından istemci uygulaması, dosya için bir herkese açık kullanıcı adı almak üzere bu kimliği kullanabilir. ve nihayetinde de Yeni dosyaya yazmak için openDocument() tuşlarına basın.

Aşağıdaki kod snippet'i, Doküman sağlayıcı olabilir.

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);
}

Yukarıdaki snippet'e ilişkin kodun tamamını StorageProvider kod örneğidir.

Doküman yönetimi özelliklerini destekleme

Doküman sağlayıcınız, dosya açma, oluşturma ve görüntülemenin yanı sıra istemci uygulamalarının yeniden adlandırma, kopyalama, taşıma ve silme işlemlerini yapmasına da izin verebilir dosyası olarak da kaydedebilir. Google Drive'a doküman yönetimi işlevi eklemek için bir bayrak ekleyin. COLUMN_FLAGS sütun belirtin. Ayrıca ekip üyelerinden ilgili DocumentsProvider yöntemi sınıfını kullanır.

Aşağıdaki tabloda COLUMN_FLAGS işareti ve DocumentsProvider yöntemi de dahil olmak üzere sağlayıcının belirli özellikleri sunması gerekir.

Özellik İşaretle Yöntem
Dosya silme FLAG_SUPPORTS_DELETE deleteDocument()
Dosyayı yeniden adlandırma FLAG_SUPPORTS_RENAME renameDocument()
Dosyayı, doküman sağlayıcı içindeki yeni bir üst dizine kopyalama FLAG_SUPPORTS_COPY copyDocument()
Bir dosyayı doküman sağlayıcı içinde bir dizinden diğerine taşıma FLAG_SUPPORTS_MOVE moveDocument()
Bir dosyayı üst dizininden kaldırma FLAG_SUPPORTS_REMOVE removeDocument()

Sanal dosyaları ve alternatif dosya biçimlerini destekleme

Sanal dosyalar Android 7.0'da (API seviyesi 24) kullanıma sunulan bir özellik, kullanılmayan dosyalara görüntüleme erişimi sağlamak için bayt kodu temsili kullanılır. Diğer uygulamaların sanal dosyaları görüntüleyebilmesini sağlamak için: Belge sağlayıcınızın, açılabilir alternatif bir dosya oluşturması gerekiyor. temsil eder.

Örneğin, bir doküman sağlayıcısının diğer uygulamaların doğrudan açamayacağı biçim, temelde sanal bir dosya. İstemci uygulaması bir ACTION_VIEW niyeti gönderdiğinde CATEGORY_OPENABLE kategorisi hariç Kullanıcılar bu sanal dosyaları doküman sağlayıcı içinden seçebilir görüntüleyin. Ardından, belge sağlayıcı sanal dosyayı döndürür. bir resim dosyası olarak yükleyebilirsiniz. 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 FLAG_VIRTUAL_DOCUMENT. queryDocument() yöntemidir. Bu işaret, istemci uygulamalarını, dosyanın doğrudan bir bağlantısı olmadığı konusunda uyarır bayt kodu temsilidir ve doğrudan açılamaz.

Belge sağlayıcınızdaki bir dosyanın sanal olduğunu belirtirseniz sunumunuzu başka bir yerde sunmanız önemle tavsiye edilir Resim veya PDF gibi bir MIME türü. Belge sağlayıcı tanımladığı alternatif MIME türlerini bildirir geçersiz kılarak sanal bir dosyanın görüntülenmesini destekler. getDocumentStreamTypes(). yöntemidir. İstemci uygulamaları getStreamTypes(android.net.Uri, java.lang.String). yöntemini çağırırken sistem getDocumentStreamTypes() tercih edebilirsiniz. İlgili içeriği oluşturmak için kullanılan getDocumentStreamTypes(). yöntemi kullanılarak çalıştırılan doküman sağlayıcının dosya için desteklediğini gösterir.

Müşteri, belirlediği bu doküman sağlayıcının dokümanı görüntülenebilir bir dosyada oluşturabilmesini istemci uygulaması, openTypedAssetFileDescriptor(). yöntemini kullanır. Bu yöntem, belge sağlayıcının openTypedDocument() yöntemidir. Belge sağlayıcı, dosyayı istemci uygulamasına geri gönderir. .

Aşağıdaki kod snippet'i, getDocumentStreamTypes(). ve openTypedDocument() yöntemlerine göz atın.

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();
}

Güvenlik

Belge sağlayıcınızın şifre korumalı bir bulut depolama alanı hizmeti olduğunu varsayalım. ve dosyalarını paylaşmaya başlamadan önce kullanıcıların giriş yaptığından emin olmak istiyorsanız. Kullanıcı giriş yapmamışsa uygulamanız ne yapmalıdır? Çözüm, sisteme queryRoots() uygulamanızda sıfır kök 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() işlevini çağırmaktır. DocumentsContract adlı yeri hatırlıyor musunuz? Bunu kullanarak bu URI'yı kullanın. Aşağıdaki snippet, sisteme kullanıcının giriş durumu her değiştiğinde doküman sağlayıcısına izin verilir. Kullanıcı giriş yapılmışsa queryRoots() öğesine yapılan bir çağrı, boş imleç, yukarıda gösterildiği gibi. Bu, sağlayıcının dokümanlarının yalnızca Kullanıcı, sağlayıcıya giriş yaptığında 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 sayfa ile ilgili örnek kod için bkz:

Bu sayfayla ilgili videolar için şuraya bakın:

Daha fazla ilgili bilgi için aşağıdaki kaynakları inceleyebilirsiniz: