Android API Guides---Storage Access Framework

存储访问架构
Android 4.4系统(API级别19)推出存储访问框架(SAF)。新加坡武装部队变得非常简单,为用户在其所有自己喜欢的文件存储提供商的浏览和打开文档,图像和其他文件。一个标准的,易于使用的用户界面允许用户在浏览整个应用程序和供应商一致的方式文件和访问近期]。
云或本地存储服务可以通过实现封装他们的服务文档提供参与这个生态系统。需要访问一个供应商的文档可以使用SAF集成,只需几行代码客户端应用程序。
该SAF包括以下内容:
文件提供者的内容提供商允许存储服务(如谷歌驱动器)来揭示它管理的文件。文档提供者实现为DocumentsProvider类的子类。该文件提供商的架构是基于传统文件的层次结构,但如何将文档提供物理存储的数据是由你。 Android平台包括几个内置文档提供商,如下载,图片和视频。
客户端应用程序-A调用的ACTION_OPEN_DOCUMENT和/或ACTION_CREATE_DOCUMENT意图和接收文件供应商返回的文件的自定义应用程序。
选择器-A系统的用户界面,让所有的文件提供满足客户端应用程序的搜索条件的用户访问文件。
是一些由SAF提供的功能如下:
允许用户从所有文件提供者,而不仅仅是一个单一的应用程序浏览内容。
它使你的应用,以获得长期的,持续的访问由文件供应商所拥有的文件。通过这个接入用户可以添加,编辑,保存和删除文件的供应商。
支持多个用户帐户和瞬态根,如USB存储提供商,这只有在驱动器插入出现。
概观

该中心SAF周围是DocumentsProvider类的子类内容提供商。内的文件提供者,数据被构造为传统的文件层次结构:


图1.文档提供数据模型。根指向一个单一的文件,然后启动扇出整个树。


请注意以下几点:


每个文档提供报告的一个或多个“根”这是起点为探索文档的树。每一根都有一个唯一COLUMN_ROOT_ID,它指向一个文件(一个目录),表示根目录下的内容。根是设计动态,支持使用情况下,像多个帐户,短暂的USB存储设备或用户登录/注销。
在每一根都是一个单独的文档。该文件指向1至N的文件,其中每个依次可以指向1至N的文档。
每个存储后端通过一个独特的COLUMN_DOCUMENT_ID引用它们的表面单个文件和目录。文档ID必须是唯一的,因为它们是用来在设备重新启动持久URI补助没有改变,一旦发出。
文件可以是可打开的文件(具有特定MIME类型),或含有额外的文件的目录(用MIME_TYPE_DIR MIME类型)。
每个文档可以具有不同的能力,如通过COLUMN_FLAGS说明。例如,FLAG_SUPPORTS_WRITE,FLAG_SUPPORTS_DELETE和FLAG_SUPPORTS_THUMBNAIL。同一COLUMN_DOCUMENT_ID可以包含在多个目录。
控制流


如上所述,文档提供者数据模型是基于传统的文件的层次结构。但是,您可以物理存储你的数据,只要你喜欢,只要它可以通过DocumentsProvider API进行访问。例如,你可以使用你的数据基于标签的云存储。


图2示出的相片应用可能如何使用SAF访问存储的数据,例如:


图2.存储访问架构流程
请注意以下几点:
在SAF,供应商和客户不直接交互。客户端请求的权限与文件(即,阅读,编辑,创建或删除文件)进行交互。
当一个应用程序(在此例中,一个照片应用)触发意图ACTION_OPEN_DOCUMENT或行动CREATE_DOCUMENT交互启动。这样做的目的可能包括过滤器,以进一步细化标准,例如,“给我说有'形象'MIME类型的所有打开的文件。”
一旦意图火灾,该系统选择器前进到每个已注册的提供者和显示用户的匹配内容根源。
在选择器为用户提供了访问文档,即使底层文件提供者可能是非常不同的标准接口。例如,图2显示了谷歌驱动器提供商,USB提供商和云服务提供商。
图3显示了用户在其中搜索图像选择了谷歌驱动器帐户选择器:

Android API Guides---Storage Access Framework_第1张图片

图3.选择器


当用户选择谷歌Drive都显示的图像,如图4从这一点上,用户可以与之互动以任何方式被提供者和客户机应用程序的支持。

Android API Guides---Storage Access Framework_第2张图片

图4.图片


编写客户端应用程序


在Android 4.3和更低的,如果你希望你的应用程序可以从另一台应用程序文件时,它必须调用的意图,如ACTION_PICK或ACTION_GET_CONTENT。然后,用户必须选择其中一个应用程序来选择一个文件,并选择应用程序必须为用户提供的用户界面来浏览,并从可用的文件挑。


在Android 4.4及更高版本,可以选择使用ACTION_OPEN_DOCUMENT意图,其中显示由该允许用户浏览其他应用程序已提供的所有文件系统控制的选择器UI的附加选项。从该单个用户界面中,用户可以从任何所支持的应用程序的选择一个文件。


ACTION_OPEN_DOCUMENT并不旨在成为ACTION_GET_CONTENT更换。你应该使用一个取决于你的应用程序的需求:


如果你希望你的应用程序只需读取/导入数据使用ACTION_GET_CONTENT。用这种方法,应用导入的数据,拷贝诸如图像文件。
如果你希望你的应用,以获得长期的,持续的访问由文件提供者拥有的文档使用ACTION_OPEN_DOCUMENT。一个例子是一个照片编辑应用程序,允许用户编辑存储在文档图像提供商。
本节将介绍如何根据ACTION_OPEN_DOCUMENT和ACTION_CREATE_DOCUMENT意图编写客户端应用程序。


搜索文件


下面的代码片断使用ACTION_OPEN_DOCUMENT搜索包含图像文件的文件提供者:

private static final int READ_REQUEST_CODE = 42;
...
/**
 * Fires an intent to spin up the "file chooser" UI and select an image.
 */
public void performFileSearch() {

    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file
    // browser.
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);

    // Filter to only show results that can be "opened", such as a
    // file (as opposed to a list of contacts or timezones)
    intent.addCategory(Intent.CATEGORY_OPENABLE);

    // Filter to show only images, using the image MIME data type.
    // If one wanted to search for ogg vorbis files, the type would be "audio/ogg".
    // To search for all documents available via installed storage providers,
    // it would be "*/*".
    intent.setType("image/*");

    startActivityForResult(intent, READ_REQUEST_CODE);
}
请注意以下几点:
当应用程序触发ACTION_OPEN_DOCUMENT意图,它将启动一个显示所有匹配的文件提供者选择器。
添加类别CATEGORY_OPENABLE到意图对结果进行过滤,以便仅显示可以打开文件,如图像文件。
声明intent.setType(“图像/*”)进一步过滤仅显示有图像MIME数据类型的文档。
处理结果
一旦用户选择器中选择一种文件的onActivityResult()被调用。指向被选择的文档的URI包含在结果数据参数。提取URI使用的getData()。一旦你拥有它,你可以用它来获取用户想要的文件。 例如:

@Override
public void onActivityResult(int requestCode, int resultCode,
        Intent resultData) {

    // The ACTION_OPEN_DOCUMENT intent was sent with the request code
    // READ_REQUEST_CODE. If the request code seen here doesn't match, it's the
    // response to some other intent, and the code below shouldn't run at all.

    if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
        // The document selected by the user won't be returned in the intent.
        // Instead, a URI to that document will be contained in the return intent
        // provided to this method as a parameter.
        // Pull that URI using resultData.getData().
        Uri uri = null;
        if (resultData != null) {
            uri = resultData.getData();
            Log.i(TAG, "Uri: " + uri.toString());
            showImage(uri);
        }
    }
}
检查文档元数据


一旦你的URI为一个文件,你可以访问它的元数据。这段代码抓住由URI指定的文件元数据,并记录它:

public void dumpImageMetaData(Uri uri) {

    // The query, since it only applies to a single document, will only return
    // one row. There's no need to filter, sort, or select fields, since we want
    // all fields for one document.
    Cursor cursor = getActivity().getContentResolver()
            .query(uri, null, null, null, null, null);

    try {
    // moveToFirst() returns false if the cursor has 0 rows.  Very handy for
    // "if there's anything to look at, look at it" conditionals.
        if (cursor != null && cursor.moveToFirst()) {

            // Note it's called "Display Name".  This is
            // provider-specific, and might not necessarily be the file name.
            String displayName = cursor.getString(
                    cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
            Log.i(TAG, "Display Name: " + displayName);

            int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
            // If the size is unknown, the value stored is null.  But since an
            // int can't be null in Java, the behavior is implementation-specific,
            // which is just a fancy term for "unpredictable".  So as
            // a rule, check if it's null before assigning to an int.  This will
            // happen often:  The storage API allows for remote files, whose
            // size might not be locally known.
            String size = null;
            if (!cursor.isNull(sizeIndex)) {
                // Technically the column stores an int, but cursor.getString()
                // will do the conversion automatically.
                size = cursor.getString(sizeIndex);
            } else {
                size = "Unknown";
            }
            Log.i(TAG, "Size: " + size);
        }
    } finally {
        cursor.close();
    }
}
打开一个文档


一旦你的URI为一个文件,你可以打开它,或者你想用它做其他。


位图


下面是如何你可能会打开一个位图的例子:

private Bitmap getBitmapFromUri(Uri uri) throws IOException {
    ParcelFileDescriptor parcelFileDescriptor =
            getContentResolver().openFileDescriptor(uri, "r");
    FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
    Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
    parcelFileDescriptor.close();
    return image;
}
请注意,你不应该做的UI线程此操作。这样做的背景下,使用AsyncTask的。一旦你打开了位图,您可以在ImageView的显示。


获取一个InputStream


这里是你如何能得到从URI的InputStream一个例子。在这个片段中,该文件的行被读入一个字符串:

private String readTextFromUri(Uri uri) throws IOException {
    InputStream inputStream = getContentResolver().openInputStream(uri);
    BufferedReader reader = new BufferedReader(new InputStreamReader(
            inputStream));
    StringBuilder stringBuilder = new StringBuilder();
    String line;
    while ((line = reader.readLine()) != null) {
        stringBuilder.append(line);
    }
    fileInputStream.close();
    parcelFileDescriptor.close();
    return stringBuilder.toString();
}
创建一个新文档
您的应用程序可以创建在使用操作CREATE_DOCUMENT意图文档提供一个新的文档。要创建你给你的意图MIME类型和文件名的文件,并具有独特的请求的代码启动。其余的是照顾你:

// Here are some examples of how you might call this method.
// The first parameter is the MIME type, and the second parameter is the name
// of the file you are creating:
//
// createFile("text/plain", "foobar.txt");
// createFile("image/png", "mypicture.png");

// Unique request code.
private static final int WRITE_REQUEST_CODE = 43;
...
private void createFile(String mimeType, String fileName) {
    Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);

    // Filter to only show results that can be "opened", such as
    // a file (as opposed to a list of contacts or timezones).
    intent.addCategory(Intent.CATEGORY_OPENABLE);

    // Create a file with the requested MIME type.
    intent.setType(mimeType);
    intent.putExtra(Intent.EXTRA_TITLE, fileName);
    startActivityForResult(intent, WRITE_REQUEST_CODE);
}
一旦你创建一个新文档,你可以得到其的onActivityResult(URI),这样就可以继续写吧。
删除文件
如果你有URI为一个文件和文档的Document.COLUMN flags包含SUPPORTS删除,您可以删除该文件。 例如:

DocumentsContract.deleteDocument(getContentResolver(), uri);
编辑文档


您可以使用SAF到位,编辑一个文本文件。这段代码触发ACTION_OPEN_DOCUMENT意图和使用类别CATEGORY_OPENABLE以只显示可打开的文档。它还过滤器只显示文本文件:

private static final int EDIT_REQUEST_CODE = 44;
/**
 * Open a file for writing and append some text to it.
 */
 private void editDocument() {
    // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's
    // file browser.
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);

    // Filter to only show results that can be "opened", such as a
    // file (as opposed to a list of contacts or timezones).
    intent.addCategory(Intent.CATEGORY_OPENABLE);

    // Filter to show only text files.
    intent.setType("text/plain");

    startActivityForResult(intent, EDIT_REQUEST_CODE);
}
接下来,从的onActivityResult()(请参阅处理结果),你可以调用代码来执行编辑。下面的代码片段获取从ContentResolver的一个FileOutputStream。在默认情况下它使用“写入”模式。这是最好的做法,要求您需要访问最少的,所以不要问读/写,如果你需要的是写:

private void alterDocument(Uri uri) {
    try {
        ParcelFileDescriptor pfd = getActivity().getContentResolver().
                openFileDescriptor(uri, "w");
        FileOutputStream fileOutputStream =
                new FileOutputStream(pfd.getFileDescriptor());
        fileOutputStream.write(("Overwritten by MyCloud at " +
                System.currentTimeMillis() + "\n").getBytes());
        // Let the document provider know you're done by closing the stream.
        fileOutputStream.close();
        pfd.close();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}
坚持权限


当你的应用程序打开进行读取或写入文件时,系统会给你的应用该文件的URI权限授予。它会一直持续到用户的设备重新启动。但是,假设你的应用程序是一个图像编辑应用程序,并希望用户能够从你的应用程序访问他们所编辑的最后5张图片,直接。如果用户的设备重新启动后,你必须给用户发送回系统选择器来查找文件,这显然是不理想的。


为了防止这种情况发生,你可以坚持的系统给您的应用程序的权限。实际上,你的应用程序“需要”,该系统提供了持久化的URI权限授予。这使得通过您的应用程序文件的用户继续访问,即使该设备已经重新启动:

final int takeFlags = intent.getFlags()
            & (Intent.FLAG_GRANT_READ_URI_PERMISSION
            | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
// Check for the freshest data.
getContentResolver().takePersistableUriPermission(uri, takeFlags);
还有最后一步。您可能已经保存您的应用程序访问最新的URI,但它们可能不再有效,另一个应用程序可能已删除或修改的文件。因此,你应该总是调用getContentResolver()。takePersistableUriPermission()来检查最新的数据。


编写自定义文档提供


如果您正在开发,对于文件(如云端储存服务)提供存储服务的应用程序,你可以通过编写自定义文档提供使可通过SAF文件。本节介绍了如何做到这一点。


表现


要实现自定义文档提供商,以下内容添加到您的应用程序的清单:


API级别19或更高的目标。
A <provider>元素声明自定义的存储供应商。
你的供应商,这是它的类名,包括包名的名称。例如:com.example.android.storageprovider.MyCloudProvider。
你的权威,这是您的包名称的名称(在该例子中,com.example.android.storageprovider)加的内容提供者(文件)的类型。例如,com.example.android.storageprovider.documents。
属性android:导出设置为“真”。您必须导出你的供应商,使其他应用程序可以看到它。
属性android:grantUriPermissions设置为“真”。此设置允许系统授予的其他应用程序访问内容提供商。对于如何坚持赠款为特定文档的讨论,参见坚持权限。
该MANAGE_DOCUMENTS许可。默认情况下,供应商是提供给大家。添加该权限限制你的供应商系统。此限制是出于安全很重要。
Android的:启用属性设置为在资源文件中定义一个布尔值。这个属性的目的是禁止在运行Android 4.3或更低的设备供应商。例如,机器人:启用=“@布尔/ atLeastKitKat”。除了包括在清单此属性,你需要做到以下几点:
在根据RES /值的bool.xml资源文件/添加​​此行:

<bool name="atLeastKitKat">false</bool>
In your  bool.xml  resources file under  res/values-v19/ , add this line:

<bool name="atLeastKitKat">true</bool>
一个意图过滤器,其中包括android.content.action.DOCUMENTS提供商的行动,让你的供应商在系统搜索提供商出现在选择器。
下面是从包括一个提供程序的示例清单摘录:

<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"
            android:enabled="@bool/atLeastKitKat">
            <intent-filter>
                <action android:name="android.content.action.DOCUMENTS_PROVIDER" />
            </intent-filter>
        </provider>
    </application>

</manifest>
运行Android4.3和更低的配套器件


该ACTION_OPEN_DOCUMENT意图是仅适用于运行Android 4.4及更高版本的设备可用。如果你希望你的应用程序支持ACTION_GET_CONTENT,以适应正在运行的是Android 4.3和更低的设备,你应该在你的清单中禁止ACTION_GET_CONTENT意图过滤器运行Android4.4或更高版本的设备。文档提供者和ACTION_GET_CONTENT应考虑相互排斥。如果同时支持他们两个,你的应用程序将在系统选择器UI中出现两次,提供访问您的存储数据的两种不同方式。这会让用户感到困惑。


这里是禁止用于运行Android版本4.4或更高版本的设备的ACTION_GET_CONTENT意图过滤的推荐方式:


在根据RES /值的bool.xml资源文件/添加此行

<bool name="atMostJellyBeanMR2">true</bool>
In your  bool.xml  resources file under  res/values-v19/ , add this line:

bool name="atMostJellyBeanMR2">false</bool>

添加活动别名禁用4.4版本的ACTION_GET_CONTENT意图过滤器(API等级19)高。 例如:

<!-- 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>
合同


通常,当你写一个自定义的内容提供商,任务之一是实施合同类,作为内容提供商的开发人员指南中所述。合同类是包含的URI,涉及到供应商常量定义,列名,MIME类型和其他元数据有public final类。新加坡武装部队提供了这些合同类你,所以你不需要编写自己的:


DocumentsContract.Document
DocumentsContract.Root
例如,这里有您可能会在当你的文档提供查询的文档或根光标返回列:

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,};
子类DocumentsProvider
编写定制文档提供下一步是继承抽象类的文档提供。至少,你需要实现以下方法:
查询根()
queryChildDocuments()
queryDocument()
使用openDocument()
这些都是你实现严格要求的唯一方法,但是还有更多你可能想。见DocumentsProvider了解详情。
实施queryRoots
你的实现queryRoots的()必须返回指向您的文档提供的所有根目录中的光标,使用DocumentsContract.Root定义的列。
在下面的片段中,投影参数表示调用者想要找回特定字段。该片断创建一个新的光标,并增加了一个行吧,一个根,一个顶级目录,如下载或图像。大多数供应商只能有一个根。你可能有一个以上的,例如,在多个用户帐户的情况下。在这种情况下,只要添加一个第二排的光标。

@Override
public Cursor queryRoots(String[] projection) throws FileNotFoundException {

    // Create 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.
    // Construct one row for a root called "MyCloud".
    final MatrixCursor.RowBuilder row = result.newRow();
    row.add(Root.COLUMN_ROOT_ID, ROOT);
    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 once it's shared.
    row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(mBaseDir));

    // The child MIME types are used to filter the roots and only present to the
    //  user roots that contain the desired type somewhere in their file hierarchy.
    row.add(Root.COLUMN_MIME_TYPES, getChildMimeTypes(mBaseDir));
    row.add(Root.COLUMN_AVAILABLE_BYTES, mBaseDir.getFreeSpace());
    row.add(Root.COLUMN_ICON, R.drawable.ic_launcher);

    return result;
}
实施queryChildDocuments
你的实现查询子文档()必须返回一个指向一个光标到指定目录下的所有文件,使用DocumentsContract.Document定义的列。
当你选择的选择器UI应用程序根目录时调用此方法。它得到的根目录下的一个目录的子文档。它可以在文件层次结构的任何级别被调用,而不仅仅是根。这段代码使得与请求的列的新光标,然后将有关的父目录,将光标每一个直系子女的信息。一个孩子可以是图片,另一个目录的任何文件:

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

实施queryDocument
你的实现查询文档()的必须返回一个指向指定的文件,使用DocumentsContract.Document定义的列光标。
该queryDocument()方法返回在查询子文档()传递相同的信息,但对于一个特定的文件:

@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;
}
实现使用openDocument
您必须实现使用openDocument()返回表示指定文件ParcelFileDescriptor。其他应用程序可以使用返回ParcelFileDescriptor流数据。当用户选择一个文件和客户端应用程序请求访问它通过调用打开的文件描述符()系统调用此方法。 例如:
@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 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);
    }
}
安全
假设你的文档提供一个密码保护的云存储服务,并要确保用户在你开始分享他们的文件之前登录。什么应该您的应用程序做,如果用户没有登录?解决的办法是在执行查询根()返回零根。也就是说,一个空的根光标:
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;
}
另一步骤是调用getContentResolver()。有NotifyChange()。还记得DocumentsContract? We'are用它来使这个URI。下面的代码片断告诉系统查询您的文档提供每当用户的登录状态变化的根源。如果用户没有登录,打电话查询根()返回一个空光标,如上图所示。这保证了如果用户登录到提供者的提供者的文件才可用。
private void onLoginButtonClick() {
    loginOrLogout();
    getContentResolver().notifyChange(DocumentsContract
            .buildRootsUri(AUTHORITY), null);
}

你可能感兴趣的:(android,api,sdk,阅读,谷歌)