Android Scoped Storage适配完全指南

关键词:Android、Scoped Storage、适配、存储权限、文件访问

摘要:本文将全面介绍Android Scoped Storage的相关知识,从背景出发,详细解释核心概念,阐述其原理和架构,给出具体的算法实现步骤,结合项目实战案例进行代码解读,分析实际应用场景,推荐相关工具和资源,探讨未来发展趋势与挑战。旨在帮助开发者全面掌握Android Scoped Storage的适配方法,顺利完成应用的升级改造。

背景介绍

目的和范围

在Android系统不断发展的过程中,为了更好地保护用户的隐私和数据安全,Google推出了Scoped Storage(分区存储)机制。本指南的目的就是帮助开发者了解Scoped Storage的相关概念、原理和适配方法,使开发者能够将应用适配到支持Scoped Storage的Android系统上。本指南的范围涵盖了Scoped Storage的核心概念、算法原理、代码实现、实际应用场景等方面。

预期读者

本指南主要面向Android开发者,尤其是那些需要将应用适配到Android 10及以上系统的开发者。对于初学者来说,本指南可以帮助他们快速了解Scoped Storage的基本知识;对于有一定经验的开发者来说,本指南可以提供详细的技术细节和实战案例,帮助他们更好地完成适配工作。

文档结构概述

本指南将按照以下结构进行组织:首先介绍核心概念,通过故事引入和通俗易懂的解释让读者了解Scoped Storage的基本概念;然后阐述核心概念之间的关系,并给出原理和架构的文本示意图及Mermaid流程图;接着讲解核心算法原理和具体操作步骤,结合Python代码进行详细阐述;再给出数学模型和公式,并举例说明;之后通过项目实战案例,介绍开发环境搭建、源代码实现和代码解读;分析实际应用场景,推荐相关工具和资源;探讨未来发展趋势与挑战;最后进行总结,提出思考题,并提供常见问题与解答和扩展阅读参考资料。

术语表

核心术语定义
  • Scoped Storage(分区存储):Android 10及以上系统引入的一种存储机制,它限制了应用对外部存储的访问权限,使应用只能访问自己的专属目录和特定的公共目录。
  • 外部存储(External Storage):指的是设备上可用于存储文件的非系统分区,例如SD卡或内置的大容量存储区域。
  • 应用专属目录(App-Specific Directory):每个应用在外部存储中都有自己的专属目录,应用可以自由读写该目录下的文件,而不需要额外的权限。
相关概念解释
  • 存储权限:在Android系统中,应用访问外部存储需要相应的权限。在Scoped Storage之前,应用可以通过请求READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE权限来访问整个外部存储;而在Scoped Storage中,权限管理更加精细。
  • 媒体文件(Media Files):如图片、音频、视频等文件,在Scoped Storage中有专门的访问方式和管理机制。
缩略词列表
  • SAF(Storage Access Framework):存储访问框架,用于在Scoped Storage中让用户选择文件或目录。

核心概念与联系

故事引入

想象一下,有一个大型的图书馆,里面存放着各种各样的书籍。以前,图书馆的管理比较宽松,每个读者都可以自由地在图书馆的各个角落寻找和借阅书籍。但是,随着图书馆的规模越来越大,书籍数量越来越多,这种管理方式出现了一些问题,比如有些读者会随意乱放书籍,导致其他读者找不到自己需要的书。于是,图书馆决定采用一种新的管理方式,将图书馆划分为不同的区域,每个读者只能在自己的专属区域内自由活动,而对于公共区域的书籍,需要通过特定的流程才能借阅。Android Scoped Storage就像是这个新的图书馆管理方式,它对应用访问外部存储的权限进行了限制,让数据管理更加有序和安全。

核心概念解释(像给小学生讲故事一样)

** 核心概念一:Scoped Storage(分区存储)**
Scoped Storage就像是一个小区,每个应用都有自己的小房子(应用专属目录),应用可以在自己的小房子里自由地放东西和拿东西,不需要经过别人的同意。而对于小区里的公共区域(公共目录),应用不能随便进去拿东西,需要经过一定的程序才能访问。

** 核心概念二:应用专属目录**
应用专属目录就像是每个应用的小秘密基地。在这个基地里,应用可以放心地存储自己的重要文件,比如游戏应用可以把玩家的游戏记录存放在这里,音乐应用可以把下载的音乐文件放在这里。其他应用是不能随便进入这个秘密基地的,只有这个应用自己可以自由进出。

** 核心概念三:SAF(Storage Access Framework)**
SAF就像是一个小秘书,当应用需要访问公共区域的文件时,它可以帮助应用和用户进行沟通。比如,当一个图片编辑应用需要用户选择一张图片进行编辑时,SAF就会弹出一个窗口,让用户在公共的图片库中选择一张图片,然后把这张图片的信息告诉给应用。

核心概念之间的关系(用小学生能理解的比喻)

Scoped Storage、应用专属目录和SAF就像一个团队,Scoped Storage是队长,它制定了整个团队的规则;应用专属目录是队员的私人领地,队员可以在自己的领地自由活动;SAF是团队的沟通使者,负责和外界进行沟通。
** 概念一和概念二的关系:**
Scoped Storage规定了应用专属目录的存在和使用规则。就像队长给每个队员分配了一个私人房间,队员可以在房间里自由地做自己想做的事情,但是不能超出队长规定的范围。
** 概念二和概念三的关系:**
当应用专属目录里的资源不够时,应用就需要通过SAF去公共区域获取资源。就像队员在自己的房间里找不到需要的东西时,就需要通过沟通使者去公共区域寻找。
** 概念一和概念三的关系:**
Scoped Storage通过SAF来实现对公共区域文件的访问控制。队长通过沟通使者来管理队员和外界的交流,确保队员在和外界交流时遵守团队的规则。

核心概念原理和架构的文本示意图(专业定义)

Scoped Storage的核心原理是通过权限管理和目录划分,限制应用对外部存储的访问。应用只能访问自己的专属目录,对于公共目录的访问需要通过SAF或特定的API。其架构主要包括应用层、系统层和存储层。应用层通过调用系统提供的API来访问存储层的文件;系统层负责权限管理和文件访问控制;存储层则是实际存储文件的地方,包括应用专属目录和公共目录。

Mermaid 流程图

访问专属目录
通过SAF访问
权限管理
文件访问控制
文件访问控制
应用
应用专属目录
公共目录
系统层
存储层

核心算法原理 & 具体操作步骤

访问应用专属目录

在Android中,应用可以通过以下代码访问自己的专属目录:

// 获取应用专属目录
File appSpecificDir = getExternalFilesDir(null);
if (appSpecificDir != null) {// 在专属目录下创建一个新文件File newFile = new File(appSpecificDir, "test.txt");try {// 写入数据FileOutputStream fos = new FileOutputStream(newFile);fos.write("Hello, Scoped Storage!".getBytes());fos.close();} catch (IOException e) {e.printStackTrace();}
}

通过SAF访问公共目录

以下是一个通过SAF选择图片文件的示例代码:

// 创建一个Intent,用于启动SAF
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("image/*");
// 启动SAF
startActivityForResult(intent, PICK_IMAGE_REQUEST_CODE);// 在onActivityResult方法中处理选择结果
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);if (requestCode == PICK_IMAGE_REQUEST_CODE && resultCode == RESULT_OK) {if (data != null) {Uri uri = data.getData();try {// 通过Uri获取文件内容InputStream inputStream = getContentResolver().openInputStream(uri);// 处理文件内容} catch (FileNotFoundException e) {e.printStackTrace();}}}
}

数学模型和公式 & 详细讲解 & 举例说明

在Scoped Storage的适配中,并没有严格意义上的数学模型和公式。但是,我们可以用一些简单的逻辑来描述应用对文件的访问规则。
假设 AAA 表示应用,DappD_{app}Dapp 表示应用专属目录,DpublicD_{public}Dpublic 表示公共目录,PPP 表示权限。则应用对文件的访问规则可以表示为:
A→PselfDapp A \xrightarrow{P_{self}} D_{app} APselfDapp
A→PsafDpublic A \xrightarrow{P_{saf}} D_{public} APsafDpublic
其中,PselfP_{self}Pself 表示应用访问自己专属目录的权限,这个权限是默认拥有的;PsafP_{saf}Psaf 表示应用通过SAF访问公共目录的权限,需要用户授权。

举例说明:一个图片编辑应用,它可以自由地在自己的专属目录中存储和读取用户编辑后的图片文件,因为它拥有访问自己专属目录的权限;而当它需要从公共的图片库中选择一张图片进行编辑时,就需要通过SAF,并且获得用户的授权。

项目实战:代码实际案例和详细解释说明

开发环境搭建

  1. 打开Android Studio,创建一个新的Android项目。
  2. 在项目的 build.gradle 文件中,将 targetSdkVersion 设置为29或更高版本,以启用Scoped Storage。
android {compileSdkVersion 31buildToolsVersion "31.0.0"defaultConfig {applicationId "com.example.scopedstorageapp"minSdkVersion 21targetSdkVersion 31versionCode 1versionName "1.0"testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"}...
}

源代码详细实现和代码解读

以下是一个完整的示例代码,包括访问应用专属目录和通过SAF访问公共目录:

import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.OpenableColumns;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;public class MainActivity extends AppCompatActivity {private static final int PICK_IMAGE_REQUEST_CODE = 1;private TextView textView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);textView = findViewById(R.id.textView);Button writeToAppSpecificDirButton = findViewById(R.id.writeToAppSpecificDirButton);Button pickImageButton = findViewById(R.id.pickImageButton);// 写入应用专属目录的按钮点击事件writeToAppSpecificDirButton.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {writeToAppSpecificDir();}});// 选择图片的按钮点击事件pickImageButton.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {pickImage();}});}private void writeToAppSpecificDir() {// 获取应用专属目录File appSpecificDir = getExternalFilesDir(null);if (appSpecificDir != null) {// 在专属目录下创建一个新文件File newFile = new File(appSpecificDir, "test.txt");try {// 写入数据FileOutputStream fos = new FileOutputStream(newFile);fos.write("Hello, Scoped Storage!".getBytes());fos.close();textView.setText("文件已写入应用专属目录:" + newFile.getAbsolutePath());} catch (IOException e) {e.printStackTrace();textView.setText("写入文件时出错:" + e.getMessage());}} else {textView.setText("无法获取应用专属目录");}}private void pickImage() {// 创建一个Intent,用于启动SAFIntent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);intent.addCategory(Intent.CATEGORY_OPENABLE);intent.setType("image/*");// 启动SAFstartActivityForResult(intent, PICK_IMAGE_REQUEST_CODE);}@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);if (requestCode == PICK_IMAGE_REQUEST_CODE && resultCode == RESULT_OK) {if (data != null) {Uri uri = data.getData();try {// 通过Uri获取文件内容InputStream inputStream = getContentResolver().openInputStream(uri);// 获取文件名String fileName = getFileName(uri);textView.setText("已选择图片:" + fileName);} catch (IOException e) {e.printStackTrace();textView.setText("读取文件时出错:" + e.getMessage());}}}}private String getFileName(Uri uri) {String result = null;if (uri.getScheme().equals("content")) {Cursor cursor = getContentResolver().query(uri, null, null, null, null);try {if (cursor != null && cursor.moveToFirst()) {result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));}} finally {cursor.close();}}if (result == null) {result = uri.getPath();int cut = result.lastIndexOf('/');if (cut != -1) {result = result.substring(cut + 1);}}return result;}
}

代码解读与分析

  • writeToAppSpecificDir 方法:该方法用于向应用专属目录写入文件。首先通过 getExternalFilesDir 方法获取应用专属目录,然后在该目录下创建一个新的文件,并将数据写入该文件。
  • pickImage 方法:该方法用于启动SAF,让用户选择一张图片。通过创建一个 Intent,并设置其动作为 ACTION_OPEN_DOCUMENT,类型为 image/*,然后调用 startActivityForResult 方法启动SAF。
  • onActivityResult 方法:该方法用于处理SAF选择结果。当用户选择了一张图片后,会返回一个 Uri,通过该 Uri 可以获取文件的输入流,进而读取文件内容。
  • getFileName 方法:该方法用于获取文件的名称。通过 Uri 查询文件的元数据,从中获取文件名。

实际应用场景

  • 文件备份与恢复:应用可以将用户的数据备份到自己的专属目录中,当需要恢复数据时,从专属目录中读取文件。
  • 媒体文件处理:图片、音频、视频编辑应用可以通过SAF让用户选择媒体文件进行处理。
  • 文件共享:应用可以通过SAF将自己的文件分享给其他应用,或者接收其他应用分享的文件。

工具和资源推荐

  • Android Studio:官方的Android开发工具,提供了丰富的开发和调试功能。
  • Android Developer Documentation:官方的Android开发文档,包含了Scoped Storage的详细介绍和使用指南。
  • Stack Overflow:一个技术问答社区,开发者可以在上面查找关于Scoped Storage适配的相关问题和解决方案。

未来发展趋势与挑战

发展趋势

  • 更加严格的权限管理:未来Android系统可能会进一步加强对应用存储权限的管理,提高用户数据的安全性。
  • 更好的用户体验:SAF可能会不断优化,提供更加便捷的文件选择和管理界面,提升用户体验。
  • 多设备同步:随着多设备互联的发展,Scoped Storage可能会支持更好的文件在不同设备之间的同步和共享。

挑战

  • 适配难度增加:对于一些旧的应用,适配Scoped Storage可能会面临较大的难度,需要进行大量的代码修改。
  • 兼容性问题:不同的Android设备和版本可能会存在一定的兼容性问题,需要开发者进行充分的测试。
  • 用户教育:用户可能对新的存储机制不太熟悉,需要开发者在应用中提供清晰的引导和说明。

总结:学到了什么?

核心概念回顾:

我们学习了Scoped Storage(分区存储)、应用专属目录和SAF(Storage Access Framework)。Scoped Storage是一种新的存储管理机制,它限制了应用对外部存储的访问权限;应用专属目录是每个应用在外部存储中的私人领地,应用可以自由读写该目录下的文件;SAF是一个用于在Scoped Storage中让用户选择文件或目录的框架。

概念关系回顾:

我们了解了Scoped Storage、应用专属目录和SAF之间的关系。Scoped Storage规定了应用专属目录的使用规则,应用可以自由访问自己的专属目录;当应用需要访问公共目录时,需要通过SAF,并获得用户的授权。

思考题:动动小脑筋

思考题一:

你能想到在哪些应用场景中,Scoped Storage的权限管理机制可能会带来一些不便?

思考题二:

如果你是一个开发者,你会如何优化应用在Scoped Storage下的文件访问体验?

附录:常见问题与解答

问题一:在Android 10以下的系统中,是否需要适配Scoped Storage?

答:不需要,Scoped Storage是从Android 10开始引入的,在Android 10以下的系统中仍然使用传统的存储权限管理方式。

问题二:如果应用需要访问多个公共目录,应该如何处理?

答:可以多次调用SAF,让用户分别选择不同的目录。也可以使用 ACTION_OPEN_DOCUMENT_TREE 动作,让用户选择一个目录树,从而获得对该目录树及其子目录的访问权限。

扩展阅读 & 参考资料

  • Android Developer Documentation: https://developer.android.com/about/versions/10/privacy/changes#scoped-storage
  • Stack Overflow: https://stackoverflow.com/questions/tagged/scoped-storage

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Typecho集成PHPMailer实现邮件订阅功能完整指南

文章目录 Typecho使用PHPMailer实现文章推送订阅功能详解 1. 背景与需求分析 1.1 为什么选择PHPMailer 1.2 功能需求 2. 环境准备与配置 2.1 安装PHPMailer 2.2 数据库设计 3. 核心功能实现 3.1 邮件服务封装类 3.2 订阅功能实现 3.2.1 订阅表单处理 3.2.2 确认订阅处理 3.3 文…

无线-二层组网-直接转发

文章目录无线二层组网直接转发🏡作者主页:点击! 🤖Datacom专栏:点击! ⏰️创作时间:2025年07月16日08点00分 无线二层组网 直接转发 本地转发中所有的沿途都需要配置对应VLAN的通过&#xff…

gin go-kratos go-zero框架对比

Gin、Go-Kratos 和 Go-Zero 是 Go 语言中三种常见的服务框架,它们在定位、设计理念、复杂度和适用场景上差异较大。下面我们从功能定位、设计理念、优劣对比、使用建议等维度进行深入对比。🧭 一句话总结框架定位Gin轻量级、高性能的 HTTP 路由框架Go-Kr…

4G模块 A7670发送英文短信到手机

命令说明ATi显示产品的标志信息 ATCIMI查询IMSI ATCICCID从SIM卡读取ICCID ATCGSN查询产品序列号 ATCPIN查询卡状态 ATCSQ查询信号强度 ATCGATT查询当前PS域状态 ATCREG查询GPRS注册状态 ATCEREG查询4G注册状态 ATCGPADDR查询PDP地址 ATCMGF选择短信格式 ATCMGS发送短信流程第一…

归并排序递归法和非递归法的简单简单介绍

基本思想: 归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个…

webrtc之子带分割下——SplittingFilter源码分析

文章目录前言一、频带分割过程1.SplittingFilter的创建2.频带分割整体流程1)分割时机2)分割规则3)分割核心代码3.频带合并二、算法实现1.实现原理介绍2.All pass QMF系统源码1)提高精度2)经过串联全通滤波器3&#xff…

Java运维之Tomcat升级

Tomcat升级准备工作 下述所有过程中,包含了两种升级方式,一种是备份旧版本的 bin 和 lib,将新版本的 bin 和 lib 对旧版本进行覆盖;另一种是直接备份旧版本的Tomcat包,运行新版本,将旧版本的配置文件(conf/ * )和应用(webapps/ * )等同步到新版本。 1. 到官网下载指…

MySQL的可重复读隔离级别实现原理分析

MySQL 的 可重复读(Repeatable Read, RR) 隔离级别主要通过 多版本并发控制(Multi-Version Concurrency Control, MVCC) 和 锁机制(特别是间隙锁) 来实现的。其核心目标是:在一个事务内&#xf…

利用Java自定义格式,循环导出数据、图片到excel

利用Java自定义格式,循环导出数据、图片到excel1、自定义格式循环导出数据1.1.设置格式1.1.1、居中样式1.1.2、应用样式到合并区域1.1.3、合并单元格1.1.4、设置列宽1.2、写入数据1.2.1、创建标签头部1.2.2、写入标签内容2、自定义格式循环导出图片2.1、设置格式并插…

SAP学习笔记 - 开发45 - RAP开发 Managed App New Service Definition,Metadata Extension

上一章讲了在 Data Model View ( CDS View for BO Structure )基础上创建 Projection View ( CDS View for BO Projection )。 SAP学习笔记 - 开发44 - RAP开发 Managed App 建 Projection View,Provider Contract,用 redirected to 设定父子关系-CSDN博…

React强大且灵活hooks库——ahooks入门实践之高级类hook(advanced)详解

什么是 ahooks? ahooks 是一个 React Hooks 库,提供了大量实用的自定义 hooks,帮助开发者更高效地构建 React 应用。其中高级类 hooks 是 ahooks 的一个重要分类,专门用于处理一些高级场景,如受控值、事件发射器、性能…

计算机网络——数据链路层(25王道最新版)

数据链路层前言数据链路层的功能封装成帧(组帧)字符计数法字节填充法零比特填充法违规编码法小节差错控制检错编码奇偶校验码CRC校验码(循环冗余校验码)基本思想如何构造如何检错纠错纠错编码海明校验码设计思路求解步骤&#xff…

【PTA数据结构 | C语言版】字符串替换算法

本专栏持续输出数据结构题目集,欢迎订阅。 文章目录题目代码题目 请编写程序,将给定主串 s 中的子串 sub_s 替换成另一个给定字符串 t,再输出替换后的主串 s。 输入格式: 输入给出 3 个非空字符串,依次为&#xff1a…

事物生效,订单类内部更新订单

代码如下以下代码1不生效,2生效解决方案1,外层方法加注解,内层不加2,不要拆分方法,把更新订单操作放在带事物的大方法中3,拆方法(内部),注入自己,用代理对象调…

非对称加密:RSA

文章目录 非对称加密:RSA 1、RSA 加解密 2、RSA 生成密钥对(公钥、私钥)、加解密 参考资料 非对称加密:RSA 1、RSA 加解密 <!-- RSA --><!-- 引入jsencrypt库 --><script src="https://cdn.bootcdn.net/ajax/libs/jsencrypt/3.3.2/jsencrypt.min.js&q…

MongoDB 数据库 启用访问控制

0. 最近服务器安装了 MongoDB 被勒索了 测试服务器安装了 MongoDB 等&#xff0c;开放了 27017 对所有 ip。 哈哈哈哈哈哈&#xff0c;问就是有点犯懒&#xff0c;之前都是只允许自己的 ip。 好家伙&#xff0c;然后没过几个小时&#xff0c;数据库集合被清空&#xff0c;只留…

【Unity Sprite属性拓展】

Unity Inspector 精灵图预览为 Unity 中的 Sprite 类型属性提供了​​增强版的 Inspector 显示​​&#xff0c;在保留标准精灵选择功能的基础上&#xff0c;添加了大型预览图和精灵名称显示功能代码 using UnityEngine; using UnityEditor;// 1️⃣ 告诉 Unity&#xff1a;所有…

细菌实验入门:浓度测定与菌种鉴定技术详解

在微生物实验中&#xff0c;细菌浓度的精准测定和菌种的准确鉴定是两项基础且核心的操作。本文将详细介绍相关技术的原理、操作步骤及注意事项&#xff0c;为新手提供系统性指导。一、细菌浓度测定方法1. 光密度法&#xff08;OD600&#xff09;&#xff1a;快速定量的首选原理…

GaussDB 数据库架构师修炼(一)数据库容量规划

1、容量规划的定义GaussDB容量规划是指根据客户业务系统的负载需求或历史运行数据&#xff0c;进行合理规划GaussDB的计算、存储和网络资源配置&#xff0c;以满足业务系统正常使用和未来若干年负载增长诉求的过程。2、容量规划活动主要步骤需求收集调研生产系统的业务特征&…

hashMap原理(一)

概念HashMap是java中一种非常常用的基于哈希表的数据结构&#xff0c;允许o(1)的时间复杂度进行元素插入&#xff0c;查找&#xff0c;和删除。它通过”键-值“ 对的方式存储数据。总的来说&#xff1a;HashMap的底层原理&#xff1a;数组链表红黑树&#xff08;jdk1.8之后还涉…