FileProvider 路径配置策略的理解

FileProvider 路径配置策略的理解

★ FileProvider的使用

在AndroidManifest.xml中

    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//files 或者/data/data//files
这两个目录指向相同的位置
cache-path    /data/user/0//cache 或者 /data/data//cache
external-path    /storage/emulated/0或者/sdcard/
external-files-path    /storage/emulated/0/Android/data//files 或者 /sdcard/Android/data//files
external-cache-path    /storage/emulated/0/Android/data//cache 或者 /sdcard/Android/data//cache

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,完整路径是valuestrat.addRoot(name, buildPath(target, path));}}}return strat;}

注意:/data/user/0是指向/data/data目录,所以/data/user/0//files也就是/data/data//files
执行下面的命令可以看到:

        public File getFileForUri(Uri uri) {String path = uri.getEncodedPath();final int splitIndex = path.indexOf('/', 1);// 解析出tag,在此例中tag是my_filesfinal String tag = Uri.decode(path.substring(1, splitIndex));// path是uri中的,path可以只是文件名,也可以是带路径的文件名path = Uri.decode(path.substring(splitIndex + 1));// 这个tag就是``中的属性namefinal 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最匹配的路径,即取匹配最长的那个rootMap.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;}}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() @ActivityThreadActivityThread thread = new ActivityThread();thread.attach(false);
-> attach() @ActivityThread
-> mgr.attachApplication(mAppThread); @ActivityThreadmgr是IActivityManager类型的接口,是 ActivityManagerProxy 的实例,最终调用 ActivityManagerService的对应的方法。这里会切换进程到system_server进程中(ActivityManagerService所在的进程)-> attachApplication() @ActivityManagerService
-> attachApplicationLocked() @ActivityManagerServiceList 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() @ActivityThreadif (!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文件很有帮助。

 返回到刚才的位置: 如何更好地理解这几个路径的用法?

 

 

 

 

 

 

 


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部