FileProvider 原理简单分析

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

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • 由于android系统中应用程序之间不能共享内存。因此,在不同应用程序之间交互数据(跨进程通讯)就稍微麻烦一些。在...
    Ten_Minutes阅读 8,251评论 1 7
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,209评论 0 17
  • 生活不止眼前的苟且,还有现实的无奈与纠结。 那些虚荣的故事太多听的人太少,付出的太少想要的太多。 想的太多做的太少...
    南方遇玉阅读 238评论 1 0