【版权申明】非商业目的可自由转载
博文地址:https://blog.csdn.net/ShuSheng0007/article/details/103125707
出自:shusheng007

相关文章
Android开发者之数据存储,你真的会存储数据吗?

文章目录

  • 概述
  • 共享文件
    • Android7.0之前
    • Android7.0之后
      • 如何实施
        • 第一步: 在`AndroidManifest.xml`中声明一个`FileProvider`
        • 第二步: 配置此`FileProvider`要映射的文件路径文件
        • 第三步:将文件路径映射为Uri
        • 第四步:对获得的Uri进行授权
        • 第五步:将此Uri 提供给使用者
      • 如何读取文件
  • 总结

概述

前段时间Facebook的“隐私门”事件闹的沸沸扬扬,可见人们对于自己的数据安全性关注度越来越高。在可预见的将来,我们的生活会越来越数字化,数据安全问题将成为未来的头号问题。我们可以发现,这两年google对Android的升级主要是在安全性上做文章。Android已经过了那个野蛮生长的年代了,便利与安全问题的权衡将是未来重点。今天我们就看一下Android在App间共享文件的进化过程。

共享文件

假设有两个App : AppProviderAppConsumer 。 AppProvider要分享自己的女朋友照片 girlfriend.jpg 给AppConsumer ,就是下面这个小姐姐。


先看一下效果图:

上图演示了使用系统安装App安装一个新的app

上图演示了一个App读取其他App的分享文件的过程

Android7.0之前

在Android7.0之前,AppProvider 需要先把这张图片分享给AppConsumer 需要做如下几步:

  1. 将图片放到文件系统的非私有目录下
  2. 将图片访问权限设置为可读
  3. 将图片的地址告诉AppConsumer
  4. AppConsumer还必须拥有外部存储读取权限

这种方式存在什么问题呢?相信你已经猜到了,存在数据安全性问题!本来AppProvider 只是想给AppConsumer分享自己的女朋友照片,但是这样一来,其他App只要知道了这个文件地址都可以查看,万一是一张"门照片",AppProvider 就废了!以前网络不发达的时候没事,现在整不好就是一个门事件啊,还是小心为妙。

那怎么办呢,Android为此专门给出了一个解决方案,我们接着往下看。

Android7.0之后

Android 7.0 为此专门提供了一个叫 FileProvider的东西,它是ContentProvider的子类。我们可以使用它将文件路径映射为匿名Uri, 然后对此Uri授于临时访问权限

当FileProvider被提出一段时间后我们就需要适配7.0了,虽然在搜索引擎的帮助下成功了,但是没有较深入的理解一下,直到第二次遇到相关问题的时候还是不太会,所以说对于一项技术只有理解了其原理才能轻松正确的使用。

我一贯认为,对于一个新的知识点,首先要可以正确的使用,然后再理解其原理,然后再回过头来看那些使用步骤就会有一种恍然大悟的感觉。那我们接下来先看一下如何通过FileProvider去安全的分享一个文件,其实只需如下简单的5步。

如何实施

第一步: 在AndroidManifest.xml中声明一个FileProvider

由于FileProvider本质上是一个ContentProvider,所以使用它的第一步自然是在AndroidManifest.xml声明一下,如下代码所示

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

上面的代码使用了androidx中的FileProvider。值得注意的是其中的 android:exported 属性必须为falseandroid:grantUriPermissions 属性必须为true 。我们先看看如果设置exported为true会发生什么呢?你会发现运行时崩溃,报错日志如下:

    java.lang.RuntimeException: Unable to get provider androidx.core.content.FileProvider: java.lang.SecurityException: Provider must not be exported...Caused by: java.lang.SecurityException: Provider must not be exportedat androidx.core.content.FileProvider.attachInfo(FileProvider.java:386)...

日志非常清楚的告诉你 Provider must not be exported,如果把grantUriPermissions设置为false效果一样,关于这个问题我们可以从源码中找到答案

FileProvider中有一个叫attachInfo()的方法,这个方法的作用是将此provider的信息提供给操作系统注册用的,其清晰的表明,这两个属性如果不满足要求就会抛SecurityException异常。

    /*** After the FileProvider is instantiated, this method is called to provide the system with* information about the provider.*/@Overridepublic void attachInfo(@NonNull Context context, @NonNull ProviderInfo info) {super.attachInfo(context, info);// Sanity check our securityif (info.exported) {throw new SecurityException("Provider must not be exported");}if (!info.grantUriPermissions) {throw new SecurityException("Provider must grant uri permissions");}mStrategy = getPathStrategy(context, info.authority);}

第二步: 配置此FileProvider要映射的文件路径文件

我们会发现声明中包含了一个<meta-data>标签。其name属性为固定值,而resource属性需要一个xm文件,这个文件就是用来做路径映射的。这个xml文件一般放在src/res/xml/路径下,可任意命名。那它长什么样呢,分别代表什么意思呢,这块也是一个难点,反正我第一 次使用的时候没有搞太清楚。那让我们一起看一下它的一个例子

    <?xml version="1.0" encoding="utf-8"?><paths><external-files-pathname="external-files"path="." /><external-cache-pathname="external-cache"path="images/" />...</paths>

<paths>标签里面可以包括多个子标签,每个子标签对应Android系统中的一个文件路径,如果对这块不了解,请先阅读Android开发者之数据存储,你真的会存储数据吗?。

例如上面的文件中有两个子标签:<external-files-path>代表context.getExternalFilesDir(null)获取到的文件路径,而<external-cache-path>代表context.externalCacheDir获取到的文件路径。

paths节点内部支持以下几个子节点,分别为:

<external-path/> 代表Environment.getExternalStorageDirectory()
<files-path/> 代表context.getFilesDir()
<cache-path/> 代表context.getCacheDir()
<external-files-path>代表context.getExternalFilesDirs()
<external-cache-path>代表getExternalCacheDirs()

每个子标签里面又有两个属性,这两个属性分别代表什么意思呢?我们知道FileProvider的原理就使将file:/// 的Uri 替换为content://的Uri,那么为了安全,我们肯定不希望产生出的Uri包含我们文件的具体路径信息吧,如果是那样的话,恶意用户就知道我们的文件的存储位置了。

我们看下面的映射关系:
file 路径:/storage/emulated/0/Android/data/top.ss007.devmemocompanion/files/myGoddess.jpg
uri 路径:content://top.ss007.devmemocompanion.fileProvider/external-files/myGoddess.jpg

通过对比可以发现具体的文件路径信息被替换了,那替换的规则是什么呢?秘密就隐藏在子标签的两个属性中:

name:我们得到的Uri格式为 content:// + 我们声明的那个FileProviderauthorities属性值 + name属性值+ 文件名称。例如我们在文件配置路径中name的值为external-files
path:这个属性值表示要映射的子路径,例如下面的子标签的意义为

<external-cache-pathname="external-cache"path="images/" />

表示可以映射的路径为 Context.externalCacheDir?.path + "/images"及其子目录。什么意思呢?如果你有一个文件不在这个目录或者子目录下,对不起,映射会失败。所以有一种粗暴的做法就是将Android系统的所有目录都配置在这个文件下,那样就不会出错了,就像下面这样,本人不是太赞成这种方式。

 <paths><external-pathname="external-path"path="."/><files-pathname="files-path"path="." /><cache-pathname="cache"path="." /><external-files-pathname="external-files"path="." /><external-cache-pathname="external-cache"path="." /></paths>

值得注意的是,从这个xml文件支持的子标签可以看出,通过FileProvider可以将存放在私有目录下的文件安全的分享给其他App。

第三步:将文件路径映射为Uri

当配置好了FileProvider后就可以着手将文件映射为Uri了,调用 FileProvider 的如下方法即可

public static Uri getUriForFile(@NonNull Context context, @NonNull String authority, @NonNull File file)

下面的代码对低版本做了兼容:

object FileUtils {fun getUriForFile(context: Context, file:File): Uri {if (Build.VERSION.SDK_INT>24){return FileProvider.getUriForFile(context,context.packageName+".fileProvider",file)}return Uri.fromFile(file)}
}

第二个参数为FileProviderandroid:authorities属性值。

第四步:对获得的Uri进行授权

由于FileProviderandroid:exported属性被声明为false ,所以必须对产生的Uri进行授权。推荐的授权方式为将此URI添加到Intent的data中,然后设置权限flag给这个Intent,如下代码所示。
这种授权方式的好处是,此授权是临时的,并且当接收App的任务栈(task stack)销毁时自动失效。代码如下所示

 val intent=Intent().apply {data = FileUtils.getUriForFile(this@MainActivity,file)flags = Intent.FLAG_GRANT_READ_URI_PERMISSION}

note:其实除了上面的授权方式,Android还提供了另一套授权方式,但是不推荐使用。

使用如下代码对特定App及Uri授权

Context.grantUriPermission(String toPackage, Uri uri, int modeFlags)

使用如下代码撤销特定App及Uri的授权

Context.revokeUriPermission(String targetPackage, Uri uri, int modeFlags)

这种方式的缺点是,只要授权者不主动撤销接收App的权限,那么这个权限就一直有效。

第五步:将此Uri 提供给使用者

有主动和被动两种方式提供Uri给接收App.

主动方式startActivity(Intent intent) 例如我们要安装一个APK到系统中,就是主动将Uri提供给系统安装App.如下面代码所示:

 private fun installApk(act: Activity,file:File) {val intent = Intent(Intent.ACTION_VIEW).apply {setDataAndType(FileUtils.getUriForFile(act, file),"application/vnd.android.package-archive")flags = Intent.FLAG_GRANT_READ_URI_PERMISSIONaddFlags(Intent.FLAG_ACTIVITY_NEW_TASK)}startActivity(intent)}

被动方式startActivityForResult(Intent intent, int requestCode) 例如我们从相册App中选择一张照片

 private fun selectImage(file: File) {val intent = Intent().apply {data = FileUtils.getUriForFile(this@MainActivity, file)flags = Intent.FLAG_GRANT_READ_URI_PERMISSION}setResult(Activity.RESULT_OK, intent)finish()}

如何读取文件

只要获取到了文件Uri,我们就可以通过ContentResolverParcelFileDescriptor openFileDescriptor(@NonNull Uri uri, @NonNull String mode)方法获得一个ParcelFileDescriptor对象,然后通过其FileDescriptor getFileDescriptor()方法获得FileDescrptor. 只要获得FileDescrptor就好说了,你可以转化为Stream保存成文件。

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {super.onActivityResult(requestCode, resultCode, data)if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_GET_FILE) {data?.data?.also { returnUri ->val input = try {contentResolver.openFileDescriptor(returnUri, "r")} catch (e: FileNotFoundException) {Log.e("MainActivity", "File not found.")return}val fd = input?.fileDescriptorivImage.setImageBitmap(BitmapFactory.decodeFileDescriptor(fd))}}}

总结

FileProvider的使用要点,首先其是一个ContentProvider所以需要在AndroidMenifest.xml文件中注册,其次需要隐藏真实文件路径,所以需要一个xml文档,最后就是授予接收App文件的临时访问权限。

通过上面的对比,FileProvider的优势已经很明显了,安全便捷。对于发送者安全,对于使用者便捷。发送者可以将任意路径下的文件分享给其他App,例如存放在私有目录下的文件。对于使用者,读取文件不需要获取相应的存储权限。

看来想把自己的女朋友的照片安全的分享给别人也不容易啊!希望广大程序员增强数据安全意识,杜绝门事件。

源码gitbub地址: AndroidDevMemo

Android开发之如何在App间安全地共享文件(FileProvider详解)?相关推荐

  1. Android开发中内存、内部存储、外部存储详解

    手机是有两个内存的.2G和16G同时出现在一个手机中,2G是指运行内存,16G是指存储内存. 手机的内存,分两种,一个是存储内存,相当于电脑的硬盘,一般手机参数里超过4G的都是指这个.存储内存是可以扩 ...

  2. 【Android开发学习笔记之一】5大布局方式详解

    Android中常用的5大布局方式有以下几种: 线性布局(LinearLayout):按照垂直或者水平方向布局的组件. 帧布局(FrameLayout):组件从屏幕左上方布局组件. 表格布局(Tabl ...

  3. Android开发笔记之:Handler Runnable与Thread的区别详解

    From:http://www.jb51.net/article/37465.htm 本篇文章是对在Android中Handler Runnable与Thread的区别进行了详细的分析介绍,需要的朋友 ...

  4. Android开发之保存图片到相册的三种方法详解

    有三种方法如下:三个方法都需要动态申请读写权限否则保存图片到相册也会失败 方法一: /**      * 保存bitmap到本地      *      * @param bitmap Bitmap ...

  5. Android开发日志打卡APP(二)

    Android开发日志打卡APP(二) 文章目录 Android开发日志打卡APP(二) 前言 开发过程 一.背景和标题 二.日志图标 三.日志弹框 前言 在之前的文章中,准备工作已经完成,现在我们将 ...

  6. Android开发日志打卡APP(一)

    Android开发日志打卡APP(一) 文章目录 Android开发日志打卡APP(一) 简介 界面展示 内容总结 1.控件 2.布局 3.技术 开发过程 准备工作 启动页面 底部导航栏 简介 ​ 初 ...

  7. android开发 实现动态获得app的cpu占有率并导出文件的两种方法。

    android开发 实现动态获得app的cpu占有率并导出文件的两种方法. 最近在做学校实验室的项目的时候,师兄要求我对app的性能进行评估,主要是从电量.cpu占有率.python模型的响应时间三者 ...

  8. (转)解决android开发人员,手机app图标显示不正确问题

    (转)解决android开发人员,手机app图标显示不正确问题 参考文章: (1)(转)解决android开发人员,手机app图标显示不正确问题 (2)https://www.cnblogs.com/ ...

  9. Android开发面试:架构设计和网络知识答案精解

    目录 架构设计 编程思想 六大设计原则 重构-Code Smell AOP 设计模式 创建型5个 行为型11个 结构型7个 编程范式 MVC MVP MVVM MVI 模块化 组件化 插件化.热修复 ...

最新文章

  1. 解决iOS机型点击输入框不能聚焦的问题
  2. 请求https错误: unable to find valid certification
  3. 【问题】HDFS中块(block)的大小为什么设置为128M?
  4. Java中的垃圾回收
  5. (教学思路 C#集合二)哈希表
  6. 【渝粤题库】国家开放大学2021春2786初级西方经济学题目
  7. 水星MW300R v2 路由器刷DD-Wrt 小记
  8. python 文件操作练习
  9. Windows 下修改 MySQL 编码为 utf8
  10. [转]恢复 git reset -hard 的误操作
  11. oracle查询:分组查询,取出每组中的第一条记录
  12. 交换机芯片技术知多少
  13. Win10系统下CUDA10.0的安装
  14. 解决跨域问题报错When allowCredentials is true, allowedOrigins cannot contain the special value “*“ since tha
  15. 奶牛戴上VR眼镜“看片”,开心了可以多产奶:俄罗斯官方做了实验,拯救奶牛的冬季忧郁...
  16. c语言 switch错误用法,C语言switch语句的详细用法
  17. 2019中国科学院、中国工程院院士增选名单正式发布
  18. 离线安装python包_补充
  19. 2008521美赛E题
  20. 分享15个英文SEO长尾关键词挖掘分析工具

热门文章

  1. 天猫商品详情接口(APP,H5端)
  2. 阴阳师 服务器维护,阴阳师6月16日服务器维护更新内容公告
  3. Selenium-新八大元素定位方法(BY)
  4. 如何选择称重显示仪表
  5. 初试 Centos7 上 Ceph 存储集群搭建
  6. android one miui,Xiaomi deletes Twitter poll after users choose Android One over MIUI
  7. nif 与 HiPE
  8. 用Excel,爬取网站数据
  9. wps开发笔记:wps端开发运行官方提供demo提示“请允许浏览器打开WPS Office”
  10. 来自程序员的圣诞浪漫纯CSS3打造的圣诞祝福【献给前端初学者】内附代码以及运行方法