목차
概要
流程图
데이터 베이스 MySQL 튜토리얼 android存储访问框架Storage Access Framework

android存储访问框架Storage Access Framework

Jun 07, 2016 pm 03:50 PM
android storage 저장 액자 입장

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

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

Intent intent=new Intent(Intent.ACTION_GET_CONTENT);//ACTION_OPEN_DOCUMENT  

intent.addCategory(Intent.CATEGORY_OPENABLE);  

intent.setType("image/jpeg");  

使用这样的一种方法来选择图片在android4.4中会直接弹出一个很漂亮的界面,有点像一个文件管理器,其实他比文件管理器更强大,他是一个内容提供器,可以按照目录一层一层的选择文件,也可以按照文件种类选择文件,比如图片、视频、音频等,还可以打开一个应用程序选择文件,界面如下:

android存储访问框架Storage Access Framework --android存储访问框架Storage Access Framework

android存储访问框架Storage Access Framework --android存储访问框架Storage Access Framework

其实这是一个叫做documentsui的内置程序,因为它的manifest没有带LAUNCHERactivity所以不会显示在桌面上。

下面是正文:

Storage Access Framework

Android4.4中引入了Storage Access Framework存储访问框架,简称(SAF)。SAF为用户浏览手机中存储的内容提供了方便,这些内容不仅包括文档、图片,视频、音频、下载,而且还包括所有由特定ContentProvider(须具有约定的API)提供的内容。不管这些内容来自于哪里,不管是哪个应用调用浏览系统文件内容的命令,系统都会用一个统一的界面让你去浏览。


这种能力姑且叫做一种生态系统,云存储以及本地存储都可以通过实DocumentsProvider来参与到这个系统中。而客户端app要使用SAF提供的服务只需几行代码即可。

SAF框架包括以下内容:

1Document provider文件内容提供方

这是一个特殊的content provider(内容提供方),他让一个存储服务(比如Google Drive)可以对外展示自己所管理的文件。一个Document provider其实就是实现了DocumentsProvider的子类。document-provider的schema 和传统的文件存径格式一致,但是至于你的内容是怎么存储的完全取决于你自己,android系统中已经内置了几个这样Document provider比如关于下载、图片以及视频的Document provider。(注意这里的红色DocumentsProvider是一个类,而分开写的Document provider只是一种描述,因为翻译出来可能会让人忘了他的特殊身份。)

2)客户端app

一个触发ACTION_OPEN_DOCUMENT或者ACTION_CREATE_DOCUMENTintent的客户端软件。通过触发ACTION_OPEN_DOCUMENT或者ACTION_CREATE_DOCUMENT客户端可以接收来自于Document provider的内容。

3)选择器Picker

选择器其实就是一个类似于文件管理器的界面,而且是系统级别的界面,他提供了访问满足客户端过滤条件的所有Document provider内容的通道。说的具体点选择器就是文章开头提到的documentsui程序

SAF的一些特性:

用户可以浏览所有document provider提供的内容,不光是一个app

提供了长期、持续的访问document provider中文件的能力以及数据的持久化,用户可以实现添加、删除、编辑、保存document provider所维护的内容。

支持多用户以及临时性的内容服务,比如USB storage providers只有当驱动安装成功才会出现。

概要

SAF的核心是实现了DocumentsProvider子类,即内容提供者documentprovider)。documentprovider中数据是以传统的文件目录树组织起来的:

android存储访问框架Storage Access Framework

流程图

虽说documentprovider中数据是以传统的文件目录树组织起来的,但是那只是对外表现的形式,至于你的数据在内部究竟是怎么样(甚至完全杂乱无章),完全取决于你自己,只要你对外的接口能够通过DocumentsProviderapi访问就可以。

下面的流程图展示了一个photo应用使用SAF可能的结构:

android存储访问框架Storage Access Framework

从上图可以看出选择器PickerSystem UI)是一个链接调用者与内容提供者的桥梁。它提供了一个UI同时也告诉了调用者可以选择哪些内容提供者,比如这里的DriveDocProviderUsbDocProviderCloundDocProvider

当客户端appDocument provider之间的交互是在触发了ACTION_OPEN_DOCUMENT或者ACTION_CREATE_DOCUMENT intent之后,intent还可以进一步设置过滤条件:比如限制MIME typeimage

intent触发之后选择器去寻找每一个注册了的provider,并将provider的符合条件的根目录显示出来。

选择器(即documentsui)为访问不同形式、不同来源的文件提供了统一的界面,你可以看到我的文件形式可以是图片、视频,文件的内容可以是来自本地或者是Google Drive的云服务。

下图显示了用户在选择图片的时候点中了Google Drive的情况。

android存储访问框架Storage Access Framework

客户端是如何调用的

在android4.3时代,如果你想从另外一个app中选择一个文件,比如从图库中选择一张图片文件,你必须触发一个intent比如ACTION_PICK或者ACTION_GET_CONTENT。然后在候选的app中选择一个app,从中获得你想要的文件,最关键的是被选择的app中要具有能为你提供文件的功能,如果一个不负责任的第三方开发者注册了一个恰恰符合你需求的intent,但是没有实现返回文件的功能,那么就会出现意想不到的错误。

4.4中,你多了一个选择方式,你可以发送ACTION_OPEN_DOCUMENTintent来调用系统的documentsui来选择任何文件,不需要再依赖于其他的app了。

但是并不是说ACTION_GET_CONTENT就完全没有用了,如果你只是打开读取一个文件,ACTION_GET_CONTENT还是可以的,如果你是要有写入编辑的需求,那就用ACTION_OPEN_DOCUMENT

注: 实际上在4.4系统中ACTION_GET_CONTENT启动的还是documentsui

下面演示如何用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 intent发出以后documentsui会显示所有满足条件的document provider(显示的是他们的标题),以图片为例,其实它对应的document providerMediaDocumentsProvider(在系统源码中),而访问MediaDocumentsProviderURi形式为com.android.providers.media.documents

如果在intent filter中加入categoryCATEGORY_OPENABLE的条件,则显示结果只有可以打开的文件,比如图片文件(思考一下 ,哪些是不可以打开的呢?);

如果设置intent.setType("image/*")则只显示MIME typeimage的文件。

获取返回的结果

返回结果一般是一个uri,数据保存在onActivityResult的第三个参数resultData中,通过resultData.getData()获取uri

@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获取文件的元数据。下面演示了如何得到元数据信息,并打印到log中。

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();
    }
}
로그인 후 복사

还可以获得bitmap(这段代码我也没看懂): 

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;
로그인 후 복사

获得输出流
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();
}
로그인 후 복사


如何创建一个新的文件

使用ACTION_CREATE_DOCUMENT intent来创建文件

// 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。

删除文件

前提是Document.COLUMN_FLAGS包含SUPPORTS_DELETE

DocumentsContract.deleteDocument(getContentResolver(), uri);

实现自己的document provider

如果你希望自己应用的数据也能在documentsui中打开,你就需要写一个自己的document provider。下面介绍自定义DocumentsProvider的步骤。

api 19+

首先你需要在manifest文件中声明有这样一个Provider:

Provider的name为类名加包名,比如:

com.example.android.storageprovider.MyCloudProvider

Authority为包名+provider的类型名,如:

Com.example.android.storageprovider.documents

android:exported属性的值为ture

下面是一个provider的例子写法:

<manifest...>
    ...
    <uses-sdk android:minsdkversion="19" android:targetsdkversion="19"></uses-sdk>
        ....
        <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"></action>
            </intent-filter>
        </provider>
    
</manifest...>
로그인 후 복사

DocumentsProvider的子类

你至少要实现如下几个方法:

queryRoots()

queryChildDocuments()

queryDocument()

openDocument()

还有些其他的方法,但并不是必须的。

下面演示一个实现访问文件(file)系统的DocumentsProvider的大致写法。

queryRoots的实现:
@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的实现
@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的实现
@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;
}
로그인 후 복사

为了更好的理解这篇文章,可以参考下面这些链接。


参考文章

https://developer.android.com/guide/topics/providers/document-provider.htm这篇文章的英文原文要翻墙

http://blog.csdn.net/huangyanan1989/article/details/17263203Android4.4中获取资源路径问题因为Storage Access Framework而引起的

https://github.com/iPaulPro/aFileChooser 一个文件管理器,在4.4中他是直接启用了documentsui

https://github.com/ianhanniballake/LocalStorage一个自定义的DocumentsProvider

https://github.com/xin3liang/platform_packages_providers_MediaProvider  实现了查询多媒体文件的DocumentsProvider,包括查询图片,这个是系统里面的



본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

AI Hentai를 무료로 생성하십시오.

인기 기사

R.E.P.O. 에너지 결정과 그들이하는 일 (노란색 크리스탈)
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 최고의 그래픽 설정
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 아무도들을 수없는 경우 오디오를 수정하는 방법
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25 : Myrise에서 모든 것을 잠금 해제하는 방법
4 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)

새로운 보고서는 소문난 삼성 갤럭시 S25, 갤럭시 S25 플러스, 갤럭시 S25 울트라 카메라 업그레이드에 대한 비판적인 평가를 제공합니다. 새로운 보고서는 소문난 삼성 갤럭시 S25, 갤럭시 S25 플러스, 갤럭시 S25 울트라 카메라 업그레이드에 대한 비판적인 평가를 제공합니다. Sep 12, 2024 pm 12:23 PM

최근 아이스 유니버스는 삼성의 차기 플래그십 스마트폰으로 널리 알려진 갤럭시 S25 울트라에 대한 세부 정보를 꾸준히 공개해 왔습니다. 무엇보다도 유출자는 삼성이 카메라 업그레이드를 하나만 가져올 계획이라고 주장했습니다.

삼성 갤럭시 S25 울트라, 디자인 변경 루머가 공개된 첫 번째 렌더링 이미지 유출 삼성 갤럭시 S25 울트라, 디자인 변경 루머가 공개된 첫 번째 렌더링 이미지 유출 Sep 11, 2024 am 06:37 AM

OnLeaks는 이제 Android Headlines와 제휴하여 X(이전 Twitter) 팔로어로부터 4,000달러 이상의 수익을 창출하려는 시도가 실패한 지 며칠 후 Galaxy S25 Ultra에 대한 첫 번째 모습을 제공합니다. 맥락에 따라 h 아래에 포함된 렌더링 이미지

IFA 2024 | TCL의 NXTPAPER 14는 성능 면에서는 Galaxy Tab S10 Ultra와 일치하지 않지만 크기에서는 거의 일치합니다. IFA 2024 | TCL의 NXTPAPER 14는 성능 면에서는 Galaxy Tab S10 Ultra와 일치하지 않지만 크기에서는 거의 일치합니다. Sep 07, 2024 am 06:35 AM

TCL은 두 가지 새로운 스마트폰을 발표하는 것과 함께 NXTPAPER 14라는 새로운 Android 태블릿도 발표했는데, TCL의 거대한 화면 크기는 판매 포인트 중 하나입니다. NXTPAPER 14는 TCL의 시그니처 브랜드인 무광택 LCD 패널 버전 3.0을 갖추고 있습니다.

새로운 보고서는 소문난 삼성 갤럭시 S25, 갤럭시 S25 플러스, 갤럭시 S25 울트라 카메라 업그레이드에 대한 비판적인 평가를 제공합니다. 새로운 보고서는 소문난 삼성 갤럭시 S25, 갤럭시 S25 플러스, 갤럭시 S25 울트라 카메라 업그레이드에 대한 비판적인 평가를 제공합니다. Sep 12, 2024 pm 12:22 PM

최근 아이스 유니버스는 삼성의 차기 플래그십 스마트폰으로 널리 알려진 갤럭시 S25 울트라에 대한 세부 정보를 꾸준히 공개해 왔습니다. 무엇보다도 유출자는 삼성이 카메라 업그레이드를 하나만 가져올 계획이라고 주장했습니다.

Vivo Y300 Pro는 7.69mm의 슬림한 본체에 6,500mAh 배터리를 탑재했습니다. Vivo Y300 Pro는 7.69mm의 슬림한 본체에 6,500mAh 배터리를 탑재했습니다. Sep 07, 2024 am 06:39 AM

Vivo Y300 Pro는 방금 완전히 공개되었으며 대용량 배터리를 갖춘 가장 얇은 중급 Android 휴대폰 중 하나입니다. 정확히 말하면 스마트폰의 두께는 7.69mm에 불과하지만 배터리 용량은 6,500mAh입니다. 최근 출시된 것과 동일한 용량이다.

Samsung Galaxy S24 FE는 4가지 색상과 2가지 메모리 옵션으로 예상보다 낮은 가격으로 출시될 예정 Samsung Galaxy S24 FE는 4가지 색상과 2가지 메모리 옵션으로 예상보다 낮은 가격으로 출시될 예정 Sep 12, 2024 pm 09:21 PM

삼성전자는 팬에디션(FE) 스마트폰 시리즈를 언제 업데이트할지 아직 힌트를 주지 않았다. 현재 상태로 Galaxy S23 FE는 2023년 10월 초에 출시된 회사의 최신 버전으로 남아 있습니다.

Xiaomi Redmi Note 14 Pro Plus는 Light Hunter 800 카메라를 탑재한 최초의 Qualcomm Snapdragon 7s Gen 3 스마트폰으로 출시됩니다. Xiaomi Redmi Note 14 Pro Plus는 Light Hunter 800 카메라를 탑재한 최초의 Qualcomm Snapdragon 7s Gen 3 스마트폰으로 출시됩니다. Sep 27, 2024 am 06:23 AM

Redmi Note 14 Pro Plus는 이제 작년 Redmi Note 13 Pro Plus(Amazon에서 현재 $375)의 직접적인 후속 제품으로 공식화되었습니다. 예상대로 Redmi Note 14 Pro Plus는 Redmi Note 14 및 Redmi Note 14 Pro와 함께 Redmi Note 14 시리즈를 주도합니다. 리

iQOO Z9 Turbo Plus: 잠재적으로 강화된 시리즈 플래그십에 대한 예약 시작 iQOO Z9 Turbo Plus: 잠재적으로 강화된 시리즈 플래그십에 대한 예약 시작 Sep 10, 2024 am 06:45 AM

OnePlus의 자매 브랜드 iQOO는 2023-4년 제품 주기가 거의 끝날 수 있습니다. 그럼에도 불구하고 브랜드는 Z9 시리즈가 아직 끝나지 않았다고 선언했습니다. 최종이자 아마도 최고급인 Turbo+ 변형이 예상대로 발표되었습니다. 티

See all articles