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//files/目录,path="tempfiles"是指子目录,即完整的目录为/data/data//files/tempfiles

external-path对应的是内置的sdcard目录/sdcard/path="Download"是子目录,完整目录为 /sdcard/Download

cache-path对应的是/data/data//cache/,这个例子里没有子目录。

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

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

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//files/目录,path="tempfiles"是指子目录,拼起来的完整路径为/data/data//files/tempfiles

external-path对应的是内置的sdcard目录/sdcard/path="Download"是子目录,完整目录为 /sdcard/Download

cache-path对应的是/data/data//cache/,这个例子里没有子目录。

★ 如何使用filepath_data.xml中配置的路径?

◇ 通过uri来访问文件

为例。

通过Uri content:///my_files/来访问my_files标签对应的目录中的文件

例如,content://my_authority/my_files/path/to/file001.txt对应的就是/data/data//files/path/to/file001.txt

代码可以参考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文件很有帮助。

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

更多相关文章

  1. Android当中layer-list使用
  2. 编写android jni代码时遇到的问题
  3. 解决:Failed to fectch URl https://dl-ssl.google.com/android/r
  4. Android实现九宫格
  5. 解决:Android中 Error generating final archive: Debug Certific
  6. 配置新的product(译)——android编译系统
  7. Android权限问题 及 APP应用图标
  8. ADT在线安装(http://dl-ssl.google.com/android/eclipse 打不开)
  9. Android(安卓)Studio v0.1尝鲜

随机推荐

  1. Android驱动(一)硬件访问服务学习之(一)Andro
  2. Android与JS之间跨平台异步调用实例详解
  3. Android之Intent
  4. Android应用程序的默认最大内存值
  5. 去掉android的屏幕上的title bar
  6. ionic cordova build android 打包巨慢,
  7. Android横屏竖屏切换的问题
  8. Android(安卓)使用SAX或者DOM或者pull解
  9. Android(安卓)Container原理分析
  10. 避免内存泄露