FileProvider共享文件

作者: 飛飛萨 | 来源:发表于2017-05-10 16:55 被阅读201次

    在应用间共享文件

    对于面向 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 + 临时文件名;
        //开始使用临时文件...
    }
    

    相关文章

      网友评论

        本文标题:FileProvider共享文件

        本文链接:https://www.haomeiwen.com/subject/lgjgtxtx.html