先来一个简单的配置和调用的例子:
- res/xml目录下创建filepaths.xml,配置如下:
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="myfiles" path="test"/>
</paths>
- 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>
- 简单的使用代码如下:
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;
}
网友评论