动态增加FileProvider的paths
前言
最近手头上的APP遇到了个奇怪的安全整改问题,就是FileProvider在初始化的时候会访问外部SDK目录,调用栈如下:
好家伙,FileProvider这个官方组件初始化的锅都能叫我整改?找了下资料,看了下FileProvider路径配置的规则:
<?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了,完事。
小结下,上面步骤有点乱了,正确流程如下:
- 从apk的AndroidManifest.xml里面找到要替换的provider属性
- 把上面拿到的属性做修改,添加到我们之际主工程的manifest里面去
- 在用户同意属性后,在使用provider前,动态给指定authorities的FileProvider加上路径
- 完成调用
方案三:通过Hook延迟初始化FileProvider
其实我最先想到的也是如何延迟初始化FileProvider,奈何技术有限,人太菜了,还好后面看到篇大佬写得文章,mark学习一下: 《Android 控制 ContentProvider的创建》