Android API Guides---Storage Access Framework

存储訪问架构

Android 4.4系统(API级别19)推出存储訪问框架(SAF)。新加坡武装部队变得很easy,为用户在其全部自己喜欢的文件存储提供商的浏览和打开文档,图像和其它文件。一个标准的,易于使用的用户界面同意用户在浏览整个应用程序和供应商一致的方式文件和訪问最近]。

云或本地存储服务能够通过实现封装他们的服务文档提供參与这个生态系统。

须要訪问一个供应商的文档能够使用SAF集成,仅仅需几行代码client应用程序。

该SAF包含下面内容:

文件提供者的内容提供商同意存储服务(如谷歌驱动器)来揭示它管理的文件。文档提供者实现为DocumentsProvider类的子类。

该文件提供商的架构是基于传统文件的层次结构。但怎样将文档提供物理存储的数据是由你。

Android平台包含几个内置文档提供商。例如以下载,图片和视频。

client应用程序-A调用的ACTION_OPEN_DOCUMENT和/或ACTION_CREATE_DOCUMENT意图和接收文件供应商返回的文件的自己定义应用程序。

选择器-A系统的用户界面。让全部的文件提供满足client应用程序的搜索条件的用户訪问文件。

是一些由SAF提供的功能例如以下:

同意用户从全部文件提供者,而不不过一个单一的应用程序浏览内容。

它使你的应用,以获得长期的。持续的訪问由文件供应商所拥有的文件。通过这个接入用户能够加入,编辑,保存和删除文件的供应商。

支持多个用户帐户和瞬态根。如USB存储提供商,这仅仅有在驱动器插入出现。

概观

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

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" >

图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,供应商和客户不直接交互。client请求的权限与文件(即。阅读。编辑,创建或删除文件)进行交互。

当一个应用程序(在此例中,一个照片应用)触发意图ACTION_OPEN_DOCUMENT或行动CREATE_DOCUMENT交互启动。

这样做的目的可能包含过滤器,以进一步细化标准,比如,“给我说有‘形象‘MIME类型的全部打开的文件。

一旦意图火灾,该系统选择器前进到每一个已注冊的提供者和显示用户的匹配内容根源。

在选择器为用户提供了訪问文档。即使底层文件提供者可能是很不同的标准接口。比如,图2显示了谷歌驱动器提供商。USB提供商和云服务提供商。

图3显示了用户在当中搜索图像选择了谷歌驱动器帐户选择器:

图3.选择器

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

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" >

图4.图片

编写client应用程序

在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意图编写client应用程序。

搜索文件

以下的代码片断使用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流数据。

当用户选择一个文件和client应用程序请求訪问它通过调用打开的文件描写叙述符()系统调用此方法。

比如:

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

安全

如果你的文档提供一个password保护的云存储服务。并要确保用户在你開始分享他们的文件之前登录。

什么应该您的应用程序做,如果用户没有登录?解决的办法是在运行查询根()返回零根。也就是说,一个空的根光标:

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

时间: 2024-10-12 20:01:57

Android API Guides---Storage Access Framework的相关文章

android存储访问框架Storage Access Framework

在了解storage access framework 之前,我们先来看看android4.4中的一个特性.如果我们希望能选择android手机中的一张图片,通常都是发送一个Intent给相应的程序,一般这个程序是系统自带的图库应用(如果你的手机中有两个图库类的app 很可能会叫你选择一个),这个Intent一般是这样写的: Intent intent=new Intent(Intent.ACTION_GET_CONTENT);//ACTION_OPEN_DOCUMENT intent.addC

2.App Components-Content Providers/Storage Access Framework

1. Storage Access Framework Android 4.4 (API level 19) introduces the Storage Access Framework (SAF). The SAF makes it simple for users to browse and open documents, images, and other files across all of their their preferred document storage provide

android API Guides学习--Introduction(1)

android介绍: android提供了丰富的应用程序开发框架,它允许你在java语言环境中为移动设备创建独特的应用程序与环境. 1android应用程序提供多个入口点 android 应用程序是由不同的组件组合而成的,每个组件都被单独调用.activity组件提供一块屏幕作为使用者的界面.service独立的在幕后执行. 你可以使用intent类来实现组件的切换.也可以在一个app中多次调用同一个组件.例如在一个地图应用程序中显示地址的activity组件.该模型为一个单一的应用程序提供了多

Android API Guides – Introduction to Android

Android介绍 声明: 本文由Gordon翻译 发布于www.dlvoice.com 欢迎转载,但请保留此声明 原文地址:http://developer.android.com/guide/index.html Android提供了丰富的应用框架以便用户能够创建使用Java的环境来在移动设备上开发有创新性的应用和游戏.Android API Guides文档将会详细描述如何使用Android不同API来开发应用. 假如你是Android开发的新手,最好首先了解一下下面列出的Android

翻译Android API Guides: App Manifest

原文在这里:http://developer.android.com/guide/topics/manifest/manifest-intro.html *Manifest译作"清单",这里沿用英文便于理解,其它术语同理. **文中链接都会跳转到android开发者网站. App Manifest 每一个应用都必须在它的根目录有一份AndroidManifest.xml文件(必须使用这个名字).Android系统必须在运行应用的任何代码之前了解一些重要信息,这些信息就来自于这份mani

Android API Guides –Device Compatibility

设备的兼容性 声明: 本文由Gordon翻译 发布于www.dlvoice.com 欢迎转载,但请保留此声明 原文地址:http://developer.android.com/guide/practices/compatibility.html Android可以运行在不同类型的设备上,从手机到平板以及电视都可以运行.这些不同的设备将为开发者提供大量的潜在用户.那么为了能够让你的应用在所有的设备上运行良好,你就需要接受一些特性的多样性以及提供一个不同屏幕配置的友好的用户接口. 为了达到这样的目

【Android API Guides 简译(三)】Data Storage--Storage Options

Android提供了几种永久储存手机数据的选项,而我们选择存储的方式依据于我们存储的不同的特定需求,比如你的数据是否需要只对自己公开,数据是否可以被其他应用得到或者你想要储存多大的数据. 数据存储的方式如下: Shared Preferences 通过xml类型的键值对,存储私密的原始数据. Internal Storage 内部存储 通过手机内存存储私密数据 External Storage 外部存储 在设备外部共享里存储公开的数据 SQLite Databases Android 原生内部数

Android API Guides –System Permissions

系统权限 声明: 本文由Gordon翻译 发布于www.dlvoice.com 欢迎转载,但请保留此声明 原文地址:http://developer.android.com/guide/topics/security/permissions.html Android是一个特权分离的操作系统,运行在其上的应用都有一个特定的系统身份(Linux的用户ID和组ID).系统的部分也会被分为特定的身份,Linux就是通过这个身份来区别各个应用的. 更加详细的安全特性是通过"权限"机制来控制一个进

【Android API Guides 简译(一)】App Resourses--Overview

将数据与程序分开的原因,表面是为了独立的管理数据,深层原因是使App兼容不同的环境即使你的数据支持不同语言或者不同屏幕大小的特殊设备.这是非常且越来越重要的! 对于各种各样的资源,我们统一分成两种: 默认资源和针对不同环境的备选资源 举个例子,默认资源存放在res/layout/ directory下,针对于横摆方向的设备的备选资源存放在res/layout-land/ directory(横摆方向的设备的具体方式见图).当只有默认资源时,见图1.当设置了备选资源时,见图二,Android系统会

android API Guides学习--Introduction(2)

应用程序基本原理: android应用程序是用java语言编写的,android SDK工具在APK(Android package)里编译代码(数据文件和资源文件).apk是一个后缀为.apk的档案文件,1个apk文件包含了android应用程序的所有内容,apk是用来安装应用程序的基于android的设备.一旦安装了apk文件,每个app运行在自己安全的沙盒中. android操作系统是一个多用户的Linux操作系统,每一个app有一个不同的用户. 系统默认给各自的app分配一个唯一的Lin