admin管理员组文章数量:1532346
2023年12月13日发(作者:)
Android——Android10的分区存储(ScopedStorage)
分区存储介绍
在Android10以前,只要程序获得了READ_EXTERNAL_STORAGE权限,就可以随意读取外部的存储公有目录。只要程序获得了
WRITE_EXTERNAL_STORAGE权限,就可以随意在写入外部存储的公有目录上新建文件或文件夹
Android Q之前,应用存储视图
于是Google在Android10中提出了分区存储,意在限制程序对外部存储中公有目录的使用。
分区存储对内部存储私有目录和外部存储私有目录都没有影响
Android Q之后应用存储视图
简单来说就是,在Android10中,
对于私有目录的读写没有变化,仍然可以使用File那一套,且不需要任何权限。
对于公有目录的读写,则必须使用MediaStore提供的API或是SAF(存储访问框架)
在后续的Android11中,没有了Android10中的兼容模式,不能使用File I/O来读取App外置存储的目录
使用分区存储的应用对自己创建的文件始终拥有读/写权限,无论文件是否位于应用的私有目录内,所以,如果应用仅保存和访问自己创建的文
件,则无需请求获得READ_EXTERNAL_STORAGE或WRITE_EXTERNAL_STORAGE权限
如果要访问其他应用创建的文件,则需要READ_EXTERNAL_STORAGE权限。并且仍然只能使用MediaStore提供的API或是SAF访问。
这里需要注意的是,MediaStore提供的API只能访问图片、视频、音频,如果需要访问其它任意格式的文件,需要使用SAF,它会调用系统内置
的文件浏览器供用户自主选择文件
SAF是Android在 4.4中引入的一套存储访问框架(Storage Access Framework),借助 SAF,用户可轻松在其所有
首选文档存储提供程序中浏览并打开文档、图像及其他文件。用户可通过易用的标准界面,以统一方式在所有应用和提供
程序中浏览文件,以及访问最近使用的文件。
SAF交互使用介绍
Android Q规定了App有两种存储空间模式视图:Legacy View、Filtered View
Legacy View(兼容模式) 跟以前Android Q一样,App访问Sdcard一样
Filtered View(沙箱模式)
App只能直接访问App-specific目录文件,没有权限访问App-specific外的文件。访问其他目录,只能通过MediaStore、SAF、或者其他
App提供ContentProvider访问
Scoped Storage的存储空间
公共目录:Downloads、Documents、Pictures、DCIM、Movies、Music、Ringtones
公共目录的文件在App卸载后,不会删除
可以通过SAF、MediaStore接口访问
App-specific目录
对于Filtered View App,App-specific目录只能自己直接访问
App卸载,数据会清除
运行视图
App运行视图
系统通过下列方式确定App的运行模式:
App的TargetSDK>=Q,默认为Filtered View
App的TargetSDK 应用通过设置 requestLegacyExternalStorage true:表示兼容模式Legacy View false:表示沙箱模式 Filtered View 判断当前App的运行模式 判断当前App运行的是什么模式,可以通过Environment提供的API进行判断 rnalStorageLegacy() MediaStore的Uri定义 MediaStore提供了下列几种类型的访问Uri,通过查找对应Uri数据,达到访问的目的。 Audio Internal:AL_CONTENT_URI content://media/internal/audio/media External:AL_CONTENT_URI content://media/external/audio/media 可移动存储:tentUri content://media/ Video Internal:AL_CONTENT_URI content://media/internal/video/media External:AL_CONTENT_URI content://media/external/video/media 可移动存储:tentUri content://media/ Image Internal: AL_CONTENT_URI content://media/internal/images/media。 External: AL_CONTENT_URI content://media/external/images/media。 可移动存储: tentUri content://media/ File MediaStore. tentUri content://media/ Downloads Internal: AL_CONTENT_URI content://media/internal/downloads。 External: AL_CONTENT_URI content://media/external/downloads。 可移动存储: tentUri content://media/ 获取所有的Volume 我们还可以使用getContentUri获取所有 Uri跟公共目录关系 MediaProvider对于App存放到公共目录文件,通过ContentResolver insert方法中Uri来确定 权限 MediaStroe通过不同Uri,为用户提供了增、删、改方法,权限对应如下 由上表可以看出没在操作共享存储空间时,获取的权限不同可以对应不同的操作共享存储空间的方式 WRITE_EXTERNAL_STORAGE:获取这个权限,可以修改所有的app新建的文件,但是都需要授予权限 READ_EXTERNAL_STORAGE:获取这个权限,可以读取所有的app新建的文件,不能修改其他App新建的文件 什么权限都不获取的话,只能读取、修改自己app新建的文件 操作共享存储空间,读写公共目录 通过Media定义的URI 新建文件(通过ContentResolver的insert接口,使用不同的Uri选择存储到不同的目录) /** * 通过SAF创建文件文件夹 * * @param view */ public void createSAF(View view) { Uri uri = tentUri("external"); ContentResolver contentResolver = tentResolver(); //定义path String path = ORY_DOWNLOADS + "/wx"; ContentValues contentValues = new ContentValues(); (VE_PATH, path); (Y_NAME, "wxPic"); (, "it's title"); Uri resultUri = (uri, contentValues); if (resultUri != null) { xt(this, "创建文件夹成功", _SHORT).show(); } } 新建一张图片 /** * 插入一张图片 * * @param view */ public void insertImage(View view) { Bitmap bitmap = Resource(getResources(), ); String disPlayPicName = tTimeMillis() + ""; String mimeType = "image/jpeg"; ContentValues contentValues = new ContentValues(); (Y_NAME, disPlayPicName); (_TYPE, mimeType); (VE_PATH, ORY_PICTURES + tor + "wx" + tor); imageUri = getContentResolver().insert(AL_CONTENT_URI, contentValues); OutputStream outputStream = null; try { outputStream = getContentResolver().openOutputStream(imageUri); ss(, 100, outputStream); (); FileDescriptor fileDescriptor = getContentResolver().openFileDescriptor(imageUri, "r").getFileDescriptor(); } catch (Exception e) { tackTrace(); } xt(this, "添加图片成功", _SHORT).show(); } 修改照片 /** * 修改数据 * * @param view */ public void updateImage(View view) { ContentValues contentValues = new ContentValues(); (Y_NAME, ""); int update = getContentResolver().update(imageUri, contentValues, null, null); if (update > 0) { xt(this, "修改成功", _SHORT).show(); } } 查询数据 /** * 查询数据 * * @param view */ public void query(View view) { //获取URI Uri external = AL_CONTENT_URI; //创建selection String selection = Y_NAME + "=?"; String[] arg = new String[]{""}; Cursor cursor = getContentResolver().query(external, null, selection, arg, null); if (cursor != null && First()) { Uri idUri = pendedId(external, g(0)); xt(this, "获取成功" + idUri, _SHORT).show(); (); } } 删除数据 /** * 删除图片 * * @param view */ public void deleteImage(View view) { int delete = getContentResolver().delete(imageUri, null); if (delete > 0) { xt(this, "删除成功", _SHORT).show(); } } 关于RecoverableSecurityException异常 当我们删除其他应用创建的资源时会报出RecoverableSecurityException异常,我们可以捕获这个异常然后提示给与 uri修改或删除的权限 private fun deleteImage(imageUri: Uri, adapterPosition: Int) { var row = 0 try { // Android 10+中,如果删除的是其它应用的Uri,则需要用户授权 // 会抛出RecoverableSecurityException异常 row = (imageUri, null, null) } catch (securityException: SecurityException) { if (_INT >= N_CODES.Q) { val recoverableSecurityException = securityException as? RecoverableSecurityException : throw securityException pendingDeleteImageUri = imageUri pendingDeletePosition = adapterPosition // 我们可以使用IntentSender向用户发起授权 requestRemovePermission(Sender) } else { throw securityException } } if (row > 0) { xt(this, "删除成功", _SHORT).show() Position(adapterPosition) } } private fun requestRemovePermission(intentSender: IntentSender) { startIntentSenderForResult(intentSender, REQUEST_DELETE_PERMISSION, null, 0, 0, 0, null) } private fun deletePendingImageUri(){ pendingDeleteImageUri?.let { pendingDeleteImageUri = null deleteImage(it,pendingDeletePosition) pendingDeletePosition = -1 } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { vityResult(requestCode, resultCode, data) if (resultCode == _OK && requestCode == REQUEST_DELETE_PERMISSION ) { // 执行之前的删除逻辑 deletePendingImageUri() } } 如果想获取Download文件夹下的某个非媒体文件怎么办 例如PDF,PDF为非媒体类文件,因此我们不能通过MediaStore来获取,对于这种其他类型的文件,一般使用SAF来让用户选择 private fun selectPdfUseSAF() { val intent = Intent(_OPEN_DOCUMENT).apply { type = "application/pdf" // 我们需要使用leDescriptor读取数据 addCategory(RY_OPENABLE) } startActivityForResult(intent, REQUEST_OPEN_PDF) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { vityResult(requestCode, resultCode, data) when (requestCode) { REQUEST_OPEN_PDF -> { if (resultCode == _OK) { data?.data?.also { documentUri -> val fileDescriptor = leDescriptor(documentUri, "r") ?: return // 现在,我们可以使用PdfRenderer等类通过fileDescriptor读取pdf内容 xt(this, "pdf读取成功", _SHORT).show() } } } } } 如何创建任意类型的文件 我们也推荐使用SAF让用户自己去创建,IntentAction为:ACTION_CREATE_DOCUMENT 访问App-specific目录 访问app-specific分为两种情况,一种是访问App自身App-specific目录,第二是访问其他App目录文件 App访问自身App-specific目录 Android Q,App如果启动了Filtered View,那么只能直接访问自己目录的文件: ernalStorageDirectory、getExternalStoragePublicDirectory这些接口在Android Q上废弃,App是Filtered View,无法直接访问这个目录。 通过File(“/sdcard/”)访问App是Filtered View,无法直接访问这个目录。 获取App-specific目录 获取Media接口:getExternalMediaDirs 获取Cache接口:getExternalCacheDirs 获取Obb接口:getObbDirs 获取Data接口:getExternalFilesDirs App访问App-sepecific目录内部的多媒体文件 App自身访问,和App访问自身的App-soecific目录一样 其他App访问 默认情况下,Media Scanner不会扫描App-specific里的多媒体文件,如果需要扫描通过le添加到 MediaProvider数据库中,访问方式和访问共享存储空间方式一样 App通过创建ContentProvider共享出去 App访问其他App目录文件 App是FilteredView,其他App无法直接访问当前App私有目录,需要通过以下方法: 通过SAF文件 App自定义DocumentsProvider 访问App通过ACTION_OPEN_DOCUMENT启动SAF浏览 实现FileProvider(某些手机可能有问题) App自定义私有Provider App访问不同目录的权限总结
版权声明:本文标题:Android——Android10的分区存储(ScopedStorage) 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/dongtai/1702457798a7944.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论