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项目Android Studio目录结构
  2. 【Android 系统开发】 编译 Android文件系统 u-boot 内核 并烧写
  3. android 获取路径目录方法以及判断目录是否存在,创建目录
  4. 数据存储之——Android内、外存储分区&常用存储目录详解(Android
  5. Android gradle build 修改文件名称及目录
  6. 让 Android 可以识别BMP图片文件,且目前Android所支持的所有图片
  7. Android 5.1.1 源码目录结构
  8. Android中的gen文件为空或者不存在的处理方法
  9. Android APK 扩展文件

随机推荐

  1. Android堆内存也可自己定义大小
  2. Facebook开源项目:我们为什么要用Fresco框
  3. 移动终端高级开发工程师
  4. Android(安卓)网络电台的一种实现方案
  5. 【Android(安卓)Training - 03】使用Frag
  6. ubuntu 10.10 alternate 系统SSH服务安装
  7. Android(安卓)Binder通信机制学习(二)
  8. Android(安卓)Dialog
  9. Ubuntu 12.04.3 64位 安装android sdk后
  10. kotlin Anko的实际用法