Android无需读写权限通过临时授权读写用户文件详解
作者:用户5944254635000 发布时间:2022-11-05 12:50:54
在进行需求开发的时候,我们总是避不开和用户的数据打交道,那提到获取用户的数据一定会想到的东西就是申请权限
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
在我刚学习安卓的时候,我是以为APP一定要声明了读写用户空间权限并且在用户授权之后才能获取到用户的文件,即使是做个简简单单的更换头像的功能,或者是升级APP时下载新的APK。对于后者,我们其实可以将升级的APK包放到我们应用的私有目录下(无需权限),对于前者,有什么比较轻量,适合快速开发需求的方法来满足呢。
这里插三段小说明,如果只想知道方法的可以直接跳过
首先我们要明白,为什么谷歌要用读写权限来限制APP对用户文件的操作权。答案其实很明显,因为需要防止APP恶意侵犯用户隐私,或者是在用户的目录里存放大量的垃圾文件,在用户目录里存放的文件是不会随着APP的卸载而被删除的,所以如果所有APP都在用户的目录里存放文件(像是相册文件夹/下载文件夹),那用户的体验别提有多糟糕了。
其次就是声明权限其实是有挺多弊端的,如果不是非必须的权限,其实谷歌是希望我们能不要就不要的。做过谷歌应用市场开发的就知道,你声明的每个权限都会在谷歌应用的详情页标注,这不仅仅是让用户一进来就觉得:"这个APP又要窥探我隐私",而且是让你在填应用的数据安全表单时更加地麻烦,因为你声明了读写权限,那你就要说明你的APP会获取用户的什么数据,如何保存,用户是否可以删除以及是否知情等等。还有就是你声明的权限越多,你的应用审核时间就会越长,这个我相信没有人觉得无所谓吧
第三就是,Android11及以上的版本其实已经大削了WRITE_EXTERNAL_STORAGE这个权限,谷歌不再允许APP悄悄地在用户的外置存储目录里偷偷拉屎了,你在用户目录里创建什么目录存取什么数据都要在用户知情并且同意的情况下才能进行,而本文要介绍的方式是能兼容到Android13的,所以赶紧学起来吧^-^
模拟获取用户的图片的逻辑
我们需要拿到代表用户临时授权给APP的Uri
通过
val intent = Intent(Intent.ACTION_GET_CONTENT)
.addCategory(Intent.CATEGORY_OPENABLE)
//这里传的参数是你要获取的文件类型的mimeType
.setType(mimeType)
startActivityForResult(intent,1024)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 1024 && resultCode == RESULT_OK) {
val uri = data?.data
//这里获取到的uri就是用户临时授权的文件/文件夹的的标识
}
}
或者
val launch = registerForActivityResult(ActivityResultContracts.GetContent()){uri->
//这里获取到的uri就是用户临时授权的文件/文件夹的的标识
}
//这里传的参数是你要获取的文件类型的mimeType
launch.launch("*/*")
启动系统的内容选择器让用户选择要分享给我们APP的文件,以获得文件的Uri
通过contentResolver打开文件的文件描述符FileDescriptor
val pfd : ParcelFileDescriptor? = context.contentResolver.openFileDescriptor(uri, "r")
第一个参数是我们刚刚得到的文件的uri,第二个文件是表示我们对文件的操作模式,我现在示范的是读取用户图片所以用只读模式("r")就可以了,关于mode的具体注释,这里我直接粘贴原文了
mode – The string representation of the file mode. Can be "r", "w", "wt", "wa", "rw" or "rwt". SeeParcelFileDescriptor.parseMode for more details.
通过FileDescriptor可以打开一个文件IO流(FIS或者FOS),就可以读写文件啦
FileInputStream(pfd.fileDescriptor).use {
//这里可以先将用户的图片复制到私有目录中,再让用户做进一步的编辑操作
}
FileOutputStream(pfd.fileDescriptor).use {
}
但是注意,打开的fileDescriptor是Closeable对象,所以用完之后需要手动close(),这里我用的是ktolin的扩展函数,会在use代码块里的代码运行完之后自动关闭流
另一种读取文件的方法,还是使用contentResolver直接打开io流
context.contentResolver.openInputStream(uri)?.use {
}
context.contentResolver.openOutputStream(uri)?.use {
}
模拟将文件写入用户目录的操作
其实思路是一模一样的,只是你启动文件系统的意图(intent)不一样,以及对文件的操作不一样
我们需要拿到代表用户临时授权给APP的Uri
//这里传入你要创建的文件类型的mimeType,如果是"*/*"那就代表文件夹
val launcher = registerForActivityResult(ActivityResultContracts.CreateDocument("*/*")){uri->
//这里获取到的uri是已经创建好的文件的uri
}
//这里传入要创建的文件名
launcher.launch("cache.png")
启动之后是这个界面
通过contentResolver打开文件的文件描述符FileDescriptor
val pfd : ParcelFileDescriptor? = context.contentResolver.openFileDescriptor(uri, "rw")
第一个参数是我们刚刚得到的文件的uri,第二个文件是表示我们对文件的操作模式,我现在示范的是保存一张图片所以要用读写模式("rw")
通过FileDescriptor可以打开一个文件IO流(FIS或者FOS),就可以写文件啦
FileOutputStream(pfd.fileDescriptor).use {
//这里将处理好的图片利用fos写到用户刚才用uri指定的地方
}
另一种读取文件的方法,还是使用contentResolver直接打开io流
context.contentResolver.openOutputStream(uri)?.use {
}
模拟获取用户文件夹控制权的操作
最后再模拟一下获取用户文件夹控制权的操作,通过这个方法你可以拿到其他应用在外置存储里的目录(例如一些聊天软件的聊天记录其实就是存放在这个目录的)
(先截了张图,过两天填坑)
通过Uri获取文件信息
最后再介绍一 通过Uri获取文件信息(文件名/文件大小/文件Mime类型)的方法
//第二个参数相当于是sql里的select,列表里是要过滤的列名,如果传null那说明取所有的列,这样性能会比较差
val cursor: Cursor? = context.contentResolver.query(
this,
arrayOf(MediaStore.MediaColumns.DISPLAY_NAME, MediaStore.MediaColumns.SIZE),
null,
null,
null
)?.use { cursor ->
if (cursor.moveToFirst()) {
val columnIndex1 = cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME)
if (columnIndex1 > -1) {
name = cursor.getString(columnIndex1)
}
val columnIndex2 = cursor.getColumnIndex(MediaStore.MediaColumns.SIZE)
if (columnIndex2 > -1) {
size = cursor.getLong(columnIndex2)
}
}
文件的话,用正常途径也只能拿到文件名(MediaStore.MediaColumns.DISPLAY_NAME),文件大小(MediaStore.MediaColumns.SIZE),文件Mime类型(MediaStore.MediaColumns.MIME_TYPE)这三个有用的信息 注意,获取到的cursor是Closeable对象,所以用完之后需要手动close()
来源:https://juejin.cn/post/7211687237019893817


猜你喜欢
- 最近 Google 已经发布 Android 新版本 7.0 Nougat (牛轧糖) ,相信Android手机用户在未来的几个月内会收到第
- 在java中用到的最多的时间类莫过于 java.util.Date了, 由于Date类中将getYear(),getMonth()等获取年、
- 在.NET4.0中,我可以借助System.Speech组件让电脑来识别我们的声音。以上,当我说"name",显示&qu
- 前言:什么是多数据源?最常见的单一应用中最多涉及到一个数据库,即是一个数据源(Datasource)。那么顾名思义,多数据源就是在一个单一应
- 一、冻结列DataGridViewColumn.Frozen属性为true时,该列左侧的所有列被固定,横向滚动时固定列不随滚动条滚动而左右移
- 实践过程效果代码public partial class Form1 : Form {
- 本文实例讲述了Java排序算法总结之希尔排序。分享给大家供大家参考。具体分析如下:前言:希尔排序(Shell Sort)是插入排序的一种。是
- SpringAOP的介绍:传送门demo介绍主要通过自定义注解,使用SpringAOP的环绕通知拦截请求,判断该方法是否有自定义注解,然后判
- 访问者(Visitor)模式:封装一些作用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。访
- 对于使用文件进行交换数据的应用来说,使用FTP 服务器是一个很不错的解决方案。关于FileZilla Server服务器的详细搭建配置过程,
- 概述线程池的好处和使用本篇文章就不赘叙了,不了解的可以参考下面两篇文章:一文全貌了解线程池的正确使用姿势学习线程池原理从手写一个线程池开始那
- 题目一??解法/** * Definition for singly-linked list. * public class ListNod
- 堆溢出:/*** @author LXA* 堆溢出*/public class Heap{ public
- 本文实例讲述了java求最大公约数与最小公倍数的方法。分享给大家供大家参考,具体如下:Gongyueshu.java文件:package m
- 一、项目简述功能javaweb 网上商城系统,前台+后台管理,用户注册,登录,上哦展示,分组展示,搜索,收货地址管理,购物车管理,添加,购买
- 在C#中,当引用类型需要转换的时候,经常会用到关键字is、as以及显式强转。本篇来体验这三者的用法。先来梳理.NET引用类型转换的"
- 1)如何获得MediaPlayer实例:可以使用直接new的方式:MediaPlayer mp = new MediaPlayer();也可
- 写在前面在这里,我们将会学习怎么利用java8 快速的打印出需要打印的元素利用stream打印元素在Java中,有三种不同的方法来打印Jav
- Android中处理图像是一件很常见的事情,这里记录备忘一些亲身使用过的处理图片数据的方法。转为BitmapRGB值转Bitmappriva
- 本文介绍了如何使用 C# 实现一个简化 Scheme——iScheme 及其解释器。如果你对下面的内容感兴趣:实现基本的词法分析,语法分析并