FileProvider共享文件

在应用间共享文件

对于面向 Android 7.0 的应用,Android 框架执行的 StrictMode API 政策禁止在您的应用外部公开 file:// URI。如果一项包含文件 URI 的 intent 离开您的应用,则应用出现故障,并出现 FileUriExposedException 异常。要在应用间共享文件,您应发送一项 content:// URI,并授予 URI 临时访问权限。进行此授权的最简单方式是使用 FileProvider 类。—— Google关于Android 7.0行为变更中的说明

关于Uri(Uniform Resource Indentifier)

结构:
[scheme:]scheme-specific-part[#fragment]
[scheme:][//authority][path][?query][#fragment]
[scheme:][//host:port][path][?query][#fragment]

例1:
http://192.168.6.80:8080/download/affix_read_blob.jsp?id=100&name=aaa.doc#fragid1
scheme:http
    scheme-specific-part://192.168.6.80:8080/download/affix_read_blob.jsp?id=100&name=aaa.doc
        authority:192.168.6.80:8080
            host:192.168.6.80
            port:8080
        path:/download/affix_read_blob.jsp
        query:id=100&name=aaa.doc
fragment:fragid01
例2:
content://com.tpp.testfileprovider/extfiles/abc.doc
scheme:content
    scheme-specific-part://com.tpp.testfileprovider/extfiles/abc.doc
        authority:com.tpp.testfileprovider
            host:com.tpp.testfileprovider
            port:-1
        path:/extfiles/abc.doc
        query:null
fragment:null

Uri得到各数据项的方法:

  • getScheme();
  • getSchemeSpecificPart();
  • getFragment();
  • getPath();
  • getQuery();
  • getAuthority();
  • getHost();
  • getPort();

在android中,除了scheme、authority是必须要有的,其它的几个path、query、fragment,它们每一个可以选择性的要或不要,但顺序不能变。

使用FileProvider

1,指定可共享的目录

创建可共享目录配置文件(假设叫filepaths.xml,创建在res/xml下):

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="my_download" path="download"/>
</paths>

说明:
external-path 代表的是:
Environment.getExternalStorageDirectory() 这个路径。
此时,path属性的值为:download,所以,可共享的目录为

Environment.getExternalStorageDirectory() + "/download";

注意,只有可共享的目录中的文件才被允许使用,否则程序会抛异常:failed to find configured root that contains 路径+文件;
此时,name属性为“my_download”,它是一个相对路径,假设上述路径中存在一个pdf文件:"abc.pdf",
则它的相对路径就是:/my_download/abc.pdf

当然对于<paths>元素,除了<external-path>,还可以包含以下可使用的子节点:

<file-path name="name" path="path"/> 
路径:Context.getFilesDir() + /path

<cache-path name="name" path="path"/> 
路径:Context.getCacheDir() + /path

<external-files-path name="name" path="path"/> 
路径:Context.getExternalFilesDir(String) + /path

<external-cache-path name="name" path="path"/> 
路径:Context.getExternalCacheDir() + /path
注意:external-cache-path直到support-v4:25.0.0才支持

2,在AndroidManifest.xml中定义FileProvider

<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
    package="com.example.myapp">
    <application
    ...>
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.example.myapp.fileprovider"
            android:grantUriPermissions="true"
            android:exported="false">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/filepaths" />
        </provider>
    ...
    </application>
</manifest>

其中,
<provider>元素的属性
name:android.support.v4.content.FileProvider,固定写法
authorities:指定了内容URI的提供者,一般是此应用的包名+“fileprovider”
<meta-data>元素的属性
name:android.support.FILE_PROVIDER_PATHS,固定写法
resource:就是第一步中定义的可共享目录xml文件的路径

3,代码实现(得到文件Uri并调起指定的Activity)

String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/download/abc.pdf";
File file = new File(path);
String authority = "com.example.myapp.fileprovider"; //就是上述第二步中指定的authorities
Uri fileUri = FileProvider.getUriForFile(context, authority, file);
Intent intent = new Intent();
//此处使用的是显示调用,也可以是用隐式调用
intent.setComponent(new ComponentName(
        "aaa.bbb.ccc", "aaa.bbb.ccc.dddActivity)); //根据自己的项目配置
if (fileUri != null)
{
    //Intent的接受者将会临时被赋予读取Intent中URI数据的权限
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

    String type = getContentResolver().getType(fileUri);
    intent.setDataAndType(fileUri, type);
    startActivity(intent);
}

4,被调用方需要配置和实现的地方:

4.1,AndroidManifest.xml中提供文件类型支持

<activity>
    <intent-filter>
    ...
        <data android:mimeType="application/pdf" />
    </intent-filter>
</activity>

此处支持的是pdf格式类型

4.2,得到Uri提供的具体内容:

String data = intent.getDataString();
if (data != null && data.startsWith("content://"))
{
    // 得到文件名
    String decodeUri = Uri.decode(data);
    int index = decodeUri.lastIndexOf("/");
    String fileName = decodeUri.substring(index + 1);

    // 得到Uri
    Uri dataUri = Uri.parse(data);

    ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(dataUri, "r");
    FileDescriptor fd = pfd.getFileDescriptor();

    // 创建输入流,得到这个流之后,应用程序可以选择通过流来解析文件,或者把流存成本地临时文件再打开处理
    InputStream is = new FileInputStream(fd);

    //...
}

一句话概括FileProvider:我给你一个文件流,你不要管文件是哪里来的!

再提供一个Uri生成本地临时文件的方法:
private class GetProviderFile
{
    private Uri mFileUri;
    private String mTempFileDirectory;
    private String mTempFileName;

    public GetProviderFile(Uri providerFileUri, String tempFileDirectory, String tempFileName)
    {
        mFileUri = providerFileUri;
        mTempFileDirectory = tempFileDirectory;
        mTempFileName = tempFileName;
    }

    protected Boolean execute()
    {
        boolean bFinished = false;
        try
        {
            // 判断SD卡是否存在,并且是否具有读写权限
            if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED))
            {
                ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(mFileUri, "r");
                FileDescriptor fd = pfd.getFileDescriptor();

                // 创建输入流
                InputStream is = new FileInputStream(fd);

                // 创建临时文件
                File file = new File(mTempFileDirectory);
                // 判断文件目录是否存在
                if (!file.exists())
                {
                    file.mkdir();
                }
                File fileLocal = new File(mTempFileDirectory, mTempFileName);
                FileOutputStream fos = new FileOutputStream(fileLocal);
                // 缓存
                byte buf[] = new byte[512];
                // 写入到文件中
                do
                {
                    int num_read = is.read(buf);
                    if (num_read <= 0)
                    {
                        bFinished = true;
                        break;
                    }
                    // 写入文件
                    fos.write(buf, 0, num_read);
                } while (true);

                fos.close();
                is.close();
            }
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }

        return bFinished;
    }
}

说明:此方法是同步方法,但是按照AsyncTask模式来写的,很容易改成异步方式。
使用:

GetProviderFile uri2file = new GetProviderFile (uri, 临时文件目录,, 临时文件名);
if (uri2file.execute())
{
    String filePath = 临时文件目录 + File.separator + 临时文件名;
    //开始使用临时文件...
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • 如果本文帮助到你,本人不胜荣幸,如果浪费了你的时间,本人深感抱歉。希望用最简单的大白话来帮助那些像我一样的人。如果...
    Wing_Li阅读 16,270评论 11 17
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • Android N系列适配---FileProvider Android 7.0的适配,主要包含方面: Andro...
    25a58172fbb5阅读 6,988评论 3 32
  • 块级元素,确定网站架构,页面布局,承载内容,撑起网页的元素div,H元素,ul+ol>li,p,form,tabl...
    skoll阅读 239评论 0 0