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下):


    

说明:
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

当然对于元素,除了,还可以包含以下可使用的子节点:

 
路径:Context.getFilesDir() + /path

 
路径:Context.getCacheDir() + /path

 
路径:Context.getExternalFilesDir(String) + /path

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

2,在AndroidManifest.xml中定义FileProvider


    
        
            
        
    ...
    

其中,
元素的属性
name:android.support.v4.content.FileProvider,固定写法
authorities:指定了内容URI的提供者,一般是此应用的包名+“fileprovider”
元素的属性
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中提供文件类型支持


    
    ...
        
    

此处支持的是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共享文件)