关于安卓11 sdk30 分区存储的一些适配

本文介绍了在Android 11及以上版本中应用如何适配新的存储机制,包括使用getFilesDir()方法的变化、访问应用专属目录的方法、以及如何处理外部存储等问题。

App在使用存储功能时会遇到的问题,谨以拙笔 助后至者

targetsdk = 29 (安卓10)

可以在清单文件中 application 标签加上android:requestLegacyExternalStorage=“true”

禁用分区存储,就可以正常使用老存储功能了

targetsdk ≥ 30 (安卓11+)

此时禁用分区存储 也没啥用了,强制开启分区存储

适配

访问应用专属目录(应用内部存储):

  • 应用专属目录位于/data/data/包名/下,可以直接访问。
File file = new File(getFilesDir(),"/test");
  if (!file.exists())
    file.mkdir();
// getFilesDir() -> /data/user/0/应用包名/files   
// 创建后 -> /data/user/0/应用包名/files/test  
     

关于getFilesDir() 获取目录是/data/user/0/... 而实际目录是 /data/data/...疑问
在Android 11及更高版本中,使用getFilesDir()方法获取的路径是/data/user/0/packageName,而不是之前的/data/data/packageName。
这是由于Android 11引入了应用沙盒隔离机制的一部分,称为Scoped Storage(作用域存储)。Scoped Storage的目标是提供更加安全和可控的文件访问权限。
在Scoped Storage中,每个应用都有一个私有的存储空间,只能通过应用自身的API进行访问。getFilesDir()方法返回的路径/data/user/0/packageName是一个应用的私有目录,在此目录下创建的文件只能由应用本身访问。
实际上,在/data/user/0/packageName目录下创建的文件仍然存在于/data/data/packageName目录中,但是直接访问/data/data/packageName目录的权限被限制,只有通过应用的API或者特定的访问权限才能进行访问。
这样的变化是为了增强应用数据的隐私和安全性,避免应用之间的数据冲突和滥用。

外部存储:

File file = new File(getExternalFilesDir(null),"/test");
  if (!file.exists())
    file.mkdir();
// getExternalFilesDir(null) -> /storage/emulated/0/Android/data/应用包名/files 
//创建后 ->  /storage/emulated/0/Android/data/应用包名/files/test

缓存文件:

File externalCacheFile = new File(context.getExternalCacheDir(), filename);
getExternalCacheDir()
//  -> /storage/emulated/0/Android/data/应用包名/cache

//删除缓存文件
externalCacheFile.delete();

媒体文件:

File file = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), albumName);

getExternalFilesDir(Environment.DIRECTORY_PICTURES)
//  -> /storage/emulated/0/Android/data/应用包名/files/Pictures

谷歌规定 务必使用 DIRECTORY_PICTURES 等 API 常量提供的目录名称。这些目录名称可确保系统正确处理文件如果没有适合您文件的预定义子目录名称,您可以改为将 null 传递到 getExternalFilesDir()

获取共享的存储目录路径:

Environment.getExternalStoragePublicDirectory()

如获取照片目录路径可以使用 
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)

查询可用内存:

 StorageManager storageManager =
                getApplicationContext().getSystemService(StorageManager.class);
        UUID appSpecificInternalDirUuid = null;
        try {
            appSpecificInternalDirUuid = storageManager.getUuidForPath(getFilesDir());
            long availableBytes =
                    storageManager.getAllocatableBytes(appSpecificInternalDirUuid);
            Log.i(TAG,"可用空间:"+(availableBytes / (1024 * 1024))+" M");
        } catch (IOException e) {
            e.printStackTrace();
        }
 //打印 -> 可用空间:85631 M

关于共享存储空间

  • 安卓11引入了一种通过特殊的 SAF(Storage Access Framework)API 访问外部存储的方式,称为共享存储空间(Shared Storage Space)。通过该 API,应用可以允许用户选择访问外部存储的特定文件或目录,并拥有对它们的读写权限。
  • 使用共享存储空间,应用可以在用户选择的文件或目录中进行读写操作,而无需直接访问文件路径。

相关细节详见官方:访问应用专属文件 | Android 开发者 | Android Developers (google.cn)

<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]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值