动态增加FileProvider的paths

2,465 阅读3分钟

动态增加FileProvider的paths

前言

最近手头上的APP遇到了个奇怪的安全整改问题,就是FileProvider在初始化的时候会访问外部SDK目录,调用栈如下: pic

好家伙,FileProvider这个官方组件初始化的锅都能叫我整改?找了下资料,看了下FileProvider路径配置的规则:

官方文档: developer.android.com/reference/a…

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- name是保存在hashMap里面的Key,不要重复,否则会被覆盖 -->
    <!-- path是相对目录,同一个TAG可以配置多个不同的目录,不创建目录可以填: "/" -->

    <!--1、对应根目录:new File("/")-->
    <root-path
        name="root"
        path="/" />
        
    <!--2、对应内部储存根目录:Context.getFileDir()-->
    <files-path
        name="filePath"
        path="/" />
        
    <!--3、对应内部存储缓存根目录:Context.getCacheDir()-->
    <cache-path
        name="cacheDir"
        path="/" />
        
    <!--4、对应外部内存卡根目录:Environment.getExternalStorageDirectory()-->
    <external-path
        name="externalStorageDir"
        path="/" />
        
    <!--5、对应外部储存中APP公共目录:Context.getExternalFilesDir()-->
    <external-files-path
        name="externalFileDir"
        path="/" />
        
    <!--6、对应外部储存的APP缓存目录:Context.getExternalCacheDir()-->
    <external-cache-path
        name="externalCacheDir"
        path="/" />
        
    <!--7、对应外部储存的media目录:Context.getExternalMediaDir()-->
    <external-media-path
        name="externalMediaDir"
        path="/" />
</paths>

原来是配置文件中external-path触发的啊!可是FileProvider在APP一打开的时候就开始初始化,是安卓系统的行为,那怎么改?

方案一: 删除

既然是置文件中external-path触发的,而且就这一个有问题,那把他删除不就行了吗?看了下代码,经过之前的整改,已经没有使用外部SD卡储存的情况了,所以可以直接删了。

但是,后面提交检测,又发现引入的第三方库里也有用到external-path,简直头疼。

方案二:动态添加FileProvider的path

上面哪个第三方库的问题困扰了我很久,后面直接解包aar把它配置文件改了,再jar -cvf打包回去,可是试了下功能,好像不对。。。

又想想安全整改不就是要用户同意隐私协议后才能获取SD卡目录吗?那我先给三方库配置文件的external-path删了,后面动态给它加上不就行了吗?于是花了一下午,看了下源码,问了问chatGPT,通过反射还真写了个有用的代码:

    private void addFileProviderPath() {
        try {
            // AndroidManifest.xml里面配置的authority
            String authority = "com.xxx.xxx.luckProvider";

            // 获取静态的sCache,里面存了各个FileProvider的PathStrategy
            Field field = FileProvider.class.getDeclaredField("sCache");
            field.setAccessible(true);
            Object cache = field.get(null);

            // 拿到FileProvider的PathStrategy
            assert cache != null;
            Method get = cache.getClass().getMethod("get", Object.class);
            Object pathStrategy = get.invoke(cache, authority);

            // pathStrategy需要添加的路径
            File rootPath = new File(new File("/"), "");
            File externalPath = new File(Environment.getExternalStorageDirectory(), "");

            // 对pathStrategy添加路径
            assert pathStrategy != null;
            Method addRoot = pathStrategy.getClass().getDeclaredMethod("addRoot", String.class, File.class);
            addRoot.setAccessible(true);
            addRoot.invoke(pathStrategy, "rootPath", rootPath);
            addRoot.invoke(pathStrategy, "externalPath", externalPath);
        } catch (Exception e) {
            Log.d("TAG", "addFileProviderPath exception: " + e.getMessage());
            e.printStackTrace();
        }
    }

嘿嘿,调试了一下,加上了,nice!同时我又想到如果对于每个第三方库,我都要去解包它的aar改文件么?那不是太low了。结合我多次整改的经验,这东西不就是在AndroidManifest.xml里面给它替换了不就行了么?说干就干:

        <provider
            android:name="com.luck.picture.lib.basic.PictureFileProvider"
            android:exported="false"
            android:authorities="com.xxx.xxx.luckProvider"
            android:grantUriPermissions="true">

            <meta-data
                tools:replace="android:resource"
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/empty_file_paths" />
        </provider>

直接analyse一下打包好的APK,看下里面的AndroidManifest.xml,复制一份出来,加到我们主工程的manifest里面去,把它的resource改了,并加上tool:replace属性,打包看一下,OK了,完事。

小结下,上面步骤有点乱了,正确流程如下:

  1. 从apk的AndroidManifest.xml里面找到要替换的provider属性
  2. 把上面拿到的属性做修改,添加到我们之际主工程的manifest里面去
  3. 在用户同意属性后,在使用provider前,动态给指定authorities的FileProvider加上路径
  4. 完成调用

方案三:通过Hook延迟初始化FileProvider

其实我最先想到的也是如何延迟初始化FileProvider,奈何技术有限,人太菜了,还好后面看到篇大佬写得文章,mark学习一下: 《Android 控制 ContentProvider的创建》