其实很早之前我的应用就已经兼容到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文件

android:name="android.support.v4.content.FileProvider"

android:authorities="${applicationId}.provider"

android:exported="false"

android:grantUriPermissions="true">

android:name="android.support.FILE_PROVIDER_PATHS"

android:resource="@xml/provider_paths" />

authorities:一个标识,在当前系统内必须是唯一值,一般用包名。

exported:表示该 FileProvider 是否需要公开出去。

granUriPermissions:是否允许授权文件的临时访问权限。这里需要,所以是 true。

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

name="external_storage_root"

path="." />

name="files-path"

path="." />

name="cache-path"

path="." />

name="external_file_path"

path="." />

name="external_cache_path"

path="." />

name="root-path"

path="" />

/paths>

这个配置的标签参照FileProvider里面的TAG配置。

之后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文件系统的结构及目录用途、操作方法 整理

后面那位大兄弟,写的很详细,很不错哦!

致敬前辈,砥砺前行!

android file域,Android FileProvider详细解析和踩坑指南相关推荐

  1. Android开发中的WMS详细解析

    /   今日科技快讯   / 近日,小冰公司宣布对旗下人工智能数字员工产品线启动年度升级.本次升级加强的技术包括大模型对话引擎.3D神经网络渲染.超级自然语音及AIGC人工智能内容生成.小冰公司计划将 ...

  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. uniapp 引入阿里矢量图标库的详细步骤及踩坑经历

    uniapp 引入阿里矢量图标库的详细步骤及踩坑经历 首先在阿里矢量图标库选择自己喜欢的图标 加购物车如下图 点击右上角的购物车 添加到项目 这里有坑 首先你不选中在线链接 你是看不到 @font-f ...

  6. 金九银十面试整理:BAT大厂最爱问的Android核心面试百题详细解析!

    Android是一种基于Linux的自由及开放源代码的操作系统,主要使用于移动设备,如智能手机和平板电脑,由Google公司和开放手机联盟领导及开发.这里会不断收集和更新Android基础相关的面试题 ...

  7. android gradle权威指南pdf_干货 | 携程 Android 10适配踩坑指南

    作者简介 曙光,携程资深软件工程师,负责市场营销相关研发及管理工作.2019 年 9 月 3 日,Google 发布了 Android 10 正式版.Android 10 聚焦移动创新.安全隐私和数字 ...

  8. MMDetectionV2 + Colab 超详细教程及踩坑实录

    文章目录 前言 一.环境配置 二.准备自己的数据集 Aug.14更新 三:修改config文件 3.1 文件结构 3.2 (本地)修改config文件 3.2.1 (本地)构造自己模型的权重文件 3. ...

  9. boot spring 解析csv_spring-boot-starter-thymeleaf 避坑指南

    spring-boot-starter-thymeleaf 避坑指南 spring-boot-starter-thymeleaf 避坑指南 第一步:pom配置环境 先不要管包是做什么的 总之必须要有 ...

最新文章

  1. centos6.5离线安装gcc gcc++ rpm
  2. PHP magic_quotes_gpc
  3. C1 FlexGrid 行Style设置问题
  4. go语言初体验(流程控制、range遍历、函数、结构体、面向对象)
  5. 如何修改SAP登录界面的文字
  6. 路遥工具箱全面迁移至 .NET 6.0 并发布 3.0 版本及迁移记录详解
  7. 虚线 实现_redis跳跃表实现
  8. 一份完整的问卷模板_如何写出一份优秀的个人简历?
  9. 吴恩达|机器学习作业目录
  10. TensorBoard:图形可视化
  11. 麦当劳降价“过冬” 一夜回到十年前(转)
  12. [POJ1952]BUY LOW, BUY LOWER
  13. html中img显示旋转,css如何实现图片的旋转展示效果(代码示例)
  14. php获取客户端IP
  15. python浪漫代码表白npy_【交大表白墙】表白dxy小姐姐,十里春风不如你,三里桃花不及卿,要每天开心哦!...
  16. 你应该问面试官的10个相关问题
  17. Math.cbrt() Math.sqrt() Math.pow()
  18. 《鬼谷子》飞箝第五(译文)
  19. 关于length与length()
  20. 微信‘小程序’: web前端的春天 or 噩梦?

热门文章

  1. ADC的一些基本概念
  2. 各有千秋,iFunk 旗下产品特点一览
  3. 大三数据库导论期末课程设计
  4. php影视源码支持西瓜视频,Ctcms关于增加西瓜影音以及其他P2P播放器的方法
  5. 广州市新一轮城市轨道交通建设规划方案
  6. 易错词语,你读对了吗?
  7. 软件项目管理系统-项目管理-模块统计-工作量统计
  8. 论文总结:Estimating or Propagating Gradients Through Stochastic Neurons for Conditional Computation
  9. SuperMap iObject常见问题解答集锦(二)
  10. 如何增加C盘空间而不重启电脑 - 分区助手