使用场景
背景
有部分同学只要是上传或者下载,只要用到了文件,不管三七二十一写个
FileProvider
再说。 不是每一种情况都需要使用 FileProvider 的。啥?你问行不行?有没有毛病? 这…写了确实可以,没毛病!但是这没有必要啊。 如果不需要FileProvider
就不需要定义啊,如果定义了重复的FileProvider
,还会导致清单文件合并失败,需要处理冲突,从而引出又一个问题,解决FileProvider
的冲突问题,当然这不是本文的重点,网上也有解决方案。
场景
这里我们只使用FileProvider
来说,分析一下如下场景:
- 比如我们下载文件到SD卡,当然我们一般都下载到download目录下,那么使用这个文件,需要
FileProvider
吗?
答:不需要!因为他是 共享文件夹Q中,并不是在沙盒中。 - 那我们把文件保存到沙盒中,比如
getExternalFilesDir
。那么我们使用这个沙盒中的文件,需要FileProvider
吗?
答:看情况。如果只是把此文件上传到服务器,上传到云平台,也就是我们自己App使用自己的沙盒,是不需要FileProvider
的 - 如果是想使用系统打开文件,或者传递给第三方App,那么是需要
FileProvider
的。
也就是说一般使用场景,我们只有在自己App沙盒中的文件,需要给别的App操作的时候,我们才需要使用 FileProvider。
比较典型的例子是,下载Apk到自己的沙盒文件中,然后调用Android的Apk安装器去安装应用(这是一个单独的App),我们就需要FileProvider
。或者我们沙盒中的图片,需要发送到第三方的App里面展示,我们需要FileProvider
。
FileProvider为什么被设计出来
- 为了提高私有目录的安全性,防止应用信息的泄漏,从 Android 7.0 开始,应用私有目录的访问权限被做限制。
- 具体表现为,开发人员不能够再简单地通过
file:// URI
访问其他应用的私有目录文件或者让其他应用访问自己的私有目录文件。 - 同时,也是从 7.0 开始,Android SDK 中的 StrictMode 策略禁止开发人员在应用外部公开
file:// URI
。具体表现为,当我们在应用中使用包含file:// URI
的 Intent 离开自己的应用时,程序会发生故障。 - 开发中,如果我们在使用
file:// URI
时忽视了这两条规定,将导致用户在 7.0 及更高版本系统的设备中使用到相关功能时,出现FileUriExposedException
异常,导致应用出现崩溃闪退问题。而这两个过程的替代解决方案便是使用FileProvider
。
文件目录
设备根目录
api:new File("/")
路径:/
外部sdk根目录
api:Environment.getExternalStorageDirectory()
路径:/storage/emulate/0
外部公共目录
api:Environment.getExternalStoragePublicDirectory(type);
路径:/storage/emulate/0/....
有九种type,分别对应不同的目录,如下:
DIRECTORY_MUSIC:音乐类型 /storage/emulate/0/music
DIRECTORY_PICTURES:图片类型
DIRECTORY_MOVIES:电影类型
DIRECTORY_DCIM:照片类型,相机拍摄的照片视频都在这个目录(digital camera in memory)
DIRECTORY_DOWNLOADS:下载文件类型 /storage/emulate/0/downloads
DIRECTORY_DOCUMENTS:文档类型
DIRECTORY_RINGTONES:铃声类型
DIRECTORY_ALARMS:闹钟提示音类型
DIRECTORY_NOTIFICATIONS:通知提示音类型
私有目录
随着用户删除app而删除
api:context.getCacheDir();
路径:/data/data/包名/cache
api:context.getFilesDir();
路径:/data/data/包名/files
注意:Android7.0以上区分用户目录是这个: /data/user/0/包名/cache、/data/user/0/包名/files,这个不用太在意它,其实指向的还是data/data目录。
外部私有目录
随着用户删除app而删除
api:context.getExternalFilesDir(type);
路径:/storage/emulated/0/Android/data/包名/files
api:context.getExternalCacheDir();
路径:/storage/emulated/0/Android/data/包名/cache
FileProvider使用
- 在AndroidManifest注册
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
//允许它授予 Uri 临时的权限
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
- 在res/xml 目录下添加共享目录标识文件file_path
<?xml version="1.0" encoding="utf-8"?>
<paths>
<!--
1、设备根目录
api: new File("/")
package: /
-->
<root-path
name="root"
path="" />
<!--
2、外部sdk根目录
api: Environment.getExternalStorageDirectory()
package: /storage/emulate/0
-->
<external-path
name="external"
path="." />
<!--3、内部私有目录:随着用户删除app而删除-->
<!--
3.1:
api: context.getFilesDir()
package: /data/data/包名/files
-->
<files-path
name="files"
path="." />
<!--
3.2:
api: context.getCacheDir()
package: /data/data/包名/cache
-->
<cache-path
name="cache"
path="." />
<!--4、外部私有目录:随着用户删除app而删除-->
<!--
4.1:
api: ContextCompat.getExternalFilesDirs()
package: /storage/emulated/0/Android/data/包名/files
-->
<external-files-path
name="external_file_path"
path="." />
<!--
4.2:
api: ContextCompat.getExternalCacheDirs()
package: /storage/emulated/0/Android/data/包名/cache
-->
<external-cache-path
name="external_cache_path"
path="." />
</paths>
配置
path="."
表示该路径下所有的文件夹都有权限访问。也可以指定该路径下某个文件夹有权限访问例如:
<external-files-path name="external_file_path" path="test" />
路径和Uri匹配规则
匹配规则是按照配置的@xml/file_paths
一层层往上找的。
比如我现在有一个图片在这个目录下
storage/emulated/O/Android/data/com.florizt.demo/files/imgs/test1.jpg
我配置了如下path
1、<root-path name="root" path="" />
2、<external-path name="external" path="." />
3、<external-files-path name="external_file_path" path="." />
4、<external-files-path name="test_img" path="imgs" />
通过 FileProvider 获取Uri 也是分优先顺序的。
那么打印如下:
Uri: content://com.florizt.demo.fileprovider/test_img/test1.jpg
如果把4去掉,
那么打印如下:
Uri: content://com.florizt.demo.fileprovider/external_file_path/imgs/test1.jpg
再把3也去掉,
那么打印如下:
Uri: content://com.florizt.demo.fileprovider/external/Android/data/com.florizt.demo/files/imgs/test1.jpg
再把2也去掉,
那么打印如下:
Uri: content://com.florizt.demo.fileprovider/root/storage/emulate/0/Android/data/com.florizt.demo/files/imgs/test1.jpg
再把1也去掉呢?就崩溃报错,找不到了。
最后
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。