解惑Android Scoped Storage

news2025/2/25 16:03:18

原文链接 Android Scoped Storage Puzzles

安卓对于文件存储这块,其实是相当混乱的,在早期的版本中对存储甚至是没有所谓的管理的,有多种方法可以操作文件存储,比如通过Java原生的方式(File/InputStream/OutputStream),以及通过ContentProvider的方式(openFile/openOutputStream)。文件的路径也是相当混乱的,有公共存储区域,App的沙盒,外部存储中的App沙盒,但其实整个文件系统中的文件都是可读的,除了系统目录,其他应用的沙盒(内部和外部)以外也都是可写的。这就造成了,安卓的文件系统特别的混乱,加之应用使用不规范,对存储造成极大的滥用,外部存储空间产生大量的垃圾文件,应用卸载后仍然存在,这也催生了一系列的清理软件。

但随着用户隐私问题越来越被重视,谷歌也开始收紧了存储权限管理。

存储权限的变迁史

强制动态申请权限

从Android 6.0(Android M, API 23)开始,存储权限除了在AndroidManifest中静态声明以外,还需要在运行时动态的申请,用户授权了后才可以使用。但这并没有本质的变化,只要用户授权了,就跟以前一样,所有文件可读,大量位置的文件也都可写,App依然可以随便乱搞。

Scoped Storage

后来到了Android 10 (Android Q, API 29)时提出了scoped storage的概念,并把权限进一步的收紧,仅留下几个公共的文件夹可写,其他地方均不可写。并且写媒体文件时(Image/Video/Audio),要用MediaStore API的方式,直接以Java的文件方式将不再可用。Android 10并未强制开启scoped storage,通过在Manifest中设置requestLegacyStorageManage=true,依然可以像以往那样搞,但到了Android 11时,就强制开启了scoped storage。

强制Scoped Storage

到了Android 11(Android R, API 30)时,就强制了,只有应用自己的沙盒可自由读写。创建媒体文件必须 通过MediaStore API。只有少数公共区域可读可写(Documents/Downloads/Pictures)等,并且这些区域只有自己创建的文件是可见和可读的。这就导致了,应用能乱搞的区间相当有限了。

细分文件种类权限

到了Android 13(Android T, API 33),更加变态了,把原来的WRITE_EXTERNAL_STORAGE/READ_EXTERNAL_STORAGE权限细分为对具体文件种类的操作,拆分为Image/Audio/Video三种媒体文件类型的读写。

注意:只有当targetSdk=33时,并且运行的设备是Android 13时,才需要做权限的细分处理。也许后面的版本就会强制了,如果强制那无论taregtSdk是多少都会强制使用细分 权限。

存储问题的适配

前面简单的介绍了安卓存储权限的变化过程,重点还是要了解一下如何适配。因为现在已经是2023年了,Android 13(Android T),API 33也快要普及了,所以就直接总结下现在应该怎么做。

动态申请存储权限

对于现在来说,动态申请权限已经是必备的技能之一了,不用多说了。就是先在应用的入口处,或者将要使用存储的地方去申请

在manifest中声明requestLegacyExternalStorage=true

用MediaStore API来操作媒体文件(Image/Audio/Video)

首先要声明具体的权限,然后用MediaStore API来操作。

申请另外一个权限MANAGE_EXTERNAL_STORAGE

注意: 这个不管你的targetSDK是多少,只要目标运行设备是Android 11,就必须 这样做。

从Android 11开始,要想看到公共区域(Documents/Downloads)中的所有文件,还必须申请另外一个权限MANAGE_EXTERNAL_STORAGE。因为理论上来说,公共区域你只能看见你自己创建的文件就够了,想看所有文件就越权了。但对于有些应用来说,如文件浏览器,或者请记录阅读器来说,肯定要能看到所有的文件。这个权限需要再次单独申请,用户授权后即可以看到公共区域的所有的文件了。

需要注意,这个权限像常规权限那样去申请是没用的,它的状态永远是denied的,所以,逻辑要这样写:

  1. 先正常申请存储相关的权限
  2. 正常权限有了后,判断Environment.isExternalStorageManager(),如果 是false则需要再次申请另外一个叫做access all files的权限,通过ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION或者ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION
       // after read/write permission granted
       if (!Environment.isExternalStorageManager()) {
            requestAllFilesAccess();
        }
        // ...
        
   private void requestAllFilesAccess() {
        Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
        intent.addCategory(Intent.CATEGORY_DEFAULT);
        Uri uri = Uri.fromParts("package", getPackageName(), null);
        intent.setData(uri);
        try {
            startActivity(intent);
        } catch (ActivityNotFoundException e) {
            Log.d(LOG_TAG, "Exception: " + e.getMessage());
        }
    }

需要注意,有可能会有Activity not found exception,注意try-catch下。

因为这个权限又会导致失控,只要申请了就可以在公共区域随意创建文件了,所以谷歌对这个权限管控也是比较严的。虽然你在研发测试阶段可以申请这个权限,但是在发布阶段,要给应用商店一个使用此权限的合理理由,否则Play store会拒绝上线的(当然,如果 不在Play Store上面发布也就无所谓了)。

合理使用公共区域

作为App,最应该做的就是从业务上区分文件的属性,然后合理的选择存储空间进行存储。对于App本身运行相关的文件,如配置,如算法的模型,如数据库,缓存和日志等等应用本身运行密切相关的文件,一定不可以放在公共区域,一定要放在应用自己的沙盒内(通过context.getFilesDir, context.getCacheDir, context.getExternalFilesDir, context.getExternalCacheDir, context.openFileOutput)等来读写,应用自己的沙盒也是属于App的自由空间,你可以随意玩。

对于用户生成的文件则要放在公共区域,比如说相机产出的图片和视频,编辑软件产生的文档,这些属于用户生成文件,它的所有权是用户的,可能会通过其他软件再次编辑或者分享,这些肯定要放在公共区域的。对于媒体类Image/Audio/Video则要使用MediaStore API的方式来创建和管理。对于其他文档类型,在获得了WRITE权限 后也可以在(Documents/Downloads)中创建和管理的,绝大多数情况下,这就够了。

总的来说,在用户隐私越来越被重视的今天,安卓现在存储管理策略在保护用户隐私的前提下是能够满足开发者使用的。

参考资料

  • Android API Levels
  • Open files using storage access framework
  • Storage updates in Android 11
  • Request runtime permissions
  • External Storage in Android with Example
  • Manage all files on a storage device
  • Android 11 Scoped storage permissions
  • How does MANAGE_EXTERNAL_STORAGE permission work in Android?

原创不易,打赏点赞在看收藏分享 总要有一个吧

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1099940.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++算法:前缀和、前缀乘积、前缀异或的原理、源码及测试用例

相关 源码测试用例下载 https://download.csdn.net/download/he_zhidan/88430716 包括4个压缩包,初始代码,实现前缀和,实现前缀积,实现前缀异或。都是在前者的基础上修改的。 本博文是CSDN学院课程的讲义 https://edu.csdn.net/c…

记录--P0事故预警

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 背景 某一天,前端小余同学和后端别问我小哥在做登录业务接口对接,出于业务的特殊性和安全性的考虑,她和后端小哥约定“user”相关信息参数需要通过HTTP协议的header…

Elastic Stack 和 Docker Compose 入门:第 2 部分

作者:Eddie Mitchell 欢迎阅读 Elastic Stack 和 Docker Compose 入门的第二部分。 在第一部分博客中,我们了解了 Docker Compose 的基础知识以及如何将单节点集群建立为本地游乐场,其中包括 Elasticsearch、Kibana、Logstash、Metricbeat 和…

JVS-rules规则引擎,解决大数据风控的自动化决策利器

规则引擎中的评分卡节点是一种用于评估客户信用、风险等级或其他指标的重要工具。它通常用于金融、信贷等领域,以便根据一系列预定义的规则和权重来对客户进行评分。以下是评分卡节点的主要功能、作用以及配置方式的介绍: 功能和作用: 评估…

BUUCTF学习(5): 命令执行Ping

1、介绍 2、解题 127.0.0.1|cat /flag 结束

第三章 交换技术及应用

3.1 port-vlan技术 3.1.1 VLAN概述 VLAN(Virtual Local Area Network),虚拟局域网VLAN是在一个已建好的物理网络上划分出来的逻辑网络。作用:隔离广播域,同一个VLAN主机可以通信而不同VLAN不能通信。 3.1.2 VLAN划分方法——Port-VLAN 基于端…

ArcGIS笔记7_如何创建新的shp要素文件?新shp的坐标系选择?

本文目录 前言Step 1 创建新的shp要素文件的操作Step 2 常用的坐标系选择Step 3 有点笨但好用的新建shp要素的方法 前言 很多ArcGIS新手都会遇到的问题,会编辑现成的shp要素文件,但不会创建新shp,而且创建时需要选择新的坐标系,这…

OpenSIPS 防扫描处理

文章目录 1. 问题背景2. 防范处理2.1 IP 封禁2.2 OpenSIPS 处理2.2.1 REGISTER 请求2.2.2 INVITE 请求 1. 问题背景 OpenSIPS 作为 SIP 注册服务器,通常需要放在公网供公司各地的员工使用,但是这样就会产生外部扫描问题。一般来说外部扫描量不会很大&am…

ElasticSearch-数据查询

ElasticSearch-数据查询 目录概述需求: 设计思路实现思路分析1.查询某索引下的所有数据2.二、条件查询3.、条件查询方式二4.四、分页查询5.五、格式化数据 六、排序七 其他条件执行的成立的查询九、范围查询 参考资料和推荐阅读 Survive by day and develop by nigh…

13-k8s-ingress网络

文章目录 一、ingress介绍二、创建nginx和tomcat供测试三、创建ingress-http四、yaml方式安装ingress五、helm方式安装ingress(推荐)六、Ingress的HTTPS代理 一、ingress介绍 Service对集群之外暴露服务的主要方式有两种:NotePort和LoadBalan…

强化学习基础(2)—常用算法总结

目录 1.Value-Based 2. Policy-Based 参考文献 1.Value-Based Sarsa(State-action-reward-state’-action):是为了建立和优化状态-动作(state-action)的价值Q表格所建立的方法。首先初始化Q表格,根据当前的状态和动作与环境进行…

Mapping 设计指南

Mapping 设计指南 目录概述需求: 设计思路实现思路分析1、properties2.fields 3.search_analyzer4.2、format1、enabled2、doc_values 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0…

数据库、数据中台、数据仓库、数据湖区别

数据时代,各行业的企业都已经开始通过数据库来沉淀数据,但是真的论起数据库、数据仓库、数据中台,还是新出现的数据湖,它们的概念和区别,可能知道的人就比较少了,今天我们详细来比较了解一下。 一、数据仓…

FPGA通过读写突发对DS1302时钟的配置驱动

文章目录 前言一、pandas是什么?1、理论原理1、DS1302实时时钟芯片1、特性2、引脚定义3、有关读写操作的详细内容1、读写的几个端口信号2、命令字节3、突发读写时钟寄存器4、写保护位 4、数据读写时序&寄存器地址表1、数据单字节读写时序2、寄存器地址表 5、数据…

✔ ★【备战实习(面经+项目+算法)】 10.16学习时间表(总计学习时间:5h)

✔ ★【备战实习(面经项目算法)】 坚持完成每天必做如何找到好工作1. 科学的学习方法(专注!效率!记忆!心流!)2. 每天认真完成必做项,踏实学习技术 认真完成每天必做&…

C++指针解读(10)-- 动态内存分配

栈和堆是进程内存空间中非常重要的2块区域。栈用于存放程序临时创建的局部变量;堆用于存放进程运行中被动态分配的内存段。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上;当利用free等函数释放内存时,被释放的…

虚拟机和Windows共享文件的几种方式(共享文件夹、网络映射、Winscp)

1、共享文件夹 在虚拟机的“/mnt/hgfs/”目录下能看到和Windows共享的文件; 2、网络映射 2.1、安装samba服务器 #安装samba服务器 apt-get install samba samba-common#添加samba账户 smbpasswd -a ckl passwd:1232.2、设置网络映射 3、Winscp共享文件 3.1、Wins…

snk-给github界面加一个有趣的动画

How to enable GitHub Actions on your Profile README for a snake-eating contribution graph 🐍 - DEV Community Platane/Platane (github.com) ① 创建New repository 名字和你自己的Github 用户名一样。 ② 创建之后,再这个Zero-coder仓库下创建…

[C国演义] 第十五章

第十五章 最长湍流子数组环绕字符串中唯⼀的⼦字符串 最长湍流子数组 力扣链接 子数组 ⇒ dp[i]的含义: 以arr[i] 结尾的所有子数组中的最长湍流子数组的长度 子数组 ⇒ 状态转移方程根据 最后一个位置来划分👇👇👇 初始化: 都初始化为…

任务调度框架-如何实现定时任务+RabbitMQ事务+手动ACK

任务调度框架 Java中如何实现定时任务? 比如: 1.每天早上6点定时执行 2.每月最后一个工作日,考勤统计 3.每个月25号信用卡还款 4.会员生日祝福 5.每隔3秒,自动提醒 10分钟的超时订单的自动取消,每隔30秒或1分钟查询…