FileProvider源码浅析与节点配置

大家都知道安卓7.0应用共享文件的行为变更
对于面向 Android 7.0 的应用,Android 框架执行的 StrictMode API 政策禁止在您的应用外部公开 file:// URI。如果一项包含文件 URI 的 intent 离开您的应用,则应用出现故障,并出现 FileUriExposedException 异常。
要在应用间共享文件,您应发送一项 content:// URI,并授予 URI 临时访问权限。进行此授权的最简单方式是使用FileProvider类。
首先在manifest里面注册provider。在之前的使用过程中有一点我一直是一知半解。就是配置节点文件
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/app_file_paths" />
其中app_file_paths这些节点与FileProvider到底有什么关系呢?
我们回到问题的原点FileProvider获取URI,FileProvider通过getUriForFile方法获取。我们点入进去看一下里面的实现

public static Uri getUriForFile(@NonNull Context context, @NonNull String authority, @NonNull File file) {
        FileProvider.PathStrategy strategy = getPathStrategy(context, authority);
        return strategy.getUriForFile(file);
 }
private static FileProvider.PathStrategy getPathStrategy(Context context, String authority) {
        HashMap var3 = sCache;
        synchronized(sCache) {
            FileProvider.PathStrategy strat = (FileProvider.PathStrategy)sCache.get(authority);
            if (strat == null) {
                try {
                    strat = parsePathStrategy(context, authority);
                } catch (IOException var6) {
                    throw new IllegalArgumentException("Failed to parse android.support.FILE_PROVIDER_PATHS meta-data", var6);
                } catch (XmlPullParserException var7) {
                    throw new IllegalArgumentException("Failed to parse android.support.FILE_PROVIDER_PATHS meta-data", var7);
                }

                sCache.put(authority, strat);
            }

            return strat;
        }
 }

关键代码parsePathStrategy(context, authority);

private static FileProvider.PathStrategy parsePathStrategy(Context context, String authority) throws IOException, XmlPullParserException {
        FileProvider.SimplePathStrategy strat = new FileProvider.SimplePathStrategy(authority);
        ProviderInfo info = context.getPackageManager().resolveContentProvider(authority, 128);
        XmlResourceParser in = info.loadXmlMetaData(context.getPackageManager(), "android.support.FILE_PROVIDER_PATHS");
        if (in == null) {
            throw new IllegalArgumentException("Missing android.support.FILE_PROVIDER_PATHS meta-data");
        } else {
            int type;
            while((type = in.next()) != 1) {
                if (type == 2) {
                    String tag = in.getName();
                    String name = in.getAttributeValue((String)null, "name");
                    String path = in.getAttributeValue((String)null, "path");
                    File target = null;
                    if ("root-path".equals(tag)) {
                        target = DEVICE_ROOT;
                    } else if ("files-path".equals(tag)) {
                        target = context.getFilesDir();
                    } else if ("cache-path".equals(tag)) {
                        target = context.getCacheDir();
                    } else if ("external-path".equals(tag)) {
                        target = Environment.getExternalStorageDirectory();
                    } else {
                        File[] externalMediaDirs;
                        if ("external-files-path".equals(tag)) {
                            externalMediaDirs = ContextCompat.getExternalFilesDirs(context, (String)null);
                            if (externalMediaDirs.length > 0) {
                                target = externalMediaDirs[0];
                            }
                        } else if ("external-cache-path".equals(tag)) {
                            externalMediaDirs = ContextCompat.getExternalCacheDirs(context);
                            if (externalMediaDirs.length > 0) {
                                target = externalMediaDirs[0];
                            }
                        } else if (VERSION.SDK_INT >= 21 && "external-media-path".equals(tag)) {
                            externalMediaDirs = context.getExternalMediaDirs();
                            if (externalMediaDirs.length > 0) {
                                target = externalMediaDirs[0];
                            }
                        }
                    }

                    if (target != null) {
                        strat.addRoot(name, buildPath(target, path));
                    }
                }
            }

            return strat;
        }
}

可以看到上段代码就是解析xml文件也就是我们的app_file_paths
然后添加到root里面。sCache就是保存了我们注册的provider的一个Map集合,它采用authority作为key。最终返回一个FileProvider.PathStrategy对象这是一个接口我们可以看到里面有两个方法

interface PathStrategy {
        Uri getUriForFile(File var1);

        File getFileForUri(Uri var1);
}

SimplePathStrategy实现了该接口。我们看看SimplePathStrategy的getUriForFile

        @Override
        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();
        }

path是文件的路径,mRoots是一个保存xml中节点的集合他是在parsePathStrategy()中的调用addRoot添加的
可以看到显示遍历mRoots最终找到一个与path路径最匹配的节点。这里插一下xml文件



    
    
    
     
    
     
     

external-path是包含external-files-path和external-cache-path的我们直接取external-path这样可以减少mRoot的匹配次数
所以建议少配置节点且把建议external-path的path设置为“.”也就是包括该目录下的全部路径
接下回到上面的代码中。在找到了最匹配的mostSpecific之后采用xml节点中name来代替mostSpecific这一部分。采用content作为schme这其实就是把file://包装成content。
值得一提的是如果配置的path越详细那么隐藏的路径内容也越多,安全性更高。

你可能感兴趣的:(FileProvider源码浅析与节点配置)