FileProvider 原理简单分析

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

  1. res/xml目录下创建filepaths.xml,配置如下:

    

  1. AndroidManifest.xml中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所提供的文件,例如:

FileProvider 原理简单分析_第1张图片
image

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

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

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

FileProvider 原理简单分析_第2张图片
代码调用关系

看上图:

  • 当我们调用getUriForFile的时候,首先执行了getPathStrategy方法。这个方法致力于返回一个实现PathStrategy接口的路径策略对象strategy。PathStrategy接口很简单,就要求实现getUriForFile(从文件得到对应的Uri)和getFileForUri(从Uri得到文件)两个接口方法。其具体的实现类就是SimplePathStrategy(后面贴代码)。得到strategy对象后,调用getUriForFile返回Uri即可!
  • getPathStrategy方法则主要是调用了parsePathStrategy方法从AndroidManifest.xml的节点解析得到name和具体路径的对应关系。然后把这个对应关系以authority作为key值,存入到缓存(sCache)中。例如,对应本文的例子,应该是:
    FileProvider 原理简单分析_第3张图片
    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 mostSpecific = null;
    for (Map.Entry 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 原理简单分析)