一:Android 11 (API 30)中的存储机制更新:
Android 11(API 级别 30)进一步增强了平台功能,为外部存储设备上的应用和用户数据提供了更好的保护。此版本引入了多项增强功能,例如,可主动选择启用的媒体原始文件路径访问机制、面向媒体的批量编辑操作,以及存储访问框架的界面更新。
此版本还改进了分区存储,以便开发者更轻松地迁移到此存储模型。如需了解详情,请参阅 Android 存储用例和最佳做法指南,以及标题为 Android 11 存储常见问题解答的 Medium 文章。
强制执行分区存储
在 Android 11 上运行但以 Android 10(API 级别 29)为目标平台的应用仍可请求 requestLegacyExternalStorage
属性。应用可以利用此标记暂时停用与分区存储相关的变更,例如授予对不同目录和不同类型的媒体文件的访问权限。当您将应用更新为以 Android 11 为目标平台后,系统会忽略 requestLegacyExternalStorage
标记。
保持与 Android 10 的兼容性
如果应用在 Android 10 设备上运行时选择退出分区存储,建议您继续在应用的清单文件中将 requestLegacyExternalStorage
设为 true
。这样,应用就可以在运行 Android 10 的设备上继续按预期运行。
将数据迁移到使用分区存储时可见的目录
如果您的应用使用旧版存储模型且之前以 Android 10 或更低版本为目标平台,您可能会将数据存储到启用分区存储模型后您的应用无法访问的目录中。在以 Android 11 为目标平台之前,请将数据迁移到与分区存储兼容的目录。
二:数据的迁移:
1.对于编译版本为API 29的应用来说:
如果以Android10为目标平台,需要在manifest清单中标记requestLegacyExternalStorage为true,这样可以停用分区存储,然后就能做迁移。(在Android 11会忽略该字段,强制开启分区存储,该字段也不怎么靠谱。)
2.对于编译版本为API 30的应用来说:
要想访问外部存储,需要在manifest清单中标记preserveLegacyExternalStorage 为true,这样在Android11的机器上覆盖安装时,才能访问旧版存储位置,才能去做迁移。(卸载重装会失效)
这里需要注意以下两点:
-
大多数应用都不需要使用
preserveLegacyExternalStorage
。此标记仅适用于这样一种情况:你将应用数据迁移到了与分区存储兼容的位置,并且希望用户在更新你应用时保留对数据的访问权限。使用此标记会导致更难以测试分区存储对应用的用户有何影响,因为当用户更新应用时,它会继续使用旧版存储模型。 -
如果使用
,则旧版存储模型只在用户卸载应用之前保持有效。如果用户在搭载 Android 11 的设备上安装或重新安装应用,则无论preserveLegacyExternalStorage
preserveLegacyExternalStorage
的值是什么,应用都无法停用分区存储模型。
三:如何读写数据:
1.Android 数据结构:
-
访问内部存储目录,这部分数据在程序卸载后会删除:
getDataDir(); getCacheDir();
-
访问外部存储 APP 私有目录,这部分数据在app卸载后也会删除,在Android 11后,即使拥有MANAGE_EXTERNAL_STORAGE权限,其他app也无法访问这些数据:
-
Environment.getExternalStorageDirectory(); getExternalCacheDir(); getExternalFilesDir(Environment.DIRECTORY_PICTURES); getExternalFilesDir(Environment.DIRECTORY_PICTURES); getExternalFilesDir(Environment.DIRECTORY_PICTURES);
-
访问共享的存储空间文件,比如相册等:
1.可以通过Storage Access Framework (SAF)来访问某个文件:private void startSAF() { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); //指定选择文本类型的文件 intent.setType("text/plain"); startActivityForResult(intent, 100); } @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == 100) { //选中返回的文件信息封装在Uri里 Uri uri = data.getData(); openUriForRead(uri); } }
- 可以通过 MediaStore API访问;依赖原始文件路径的 I/O 请求会被重定向到使用 MediaStore API,当使用这种方式访问本应用存储空间之外的文件时,这次重定向会造成性能影响。而且直接使用原始文件路径,并不会比使用 MediaStore API 有更多优势,因此我们强烈建议直接使用 MediaStore API。获取到Uri ,进而怼及那件进行读写操作:
-
private void test(){ String fileName="test.jpeg"; Uri contentUri = getUriByFileName(fileName); OutputStream outputStream= getContentResolver().openOutputStream(contentUri); View viewT = getWindow().getDecorView(); Bitmap newBitmap=Bitmap.createBitmap(viewT.getWidth(),viewT.getHeight(),Bitmap.Config.RGB_565); Canvas canvas=new Canvas(newBitmap); viewT.draw(canvas); newBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream); outputStream.flush(); outputStream.close(); } private Uri getUriByFileName( String fileName) { ContentValues contentValues = new ContentValues(); contentValues.put(MediaStore.Images.ImageColumns.DISPLAY_NAME, fileName); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { //RELATIVE_PATH 字段表示相对路径-------->(1) contentValues.put(MediaStore.Images.ImageColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES); } else { String dstPath = Environment.getExternalStorageDirectory() + File.separator + Environment.DIRECTORY_PICTURES + File.separator + fileName; //DATA字段在Android 10.0 之后已经废弃 contentValues.put(MediaStore.Images.ImageColumns.DATA, dstPath); } //插入相册------->(2) Uri uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues); return uri; }
MediaStore提供了下列几种类型的访问Uri,通过查找对应Uri数据,达到访问的目的。
下列每种类型又分为三种Uri,Internal、External、可移动存储:●Audio
■ Internal: MediaStore.Audio.Media.INTERNAL_CONTENT_URIcontent://media/internal/audio/media。
■ External: MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
content://media/external/audio/media。
■ 可移动存储: MediaStore.Audio.Media.getContentUri
content://media/<volumeName>/audio/media。
● Video
■ Internal: MediaStore.Video.Media.INTERNAL_CONTENT_URI
content://media/internal/video/media。
■ External: MediaStore.Video.Media.EXTERNAL_CONTENT_URI
content://media/external/video/media。
■ 可移动存储: MediaStore.Video.Media.getContentUri
content://media/<volumeName>/video/media。
● Image
■ Internal: MediaStore.Images.Media.INTERNAL_CONTENT_URI
content://media/internal/images/media。
■ External: MediaStore.Images.Media.EXTERNAL_CONTENT_URI
content://media/external/images/media。
■ 可移动存储: MediaStore.Images.Media.getContentUri
content://media/<volumeName>/images/media。
● File
■ MediaStore. Files.Media.getContentUri
content://media/<volumeName>/file。
● Downloads
■ Internal: MediaStore.Downloads.INTERNAL_CONTENT_URI
content://media/internal/downloads。
■ External: MediaStore.Downloads.EXTERNAL_CONTENT_URI
content://media/external/downloads。
■ 可移动存储: MediaStore.Downloads.getContentUri
content://media/<volumeName>/downloads。 -
访问扩展存储:需要动态申请MANAGE_EXTERNAL_STORAGE权限,然后可以遍历文件了;
使用 ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION intent 操作将用户引导至一个系统设置页面,在该页面上,用户可以为您的应用启用以下选项:授予所有文件的管理权限。
参考博客: