前言:今天修改项目的漏洞的时候,偶然发现一个关于FileProvider的坑,及时记录一下。
其实很早之前我的应用就已经兼容到Android7.0了,此次写这个文章就是想详细梳理一下android的文件系统,以及做一下FileProvider的解析。
Android7.0 (N) 开始,将严格执行 StrictMode 模式,也就是说,将对安全做更严格的校验。而从 Android N 开始,将不允许在 App 间,使用 file:// 的方式,传递一个 File ,否者会抛出 FileUriExposedException的错误,会直接引发 Crash。
但是,既然官方对文件的分享做了一个这么强硬的修改(直接抛出异常),实际上也提供了解决方案,那就是 FileProvider,通过 content://的模式替换掉 file://,同时,需要开发者主动升级 targetSdkVersion 到 24 才会执行此策略。
FileProvider是android support v4包提供的,是ContentProvider的子类,便于将自己app的数据提供给其他app访问。
在app开发过程中需要用到FileProvider的主要有

相机拍照以及图片裁剪
调用系统应用安装器安装apk(应用升级)
具体使用的方法
1、配置AndroidManifest文件

  <providerandroid:name="android.support.v4.content.FileProvider"android:authorities="${applicationId}.provider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/provider_paths" /></provider>

authorities:一个标识,在当前系统内必须是唯一值,一般用包名。
exported:表示该 FileProvider 是否需要公开出去。
granUriPermissions:是否允许授权文件的临时访问权限。这里需要,所以是 true。

2、在res的建xml目录,放入provider_paths.xml文件

<?xml version="1.0" encoding="utf-8"?>
<paths><external-pathname="external_storage_root"path="." /><files-pathname="files-path"path="." /><cache-pathname="cache-path"path="." /><!--/storage/emulated/0/Android/data/...--><external-files-pathname="external_file_path"path="." /><!--代表app 外部存储区域根目录下的文件 Context.getExternalCacheDir目录下的目录--><external-cache-pathname="external_cache_path"path="." /><!--配置root-path。这样子可以读取到sd卡和一些应用分身的目录,否则微信分身保存的图片,就会导致 java.lang.IllegalArgumentException: Failed to find configured root that contains /storage/emulated/999/tencent/MicroMsg/WeiXin/export1544062754693.jpg,在小米6的手机上微信分身有这个crash,华为没有
--><root-pathname="root-path"path="" />
/paths>

之后FileProvider的path解析策略如下

 private static FileProvider.PathStrategy parsePathStrategy(Context context, String authority) throws IOException, XmlPullParserException {FileProvider.SimplePathStrategy strat = new FileProvider.SimplePathStrategy(authority);ProviderInfo info = context.getPackageManager().resolveContentProvider(authority, 128);XmlResourceParser in = info.loadXmlMetaData(context.getPackageManager(), "android.support.FILE_PROVIDER_PATHS");if (in == null) {throw new IllegalArgumentException("Missing android.support.FILE_PROVIDER_PATHS meta-data");} else {int type;while((type = in.next()) != 1) {if (type == 2) {String tag = in.getName();String name = in.getAttributeValue((String)null, "name");String path = in.getAttributeValue((String)null, "path");File target = null;if ("root-path".equals(tag)) {target = DEVICE_ROOT;} else if ("files-path".equals(tag)) {target = context.getFilesDir();} else if ("cache-path".equals(tag)) {target = context.getCacheDir();} else if ("external-path".equals(tag)) {target = Environment.getExternalStorageDirectory();} else {File[] externalMediaDirs;if ("external-files-path".equals(tag)) {externalMediaDirs = ContextCompat.getExternalFilesDirs(context, (String)null);if (externalMediaDirs.length > 0) {target = externalMediaDirs[0];}} else if ("external-cache-path".equals(tag)) {externalMediaDirs = ContextCompat.getExternalCacheDirs(context);if (externalMediaDirs.length > 0) {target = externalMediaDirs[0];}} else if (VERSION.SDK_INT >= 21 && "external-media-path".equals(tag)) {externalMediaDirs = context.getExternalMediaDirs();if (externalMediaDirs.length > 0) {target = externalMediaDirs[0];}}}if (target != null) {strat.addRoot(name, buildPath(target, path));}}}return strat;}}

root-path 对应DEVICE_ROOT,也就是File DEVICE_ROOT = new File("/"),即根目录,一般不需要配置。
files-path对应 content.getFileDir() 获取到的目录。
cache-path对应 content.getCacheDir() 获取到的目录
external-path对应 Environment.getExternalStorageDirectory() 指向的目录。
external-files-path对应 ContextCompat.getExternalFilesDirs() 获取到的目录。
external-cache-path对应 ContextCompat.getExternalCacheDirs() 获取到的目录

|TAG Value Path
TAG_ROOT_PATH root-path /
TAG_FILES_PATH files-path /data/data/<包名>/files
TAG_CACHE_PATH cache-path /data/data/<包名>/cache
TAG_EXTERNAL external-path /storage/emulate/0
TAG_EXTERNAL_FILES external-files-path /storage/emulate/0/Android/data/<包名>/files
TAG_EXTERNAL_CACHE external-cache-path /storage/emulate/0/Android/data/<包名>/cache
|–|--|
| | |

3、使用,以安装apk为例

        Intent intent = new Intent(Intent.ACTION_VIEW);intent.addCategory(Intent.CATEGORY_DEFAULT);Uri uri;File file = new File(saveFolder, updateSaveName);if (Build.VERSION.SDK_INT >= 24) {//android 7.0以上uri = FileProvider.getUriForFile(activity, BuildConfig.APPLICATION_ID.concat(".provider"), file);} else {uri = Uri.fromFile(file);}String type = "application/vnd.android.package-archive";intent.setDataAndType(uri, type);intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);if (Build.VERSION.SDK_INT >= 24) {intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);}activity.startActivityForResult(intent, 10);

注意点
经过大量用户使用,后期反馈,在小米6,开启微信分身之后,分身微信保存的图片,使用FileProvider将一张图片的path转成Uri的过程中crash了。这张图片路径如下

/storage/emulated/999/tencent/MicroMsg/WeiXin/mmexport1544062754693.jpg

你一定觉得很奇怪,正常路径是/storage/emulate/0,怎么会有/storage/emulate/999的路径,查找原因是应用分身导致的。之后会抛

 java.lang.IllegalArgumentException: Failed to find configured root that contains /storage/emulated/999/tencent/MicroMsg/WeiXin/mmexport1544062754693.jpg

那个时候,我的代码的xml的path里面是没有配置root-path节点的。debug时,fileProvide的mRoots是5个元素

后面我添加了root-path节点之后,mRoots变成了6个

之后就完美实现了将path转成Uri。
部分手机可以插外置sdcard,比如红米手机,之后就导致找不到sdcard的root,这时候也是需要配置root-path。
下面在聊一聊Android的文件系统
外部存储的公共目录
DIRECTORY_MUSIC:音乐类型 /storage/emulate/0/music
DIRECTORY_PICTURES:图片类型
DIRECTORY_MOVIES:电影类型
DIRECTORY_DCIM:照片类型,相机拍摄的照片视频都在这个目录(digital camera in memory) /storage/emulate/0/DCIM
DIRECTORY_DOWNLOADS:下载文件类型 /storage/emulate/0/downloads
DIRECTORY_DOCUMENTS:文档类型
DIRECTORY_RINGTONES:铃声类型
DIRECTORY_ALARMS:闹钟提示音类型
DIRECTORY_NOTIFICATIONS:通知提示音类型
DIRECTORY_PODCASTS:播客音频类型

这些可以通过Environment的getExternalStoragePublicDirectory()来获取

public static File getExternalStoragePublicDirectory(String type);

其他的就不写了,好累呀
找了两篇还不错的文章,贴一下,偷个懒

Android文件系统详解
彻底理解android中的内部存储与外部存储
Android文件系统的结构及目录用途、操作方法 整理
后面那位大兄弟,写的很详细,很不错哦!

总结:不得不说,楼主这个帖子写的很详细,致敬!!!
下面和大家分享一下我遇到的问题:
1.首先,项目由于安全性的要求,不允许将应用缓存数据存放在手机SD卡或者默认内存中,因此只能通过Context.getFilesDir()获取应用的私有目录,将数据存入应用对应的私有内存中;(关于这个问题,会在下一篇中更新,敬请期待,嘿嘿)

2.那么问题来了, Android7.0 (N) 开始,将严格执行 StrictMode 模式,也就是说,将对安全做更严格的校验。而从 Android N 开始,将不允许在 App 间,使用 file:// 的方式,传递一个 File ,否者会抛出 FileUriExposedException的错误,会直接引发 Crash。所以给出的解决方案是:
既然官方对文件的分享做了一个这么强硬的修改(直接抛出异常),实际上也提供了解决方案,那就是 FileProvider,通过 content://的模式替换掉 file://,同时,需要开发者主动升级 targetSdkVersion 到 24 才会执行此策略。
FileProvider是android support v4包提供的,是ContentProvider的子类,便于将自己app的数据提供给其他app访问。
3.因此就要使用FileProvide:在使用FileProvide需要注意几个问题:
1)根据需要存与取的位置,从而确定在res的建xml目录中放入的provider_paths.xml文件的path标签类型:
比如:我这里使用的是Context.getFilesDir()来获取应用的私有目录,因此就需要使用“files-path”标签;
2)关于provider_paths.xml的name与path的说明:其实,当我们进入到FileProvider中就能从源码中看出其解析策略,其中的“name”只是作为一个键将存储的File存入一个HashMap集合中,并不是文件的名字,而“path”则是创建文件的路径。
以上属于个人见解,如有错误,欢迎大神之处,谢谢!
附上原文地址:
Android FileProvider详细解析和踩坑指南

Android FileProvider详细解析和踩坑指南相关推荐

  1. android file域,Android FileProvider详细解析和踩坑指南

    其实很早之前我的应用就已经兼容到Android7.0了,此次写这个文章就是想详细梳理一下android的文件系统,以及做一下FileProvider的解析. Android7.0 (N) 开始,将严格 ...

  2. android手机屏幕共享神器踩坑指南

    开源项目地址:https://github.com/Genymobile/scrcpy scrcpy,由 Genymobile 推出的可跨平台的.可自定义码率的.开源的屏幕共享工具.它提供了在 USB ...

  3. Android applink 踩坑指南

    Android applink 踩坑指南 原理 接入步骤 将链接与activity关联起来 加入meta data 生成身份验证JSON 真机测试 结论 官方文档 原理 与url scheme不同的地 ...

  4. android 字体文件压缩,Android 字体使用踩坑指南

    Android 字体使用踩坑指南 最近项目改版,根据ui的设计,需要使用到三字体.在使用过程中遇到一些坑,于是有了这个避坑指南! 字体压缩 第一个坑!字体库的体积太大. 字体压缩的前提是要使用的内容是 ...

  5. ARouter踩坑指南

    文章目录 ARouter踩坑指南 导读 添加依赖和配置问题 ARouter Helper插件使用问题 Kotlin中使用注解@Autowired获取参数问题 进阶用法通过URL跳转理解 使用withO ...

  6. 内网穿透,使用 IPv6 公网访问内网设备踩坑指南

    本文是开启宽带 IPv6 功能并使用公网 IPv6 地址访问内网设备的踩坑指南.IPv6 是目前个人体验最优的内网访问方案,个人体验远胜过 ZeroTier,frp 等方案. 场景 将个人设备暴露于公 ...

  7. 转:android.support升级到androidx踩坑记录

    原文链接:android.support升级到androidx踩坑记录 - 简书 年前想着Google老大之前提醒过将项目升级到androidx,所以年前一通操作猛如虎把Android Studio唰 ...

  8. openssl开发库安装时的踩坑指南

    序 前几天用linux编译一个提权脚本的时候报错 openssl/opensslv.h: 没有那个文件或目录 的问题 无论如何也解决不了,这下我记录一个踩坑指南防止下一个人掉进坑里 操作 总体介绍 首 ...

  9. 阿里云天池【Docker练习场】踩坑指南

    阿里云天池[Docker练习场]踩坑指南 题目直达 提交环境搭建(基于macOS) Docker的安装与基本功能使用 Docker安装过程遇到的小问题 提交结果注意事项 提交时的镜像配置 项目结构规范 ...

  10. 我的域名注册踩坑指南

    我的域名注册踩坑指南 一.前言 二.目前拥有的纯字母域名 ds.mba csu.asia mpkq.org impkq.com openhm.com okotlin.com ktanjava.com ...

最新文章

  1. CALayer 了解与使用
  2. ros 消息队列与缓冲区_[ROS] [笔记(1)] 一个最简单的例子:Hello Robot(消息、发布者与订阅者)...
  3. 什么是计算机网络?—Vecloud微云
  4. 遗传算法(GA)中的编码方式-二进制编码、格雷编码、实数编码
  5. [转载]unix环境高级编程备忘:理解保存的设置用户ID,设置用户ID位,有效用户ID,实际用户ID...
  6. $_SERVER['REQUEST_URI']和$_SERVER[HTTP_X_REWRITE_URL]的区别
  7. 心情随笔——2012121
  8. redis面试题简义
  9. 分布式事务解决方案之可靠消息最终一致性
  10. C++程序练习-1008:Maya Calendar-玛雅日历
  11. 蒲公英 · JELLY技术周刊 Vol.26: 请问您这个月要来点肝么?
  12. Access时间日期比较查询的方法
  13. ORACLE的HINT详解
  14. Centos 7 怎么都连不上手机阿阿阿阿Android Studio 怎么都检测不到真机啊还有关于git本地提交就缺少文件啊啊啊啊
  15. Page Life Expectancy判断服务器运行SQLSERVER时内存是否充足
  16. 知乎周源微信_每周源代码36-PDC,BabySmash和Silverlight图表
  17. junit5_在JUnit中测试预期的异常
  18. 队列----循环队列
  19. 简述程序开发中的常用的加密方法
  20. Quest商店:利用A/B测试,竟可将VR游戏转化率提升26.5%

热门文章

  1. 荒岛求生html5小游戏在线玩,荒岛求生
  2. 文明5 java 英_文明5模组“Future Worlds”(未来世界)中英版补丁
  3. 计算机网络——域名系统
  4. 盘点百度、阿里、腾讯、华为自动驾驶战略
  5. 网页打印问题-页面显示不全
  6. 程序员根本不是稳定工作!
  7. c语言topic函数,ROS学习笔记(一) 话题topic的定义与使用(publisher 和 subscriber)
  8. 【R_绘图】绘图字体设为Times New Roman
  9. 配置authorized_keys让服务器A免密登录服务器B
  10. 椭圆曲线上两种基本的运算:点集运算、P+Q详解