Android Q分区存储权限变更及适配

在Android Q中,分区存储功能为每个应用提供了隔离的存储空间,无需额外权限即可访问私有文件。此功能减少了应用所需的权限,保护了用户隐私。文章详细介绍了访问媒体文件、私有文件及卸载后保留文件的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

分区存储

在Android Q中引入了分区储存功能,在外部存储设备中为每个应用提供了一个“隔离存储沙盒”。其他应用无法直接访问应用的沙盒文件。由于文件是应用的私有文件,不再需要任何权限即可访问和保存自己的文件。此变更并有助于减少应用所需的权限数量,同时保证用户文件的隐私性。

权限变更

Android Q 更改了应用对设备外部存储设备中的文件(如:/sdcard )的访问方式。继续使用 READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE 权限,只不过当拥有这些权限的时候,你只能访问媒体文件,无法访问其他文件。

在早先的beta版本中,Android需要申请特定的媒体权限 :READ_MEDIA_IMAGES, READ_MEDIA_VIDEO , READ_MEDIA_AUDIO, 但是在beta4中,这些权限被废弃

访问私有文件

应用需要将文件存储在应用的沙盒中,并且访问这个文件夹无需权限。官方推荐应用在沙盒内存储文件的地址为Context.getExternalFilesDir()下的文件夹。
比如要获得一张图片

Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)

访问其他应用创建的文件

只有在满足以下两个条件时,您的应用才能访问其他应用创建的文件:
1、 您的应用已获得 READ_EXTERNAL_STORAGE 权限。

2、这些文件位于以下其中一个明确定义的媒体集合中:
照片:存储在 MediaStore.Images 中。
视频:存储在 MediaStore.Video 中。
音乐文件:存储在 MediaStore.Audio 中。

任何其他文件(包括“downloads”目录下的文件),必须使用存储访问框架

注意访问外部存储设备中的文件时会进入过滤视图的应用不具有对 /sdcard/DCIM/IMG1024.JPG 等路径的直接内核访问权限。要访问此类文件,应用必须使用 MediaStore.openFile() 等方法

卸载后保留应用的文件

文件存储在应用私有目录下,在卸载该应用后,系统会清除该应用的目录中的所有文件(有点类似Android/data/xxx目录)。有时我们要在卸载后保留这些文件,请将其保存到 MediaStore 中的某个目录下。

选择停用分区存储

在Android Q设备上有两种方式来让分区存储生效:

  • 以 Android 9 或更低版本为目标平台 (Target SDK <=28)
  • 如果Target SDK > 28,请在manifest中添加android:requestLegacyExternalStorage=“true”

这样就可以采用原有的存储策略。以上方式不建议使用。
官方警告明年,所有应用的主要平台版本都需要分区存储,无论其采用哪种目标 SDK 级别。

文件访问权限摘要

文件位置所需权限访问方法卸载时是否删除文件
应用私有目录getExternalFilesDir()
媒体集合(照片、视频、音频)READ_EXTERNAL_STORAGE(仅当访问其他应用的文件时)MediaStore
下载内容(文档和电子书籍)存储访问框架(加载系统的文件选择器

您可以使用存储访问框架访问上表中显示的每个位置,而无需请求任何权限。

特定文件访问适配

分享媒体文件

如果你的应用有分享照片和视频需求。请使用 MediaStore存储需要共享的文件。

如果您提供一组配套应用(例如短信应用和个人资料应用),请使用 content:// URI 设置文件共享。已经建议将此工作流作为一项安全最佳做法

使用文档

如果需要打开企业办公文档或打开另存为 EPUB 文件的图书。
通过调用 ACTION_OPEN_DOCUMENT intent 能选择要打开的文件, intent 会打开系统的文件选择器应用。显示应用所支持类型的文件,intent 中需要包含Intent.EXTRA_MIME_TYPES extra

GitHub 上的 ActionOpenDocument 示例说明了如何使用 ACTION_OPEN_DOCUMENT 打开文件。

访问和修改媒体内容

上面已经介绍过了不再重复,需要使用MediaStore

更新其他应用的媒体文件

Android Q以前应用都不太关注其它用户组访问应用目录权限,适配Android Q后你会接到厂商要求你限定用户组访问存储目录权限问题单。
要修改另一个应用保存到外部存储设备的给定媒体文件,请捕获平台抛出的 RecoverableSecurityException。然后,您可以请求用户授予您的应用对此特定内容的写入权限。

照片中的位置信息

我们拍摄的照片一般在Exif元数据中包含了位置信息,在Android Q 以前我们可以方便的获取到图片的位置信息,Android Q 会默认对您的应用隐藏此类信息。并且这种位置信息限制与适用于相机功能的限制不同。
如果您的应用需要访问照片的位置信息,请完成以下步骤:

  1. 将新的 ACCESS_MEDIA_LOCATION 权限添加到应用清单中。
  2. 在 MediaStore 对象中调用setRequireOriginal(),在调用时传入照片的 URI。
	val photoUri = MediaStore.setRequireOriginal(photoUri)
    contentResolver.openInputStream(photoUri).use { stream ->
        ExifInterface(stream).run {
            // If lat/long is null, fall back to the coordinates (0, 0).
            val latLong = ?: doubleArrayOf(0.0, 0.0)
        }
    }

<think>我们正在处理用户关于Android 11分区存储机制适配的问题。用户希望了解如何适配Android 11的分区存储机制。根据提供的引用内容,我们可以总结出以下几点: 1. Android 10(API 29)引入了分区存储(Scoped Storage)的概念,而Android 11(API 30)进一步强化了这一机制。 2. 分区存储的目的是增强用户数据的安全性和隐私性,限制应用对外部存储的随意访问。 3. 适配分区存储的关键点包括:使用应用私有目录、使用MediaStore访问公共媒体文件、使用存储访问框架(SAF)访问其他文件等。 4. 对于需要广泛访问文件的应用,可以申请MANAGE_EXTERNAL_STORAGE权限,但这需要用户授权,并且Google Play对这类应用有严格的审核。 根据引用[4],我们知道在Android 11中,应用私有目录的访问方式有所变化,但通过getFilesDir()等API获取的路径虽然显示为`/data/user/0/packageName`,实际对应的还是`/data/data/packageName`,只是访问权限被严格控制。 适配步骤: 1. **确定目标SDK版本**:将应用的targetSdkVersion设置为30(或更高)以适配Android 11。 2. **检查现有文件访问代码**:识别所有直接使用文件路径访问外部存储的代码,特别是访问公共目录(如Downloads、DCIM等)的代码。 3. **使用应用私有目录**:对于应用私有文件,使用Context提供的方法(如getFilesDir(), getExternalFilesDir())来访问,这些目录不需要权限。 4. **访问媒体文件**:使用MediaStore API来访问公共的媒体文件(图片、视频、音频等)。注意,从Android 10开始,不需要申请存储权限(READ_EXTERNAL_STORAGE/WRITE_EXTERNAL_STORAGE)就可以访问MediaStore中的文件,但如果是访问其他应用创建的非媒体文件,则可能需要使用存储访问框架(SAF)。 5. **访问其他文件**:对于非媒体文件(如下载的文档),使用存储访问框架(SAF)让用户选择文件或目录。 6. **处理需要广泛访问的情况**:如果应用确实需要访问设备上的所有文件(例如文件管理器),则申请MANAGE_EXTERNAL_STORAGE权限,并在代码中引导用户到系统设置中开启该权限。但要注意,Google Play对使用该权限的应用有严格限制,需要符合特定类型。 7. **适配文件路径访问**:避免直接使用绝对路径,而是使用ContentResolver打开文件描述符(FileDescriptor)或输入/输出流。 8. **测试**:在Android 11及更高版本的设备上充分测试。 具体代码示例: 1. 使用MediaStore插入图片到公共相册: ```java ContentValues values = new ContentValues(); values.put(MediaStore.Images.Media.DISPLAY_NAME, "image.jpg"); values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/MyApp"); ContentResolver resolver = context.getContentResolver(); Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); if (uri != null) { try (OutputStream outputStream = resolver.openOutputStream(uri)) { // 将图片数据写入outputStream } catch (IOException e) { e.printStackTrace(); } } ``` 2. 使用存储访问框架(SAF)让用户选择目录: ```java Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); startActivityForResult(intent, REQUEST_CODE_OPEN_DIRECTORY); ``` 然后在`onActivityResult`中处理返回的Uri。 3. 申请MANAGE_EXTERNAL_STORAGE权限(如果必要): 在AndroidManifest.xml中添加: ```xml <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> ``` 在代码中检查权限并引导用户设置: ```java if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (!Environment.isExternalStorageManager()) { Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION); startActivity(intent); } } ``` 注意:在Android 11中,即使应用获得了MANAGE_EXTERNAL_STORAGE权限,访问应用私有目录(如/data/data/packageName)仍然受到限制,因为这是系统保护的。 最后,关于引用[4]中提到的getFilesDir()路径问题,我们不需要改变使用方式,继续使用这些API即可,系统会处理好路径映射。 因此,适配Android 11的分区存储机制,核心是遵循作用域存储的规则,使用系统提供的API来访问文件,避免直接使用文件路径。 相关问题: 1. 在Android 11中,如何访问其他应用创建的非媒体文件? 2. 如果应用需要备份用户的整个外部存储,应该如何设计? 3. 适配分区存储后,如何测试文件访问功能?</think>### 适配 Android 11 分区存储机制的方法 #### 1. **理解分区存储的核心变化** - 📌 **作用域存储**:应用只能访问自身私有目录(`/data/user/0/包名`)和通过特定 API 访问的公共媒体文件[^4]。 - 📌 **路径限制**:直接使用 `/data/data/包名` 路径被禁止,需通过 `getFilesDir()` 等 API 获取私有路径[^4]。 - 📌 **权限变更**:`READ_EXTERNAL_STORAGE/WRITE_EXTERNAL_STORAGE` 权限Android 11 中仅限访问媒体文件,**不再支持任意文件访问**[^2]。 #### 2. **关键适配步骤** ##### (1) 私有目录访问(无需权限) ```kotlin // 获取应用私有目录 val privateDir: File = context.filesDir // 等价于 /data/user/0/包名/files // 创建私有文件 val privateFile = File(context.filesDir, "example.txt") privateFile.writeText("Hello Scoped Storage") ``` ##### (2) 公共媒体文件访问(需权限) ```kotlin // 插入图片到公共相册(需要 WRITE_EXTERNAL_STORAGE) val values = ContentValues().apply { put(MediaStore.Images.Media.DISPLAY_NAME, "photo.jpg") put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg") put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES) } val uri = contentResolver.insert( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values ) // 写入数据 uri?.let { contentResolver.openOutputStream(it).use { stream -> stream?.write(bitmapBytes) } } ``` ##### (3) 非媒体文件访问(使用存储访问框架 SAF) ```kotlin // 通过系统文件选择器获取访问权限 val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply { flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION } startActivityForResult(intent, REQUEST_CODE) ``` ##### (4) 特殊场景:访问所有文件 - 在 `AndroidManifest.xml` 声明权限: ```xml <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> ``` - 引导用户开启权限: ```kotlin if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { if (!Environment.isExternalStorageManager()) { val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION) startActivity(intent) } } ``` > ⚠️ 注意:Google Play 对此权限有严格审核,仅限文件管理器/备份工具等特定应用使用[^3]。 #### 3. **兼容性处理** ```kotlin // 根据版本选择策略 fun isScopedStorageEnabled() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R // Android 11+ if (isScopedStorageEnabled()) { // 使用 MediaStore/SAF } else { // 传统文件访问(需请求存储权限) } ``` #### 4. **必须移除的过时代码** - ❌ 禁止使用 `file://` 路径访问公共目录 - ❌ 停止使用 `Environment.getExternalStorageDirectory()` - ❌ 避免直接操作 `/sdcard/` 等路径 > **适配建议**:优先使用私有目录存储应用数据,公共文件通过 MediaStore 或 SAF 交互式访问,确保符合隐私保护要求[^2][^4]。 --- ###
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值