FileProvider 路径配置策略的理解
FileProvider 路径配置策略的理解
★ FileProvider的使用
在AndroidManifest.xml中
<provider android:name="android.support.v4.content.FileProvider" android:authorities="set_your_package_name" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepath_data" /> provider>
通常设置android:exported="false"
,以保证权限最小化。
android:resource="@xml/filepath_data"
中,filepath_data.xml文件是配置哪些路径是可以通过FileProvider访问的。
meta-data是以键值对的方式保存(key-value pairs)。android.support.FILE_PROVIDER_PATHS
作为meta-data的键(key),@xml/filepath_data
作为meta-data的值(value)。在FileProvider中会读取meta-data中的android.support.FILE_PROVIDER_PATHS
对应的值。
filepath_data.xml
<?xml version="1.0" encoding="utf-8"?><paths> <files-path name="my_files" path="tempfiles" /> <external-path name="my_external" path="Download"/> <cache-path name="my_cache" />paths>
files-path
对应app的/data/data/
目录,path="tempfiles"
是指子目录,即完整的目录为/data/data/
。
external-path
对应的是内置的sdcard目录/sdcard/
,path="Download"
是子目录,完整目录为 /sdcard/Download
。
cache-path
对应的是/data/data/
,这个例子里没有子目录。
name
属性相当于这些路径的别名,通过name可以获取到相对应的路径。
★ 如何更好地理解这几个路径的用法?
通过学习Android中解析filepath_data.xml文件的源代码,可以更容易理解和掌握这些路径的具体含义。
代码请参考FileProvider的parsePathStrategy()方法。如果想了解如何执行到此方法的,可以参考Android ContentProvider的加载过程
parsePathStrategy()
方法的代码如下(省略了一些代码):
XML文件中的TAG和属性:
private static final String TAG_ROOT_PATH = "root-path"; private static final String TAG_FILES_PATH = "files-path"; private static final String TAG_CACHE_PATH = "cache-path"; private static final String TAG_EXTERNAL = "external-path"; private static final String TAG_EXTERNAL_FILES = "external-files-path"; private static final String TAG_EXTERNAL_CACHE = "external-cache-path"; private static final String ATTR_NAME = "name"; private static final String ATTR_PATH = "path";
XML中各个tag对应的路径,如下表:
Tag | 对应的路径 |
---|---|
root-path | 根目录/ |
files-path | /data/user/0/ 或者/data/data/ 这两个目录指向相同的位置 |
cache-path | /data/user/0/ 或者 /data/data/ |
external-path | /storage/emulated/0 或者/sdcard/ |
external-files-path | /storage/emulated/0/Android/data/ 或者 /sdcard/Android/data/ |
external-cache-path | /storage/emulated/0/Android/data/ 或者 /sdcard/Android/data/ |
parsePathStrategy() @FileProviderprivate static PathStrategy parsePathStrategy(Context context, String authority) throws IOException, XmlPullParserException { final SimplePathStrategy strat = new SimplePathStrategy(authority); final ProviderInfo info = context.getPackageManager() .resolveContentProvider(authority, PackageManager.GET_META_DATA); // META_DATA_FILE_PROVIDER_PATHS 为"android.support.FILE_PROVIDER_PATHS", 这是在AndroidManifest.xml中所使用的。 // 读取filepath_data.xml文件(本文中的例子) final XmlResourceParser in = info.loadXmlMetaData( context.getPackageManager(), META_DATA_FILE_PROVIDER_PATHS); int type; while ((type = in.next()) != END_DOCUMENT) { if (type == START_TAG) { final String tag = in.getName();// 获取属性"name"和"path" final String name = in.getAttributeValue(null, ATTR_NAME); String path = in.getAttributeValue(null, ATTR_PATH); File target = null; if (TAG_ROOT_PATH.equals(tag)) { // "root-path"标签,DEVICE_ROOT = new File("/"),系统的根目录 target = DEVICE_ROOT; } else if (TAG_FILES_PATH.equals(tag)) { // "files-path"标签,getFilesDir(),对应"/data/user/0//files"目录 target = context.getFilesDir(); } else if (TAG_CACHE_PATH.equals(tag)) { // "cache-path"标签,对应"/data/user/0//cache"目录 target = context.getCacheDir(); } else if (TAG_EXTERNAL.equals(tag)) { // "external-path"标签,对应内置sdcard目录,例如"/storage/emulated/0", 或者"/sdcard/" target = Environment.getExternalStorageDirectory(); } else if (TAG_EXTERNAL_FILES.equals(tag)) { // "external-files-path"标签,对应 "/storage/emulated/0/Android/data//files"目录 File[] externalFilesDirs = ContextCompat.getExternalFilesDirs(context, null); if (externalFilesDirs.length > 0) { target = externalFilesDirs[0]; } } else if (TAG_EXTERNAL_CACHE.equals(tag)) { // "external-cache-path"标签,对应"/storage/emulated/0/Android/data//cache"目录 File[] externalCacheDirs = ContextCompat.getExternalCacheDirs(context); if (externalCacheDirs.length > 0) { target = externalCacheDirs[0]; } } if (target != null) { // 将路径拼起来,name作为key,完整路径是value strat.addRoot(name, buildPath(target, path)); } } } return strat; }
注意:/data/user/0
是指向/data/data
目录,所以/data/user/0/
也就是/data/data/
。
执行下面的命令可以看到:
ls -ld /data/user/0lrwxrwxrwx 1 root root 10 2017-04-15 00:25 /data/user/0 -> /data/data
以filepath_data.xml这个文件为例,再看一下都配置了哪些路径:
<?xml version="1.0" encoding="utf-8"?><paths> <files-path name="my_files" path="tempfiles" /> <external-path name="my_external" path="Download"/> <cache-path name="my_cache" />paths>
files-path
对应app的/data/data/
目录,path="tempfiles"
是指子目录,拼起来的完整路径为/data/data/
。
external-path
对应的是内置的sdcard目录/sdcard/
,path="Download"
是子目录,完整目录为 /sdcard/Download
。
cache-path
对应的是/data/data/
,这个例子里没有子目录。
★ 如何使用filepath_data.xml中配置的路径?
◇ 通过uri来访问文件
以
为例。
通过Uri content:///my_files/
来访问my_files
标签对应的目录中的文件
。
例如,content://my_authority/my_files/path/to/file001.txt
对应的就是/data/data/
。
代码可以参考FileProvider的getFileForUri(),下面是部分主要代码。
public File getFileForUri(Uri uri) { String path = uri.getEncodedPath(); final int splitIndex = path.indexOf('/', 1); // 解析出tag,在此例中tag是my_files final String tag = Uri.decode(path.substring(1, splitIndex)); // path是uri中的,path可以只是文件名,也可以是带路径的文件名 path = Uri.decode(path.substring(splitIndex + 1));// 这个tag就是` `中的属性name final File root = mRoots.get(tag); // 将路径拼起来,构成实际的文件路径 File file = new File(root, path); // 略 return file; } }
◇ 获取文件对应的Uri
参考FileProvider中的getUriForFile()
注:所有出错处理的代码都忽略了。
public Uri getUriForFile(File file) { String path; try { path = file.getCanonicalPath(); } catch... // 这段代码是为了找到文件file最匹配的路径,即取匹配最长的那个root 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; } } final String rootPath = mostSpecific.getValue().getPath(); // path是以/开头的 if (rootPath.endsWith("/")) { // 如果rootPath以/开头,则将rootPath长度的内容去掉后,剩下的就是uri中使用的路径 path = path.substring(rootPath.length()); } else { // 如果rootPath不是以/开头,则需要去掉path的第一个/后,再去掉rootPath.length()的内容后,剩下的就是uri中使用的路径 path = path.substring(rootPath.length() + 1); }// mostSpecific.getKey()对应的是路径配置文件中的属性name // 最终拼起来像这样:content://// path = Uri.encode(mostSpecific.getKey()) + '/' + Uri.encode(path, "/"); return new Uri.Builder().scheme("content") .authority(mAuthority).encodedPath(path).build(); }
以
为例。
对于内置sdcard中Download目录下的文件file002.txt,其路径为/sdcard/Download/file002.txt
。对应的uri为content:///my_external/file002.txt
。
★ Android ContentProvider的加载过程
当某个app的进程要启动时,Dalvik虚拟机先fork出一个新的进程,然后将此进程的名字命名为这个app的包名,然后通过反射的方式,执行 ActivityThread 的静态的main()
方法,在main()
中创建主线程 ActivityThread,并将app中的各种组件信息附加到该进程中,即调用attach()
方法。
从这个attach()
方法开始,来描述ContentProvider的加载过程。
说明:@ActivityThread
表示代码在ActivityThread类中。
-> main() @ActivityThread ActivityThread thread = new ActivityThread(); thread.attach(false);-> attach() @ActivityThread-> mgr.attachApplication(mAppThread); @ActivityThreadmgr是IActivityManager类型的接口,是 ActivityManagerProxy 的实例,最终调用 ActivityManagerService的对应的方法。这里会切换进程到system_server进程中(ActivityManagerService所在的进程)-> attachApplication() @ActivityManagerService-> attachApplicationLocked() @ActivityManagerServiceList<ProviderInfo> providers = normalMode ? generateApplicationProvidersLocked(app) : null; thread.bindApplication(processName, appInfo, providers, ...);thread 是 IApplicationThread 类型的接口,用来向app所在进程发送消息,即调用app进程中的方法。切换进程到app进程。-> bindApplication() @ActivityThreadAppBindData data = new AppBindData(); data.providers = providers; sendMessage(H.BIND_APPLICATION, data);-> handleMessage() @ActivityThreadcase BIND_APPLICATION: handleBindApplication(data);-> handleBindApplication() @ActivityThread if (!data.restrictedBackupMode) { if (!ArrayUtils.isEmpty(data.providers)) { installContentProviders(app, data.providers); } }-> installContentProviders() @ActivityThread-> installProvider() @ActivityThreadfinal java.lang.ClassLoader cl = c.getClassLoader(); localProvider = (ContentProvider)cl.loadClass(info.name).newInstance();localProvider.attachInfo(c, info);c 是context,info是ProviderInfo对象。info.name是provider的名字。 由于FileProvider中重写了attachInfo(),所以,这里的localProvider.attachInfo()将执行FileProvider的attachInfo()。-> attachInfo() @FileProvidersuper.attachInfo(context, info); // 调用父类ContentProvider的attachInfo(),设置 ContentProvider 的各种属性,并调用Provider 的onCreate() mStrategy = getPathStrategy(context, info.authority);getPathStrategy()解析filepath_data.xml文件(在本文中的例子)。-> getPathStrategy() @FileProvider-> parsePathStrategy() @FileProviderparsePathStrategy()用来解析filepath_data.xml文件,这对理解filepath_data.xml文件很有帮助。
返回到刚才的位置: 如何更好地理解这几个路径的用法?
更多相关文章
- Android项目Android Studio目录结构
- 【Android 系统开发】 编译 Android文件系统 u-boot 内核 并烧写
- android 获取路径目录方法以及判断目录是否存在,创建目录
- 数据存储之——Android内、外存储分区&常用存储目录详解(Android
- Android gradle build 修改文件名称及目录
- 让 Android 可以识别BMP图片文件,且目前Android所支持的所有图片
- Android 5.1.1 源码目录结构
- Android中的gen文件为空或者不存在的处理方法
- Android APK 扩展文件