FileProvider 原理简单分析

作者: 飛飛萨 | 来源:发表于2017-11-30 10:02 被阅读144次

    先来一个简单的配置和调用的例子:

    1. res/xml目录下创建filepaths.xml,配置如下:
    <paths xmlns:android="http://schemas.android.com/apk/res/android">
        <external-path name="myfiles" path="test"/>
    </paths>
    
    1. AndroidManifest.xml中Provider的配置如下:
    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.test.document.provider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android:support.FILE_PROVIDER_PATHS"
            android:resource="@xml/filepaths"/>
    </provider>
    
    1. 简单的使用代码如下:
    String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/test/abc.pdf";
    File file = new File(path);
    String authority = "com.test.document.provider"; //就是AndroidManifest.xml中Provider标签的authorities属性值
    Uri fileUri = FileProvider.getUriForFile(context, authority, file); //得到文件对应的Uri,此处得到的应该是:"content://com.test.document.provider/myfiles/abc.pdf"
    
    //根据uri和文件的mimetype打开文件
    Intent intent = new Intent();
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //此Intent的接受者将获取从uri中读取数据的权限
    String type = getContentResolver().getType(fileUri); //得到文件的mimetype
    intent.setDataAndType(fileUri, type);
    startActivity(intent);
    
    

    在startActivity后,系统会根据mimetype的类型弹出类似"选择要使用的应用"的对话框,让你选择用哪个应用打开你的uri所提供的文件,例如:

    image

    看上述调用的代码,很简单,可能就一句需要展开分析一下的:

    Uri fileUri = FileProvider.getUriForFile(context, authority, file); 
    

    如何从传入的authority和文件对象生成指定的uri:

    代码调用关系

    看上图:

    • 当我们调用getUriForFile的时候,首先执行了getPathStrategy方法。这个方法致力于返回一个实现PathStrategy接口的路径策略对象strategy。PathStrategy接口很简单,就要求实现getUriForFile(从文件得到对应的Uri)和getFileForUri(从Uri得到文件)两个接口方法。其具体的实现类就是SimplePathStrategy(后面贴代码)。得到strategy对象后,调用getUriForFile返回Uri即可!
    • getPathStrategy方法则主要是调用了parsePathStrategy方法从AndroidManifest.xml的<provider>节点解析得到name和具体路径的对应关系。然后把这个对应关系以authority作为key值,存入到缓存(sCache)中。例如,对应本文的例子,应该是:
      sCache
      有了这张对应表,以后要通过Uri得到对应的文件也变得简单了!

    下面是SimplePathStrategy类getUriForFile方法的源代码,主要是路径的匹配和Uri的组装:

    public Uri getUriForFile(File file) {
        String path;
        try {
            path = file.getCanonicalPath();
        } catch (IOException e) {
            throw new IllegalArgumentException("Failed to resolve canonical path for " + file);
        }
    
        // Find the most-specific root path
        Map.Entry<String, File> mostSpecific = null;
        for (Map.Entry<String, File> root : mRoots.entrySet()) {
            final String rootPath = root.getValue().getPath();
            if (path.startsWith(rootPath) && (mostSpecific == null
                    || rootPath.length() > mostSpecific.getValue().getPath().length())) {
                mostSpecific = root;
            }
        }
    
        if (mostSpecific == null) {
            throw new IllegalArgumentException(
                    "Failed to find configured root that contains " + path);
        }
    
        // Start at first char of path under root
        final String rootPath = mostSpecific.getValue().getPath();
        if (rootPath.endsWith("/")) {
            path = path.substring(rootPath.length());
        } else {
            path = path.substring(rootPath.length() + 1);
        }
    
        // Encode the tag and path separately
        path = Uri.encode(mostSpecific.getKey()) + '/' + Uri.encode(path, "/");
        return new Uri.Builder().scheme("content")
                .authority(mAuthority).encodedPath(path).build();
    }
    
    

    那么,Uri的接受者是如何凭借这个Uri得到对应文件的呢?答案在接受方调用:

    ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(Uri, "r");
    

    企图获取文件描述符的时候。在这个时候,FileProvider类的openFile会被调用。看一下openFile的源码:

    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
        // ContentProvider has already checked granted permissions
        final File file = mStrategy.getFileForUri(uri);
        final int fileMode = modeToMode(mode);
        return ParcelFileDescriptor.open(file, fileMode);
    }
    

    看到mStrategy了吧,就是一开始创建的路径策略对象,getFileForUri就是PathStrategy接口的另一个方法,一切都是熟悉的味道……
    拿到文件后返回文件描述符即可。

    再贴一下SimplePathStrategy类getFileForUri方法的源码:

    public File getFileForUri(Uri uri) {
        String path = uri.getEncodedPath();
    
        final int splitIndex = path.indexOf('/', 1);
        final String tag = Uri.decode(path.substring(1, splitIndex));
        path = Uri.decode(path.substring(splitIndex + 1));
    
        final File root = mRoots.get(tag);
        if (root == null) {
            throw new IllegalArgumentException("Unable to find configured root for " + uri);
        }
    
        File file = new File(root, path);
        try {
            file = file.getCanonicalFile();
        } catch (IOException e) {
            throw new IllegalArgumentException("Failed to resolve canonical path for " + file);
        }
    
        if (!file.getPath().startsWith(root.getPath())) {
            throw new SecurityException("Resolved path jumped beyond configured root");
        }
    
        return file;
    }
    

    相关文章

      网友评论

        本文标题:FileProvider 原理简单分析

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