FileProvider 路径配置策略的理解

news/2025/2/22 14:31:47

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

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

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

parsePathStrategy() @FileProvider

	private 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/<package_name>/files"目录
                    target = context.getFilesDir();
                } else if (TAG_CACHE_PATH.equals(tag)) {
                	// "cache-path"标签,对应"/data/user/0/<package_name>/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/<package_name>/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/<package_name>/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/<package_name>/files也就是/data/data/<package_name>/files
执行下面的命令可以看到:

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

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

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

★ 如何使用filepath_data.xml中配置的路径?
◇ 通过uri来访问文件
以<files-path name="my_files" path="tempfiles" />为例。

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

例如,content://my_authority/my_files/path/to/file001.txt对应的就是/data/data/<package_name>/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中的<file_name>,path可以只是文件名,也可以是带路径的文件名
            path = Uri.decode(path.substring(splitIndex + 1));
			// 这个tag就是`<files-path name="my_files" path="tempfiles" />`中的属性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://<authority>/<name>/<file_path>
            path = Uri.encode(mostSpecific.getKey()) + '/' + Uri.encode(path, "/");
            return new Uri.Builder().scheme("content")
                    .authority(mAuthority).encodedPath(path).build();
        }

以<external-path name="my_external" path="Download"/>为例。

对于内置sdcard中Download目录下的文件file002.txt,其路径为/sdcard/Download/file002.txt。对应的uri为content://<authority>/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); @ActivityThread
	mgr是IActivityManager类型的接口,是 ActivityManagerProxy 的实例,最终调用 ActivityManagerService的对应的方法。
	这里会切换进程到system_server进程中(ActivityManagerService所在的进程)

-> attachApplication() @ActivityManagerService
-> attachApplicationLocked() @ActivityManagerService
	List<ProviderInfo> providers = normalMode ? generateApplicationProvidersLocked(app) : null;
    thread.bindApplication(processName, appInfo, providers, ...);
	thread 是 IApplicationThread 类型的接口,用来向app所在进程发送消息,即调用app进程中的方法。切换进程到app进程。

-> bindApplication() @ActivityThread
	AppBindData data = new AppBindData();
    data.providers = providers;
    sendMessage(H.BIND_APPLICATION, data);
-> handleMessage() @ActivityThread
	case BIND_APPLICATION:
    	handleBindApplication(data);
-> handleBindApplication() @ActivityThread
        if (!data.restrictedBackupMode) {
            if (!ArrayUtils.isEmpty(data.providers)) {
                installContentProviders(app, data.providers);
            }
        }
-> installContentProviders() @ActivityThread
-> installProvider() @ActivityThread
		final 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() @FileProvider
		super.attachInfo(context, info); // 调用父类ContentProvider的attachInfo(),设置 ContentProvider 的各种属性,并调用Provider 的onCreate()
        mStrategy = getPathStrategy(context, info.authority);
		getPathStrategy()解析filepath_data.xml文件(在本文中的例子)。

-> getPathStrategy() @FileProvider
-> parsePathStrategy() @FileProvider
	parsePathStrategy()用来解析filepath_data.xml文件,这对理解filepath_data.xml文件很有帮助。

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

 

 

 

 

 

 

 


http://www.niftyadmin.cn/n/4479905.html

相关文章

适配Android7.0调取相机拍照并返回照片

Android调取系统相机拍照获取到拍摄照片或从相册中直接选取照片后展示上传是Android开发中很常见的一个功能&#xff0c;实现的思路主要是&#xff1a; 自Android 6.0以后对某些涉及用户隐私权限的获取需要动态获取&#xff0c;所以首先是检查权限&#xff0c;如没有权限则动态…

解释CPU到底如何执行程序及取译码执行(Fetch-Decode-Execute Cycle)

现代的生活中电脑无处不在&#xff0c;我们都知道电脑的核心部件就是CPU&#xff0c;那CPU是如何工作的&#xff0c;是如何执行程序的呢&#xff1f; 大多数现代处理器的工作原理是取译码执行(Fetch-Decode-Execute Cycle)。 也被称为冯诺依曼架构(Von Neumann Architecture)。…

Android中用到的MVP模式

参考&#xff1a;android架构设计—mvp模式封装 MVP模式是由MVC模式逐渐演化出来的。首先简单介绍一下MVC。这个在Spring框架里面是一个很常见的模式。 MVC M&#xff08;model&#xff09;模型, 是应用程序中用于处理应用数据逻辑的部分&#xff0c;通常模型对象负责在数据库…

C语言之#define

文章目录一.什么是#define二.#define的一般形式三.#define如何工作&#xff1f;四.终止宏 #undef五.注意六.define与const区别一.什么是#define C语言中&#xff0c;可以用 #define 定义一个标识符来表示一个常量。 特点是&#xff1a;定义的标识符不占内存&#xff0c;只是一…

C语言基本数据类型int, short int, long int, long long int, unsigned int, signed int等解析

一. 普通int类型 int类型是有符号整型&#xff0c;即int类型的值必须是整数&#xff0c;可以是正整数&#xff0c;负整数&#xff0c;零。 int类型取值范围因计算机系统而异。早起的16位IBM PC兼容机使用16位来存储一个int值&#xff0c;其取值范围是-32769 &#xff5e;32768…

C语言 getchar()原理及易错点解析

文章目录一.getchar()系列1.getchar()工作原理及作用2.使用getchar()清理回车\n3.使用getchar()清理缓存4.混合scanf()与getchar()一.getchar()系列 1.getchar()工作原理及作用 工作原理&#xff1a;getchar()是stdio.h中的库函数&#xff0c;它的作用是从stdin流中读入一个字…

C语言之 指针与多维数组最强解析

假设有以下声明&#xff1a; int multiArray [4] [2] //声明一个int类型的二维数组数组名multiArray是该数组首元素( multiArray[0] )的地址。 在本例中&#xff0c;multiArray的首元素是一个内含两个int值的数组&#xff0c;所以multiArray是这个内含两个int值的数组的地…

C语言字符串的输出与输入学习笔记

文章目录字符串的输入与输入1.字符串初始化1⃣️&#xff1a;用足够的空间的数组存储字符串&#xff1a;2⃣️&#xff1a;省略数组初始化声明中的大小2.数组与指针1⃣️&#xff1a;指针创建字符串2⃣️&#xff1a;数组与指针的区别3⃣️&#xff1a;使用指针的优缺点3.scanf…