先说结论:如果有两个或多个 FileProvider 的 authorities 重名,那么只有合并后的 AndroidManifest.xml 文件里,排在最前面的那个配置会生效。
一、场景
应用里有个自升级的功能,下载完 apk 后,通过 FileProvider 提供 Uri 进行安装。我修改了文件下载路径后,功能失效了,报错如下:
对应的 provider 的声明是:
provider_paths 内容:
二、分析
对照 FileProvider 官方文档:https://developer.android.com/reference/android/support/v4/content/FileProvider.html ,我再三确认了配置本身没有问题。
然后在报错堆栈的 android.support.v4.content.FileProvider$SimplePathStrategy.getUriForFile 方法处下断点调试:
发现 SimplePathStrategy 的 mRoots 里确实没有我配置的路径。而 SimplePathStrategy 唯一的构造方法的参数是 authority,该实例的 authority 确实是 ${applicationId}.provider 无误……那么,合理猜测,是有同名的 FileProvider,这里用到的是另一个 FileProvider 的 mRoots。
为了验证该猜测,我从两方面做确认:
- 查看合并后的 AndroidManifest.xml 文件,是否有其它 FileProvider 的 authorities 也是 ${applicationId}.provider?
- 阅读 Android Frameworks 里的相关源码,确认解析 provider 配置、取 FileProvider 实例的逻辑。
1.查看合并后的 AndroidManifest.xml
现在 Android Studio 已经提供了非常方便的查看合并后的 AndroidManifest.xml 的功能,打开 app 项目的 AndroidMenifest.xml 文件,在编辑器底部有个 Merged Manifest 选项卡,点击即可查看。
可以看到,确实有两个 FileProvider 的 authorities 都是 ${applicationId}.provider,另一个是从一个第三方库里来的,并且,它排在前面。
2.源码确认
首先是在 Android Studio 里进行,找到调用 SimplePathStrategy 构造方法的地方,是在 android.support.v4.content.FileProvider#parsePathStrategy:
这里的 context.getPackageManager().resolveContentProvider 的实现,一路通过以下路径找到:
到这里动用一点历史经验,可知实际实现类是 PackageManagerService,来看看 PackageManagerService#resolveContentProvider 的实现:
在 PackageManagerService 里继续查找写入 mProvidersByAuthority 的地方,在 PackageManagerService#commitPackageSettings:
从上面这段中我们可以得到两个知识点:
- 如果已经有同名的 authority,那么后面的 Provider 配置会被忽略掉;
- authority 可以配置多个,用分号分隔。(这一点在官方文档之类的都没有找到说明,也许官方觉得配置项的名称 autorities 就说明了一切?实测可正常使用。)
接下来还有一点需要确认的,就是 pkg.providers 是否是按 AndroidManifexs.xml 里的顺序排列的。
根据上面代码里的线索,可以留意到 PackageParser 类,按如下顺序递进:
至此,我们已经可以确定 pkg.providers 是按 AndroidManifest.xml 里的顺序解析出来的了。
三、解决方案
既然已经知道了问题的原因,那么解决方案也就呼之欲出了:
修改自己的 FileProvider 的 authorities,不会和其它库的 authorities 重名即可。
四、小结
源码面前,了无秘密。——侯捷
如果遇到疑难问题,而恰好又有源码可查,那么就不要犹豫,直接去看源码吧!花一些时间和耐心,最终会找到你想要的。