Xamarin.Android无需All Files Access的文件夹选择器权限方案咨询
合规实现Android 12(API31)下SD卡自定义位置读写方案
针对你遇到的Google Play审核禁止MANAGE_EXTERNAL_STORAGE(All Files Access)权限,且传统存储权限无法访问SD卡自定义目录的问题,推荐使用**Storage Access Framework(SAF)**实现合规的自定义目录读写,这是Google官方推荐的替代方案,无需特殊权限,完全符合政策要求。
核心方案:使用SAF系统文件选择器
SAF允许用户主动授权应用访问指定的SD卡目录,应用通过获得的Uri进行文件操作,无需申请READ_EXTERNAL_STORAGE/WRITE_EXTERNAL_STORAGE或MANAGE_EXTERNAL_STORAGE权限。
1. 启动系统目录选择器,获取用户授权的SD卡目录Uri
private const val REQUEST_CODE_SELECT_DIR = 1001 fun launchDirectoryPicker() { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) // 添加权限标记,确保读写权限及持久化授权 intent.addFlags( Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION ) startActivityForResult(intent, REQUEST_CODE_SELECT_DIR) }
2. 处理选择结果,持久化保存授权Uri
将用户选择的目录Uri保存到SharedPreferences,避免每次启动重复授权:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_CODE_SELECT_DIR && resultCode == RESULT_OK) { data?.data?.let { treeUri -> // 持久化权限,确保应用重启后仍能访问该目录 contentResolver.takePersistableUriPermission( treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION ) // 保存Uri到本地存储 getSharedPreferences("AppStoragePrefs", MODE_PRIVATE) .edit() .putString("selected_sd_dir_uri", treeUri.toString()) .apply() } } }
3. 通过ContentResolver进行目录创建与文件读写
创建自定义文件夹
fun createDataFolder(folderName: String): Uri? { val prefs = getSharedPreferences("AppStoragePrefs", MODE_PRIVATE) val treeUriString = prefs.getString("selected_sd_dir_uri", null) ?: return null val treeUri = Uri.parse(treeUriString) // 构建父目录的文档ID val parentDocId = DocumentsContract.getTreeDocumentId(treeUri) // 构建子目录的Uri val childDirUri = DocumentsContract.buildChildDocumentsUriUsingTree(treeUri, parentDocId) // 设置文件夹属性 val folderValues = ContentValues().apply { put(DocumentsContract.Document.COLUMN_DISPLAY_NAME, folderName) put(DocumentsContract.Document.COLUMN_MIME_TYPE, DocumentsContract.Document.MIME_TYPE_DIR) } // 插入并返回新文件夹的Uri return contentResolver.insert(childDirUri, folderValues) }
写入数据文件
以写入生物数据CSV文件为例:
fun writeDataFile(folderUri: Uri, fileName: String, content: String) { val fileValues = ContentValues().apply { put(DocumentsContract.Document.COLUMN_DISPLAY_NAME, fileName) put(DocumentsContract.Document.COLUMN_MIME_TYPE, "text/csv") } // 在指定文件夹下创建文件 val fileUri = contentResolver.insert(folderUri, fileValues) ?: return // 写入文件内容 contentResolver.openOutputStream(fileUri)?.use { outputStream -> outputStream.write(content.toByteArray()) } }
关键说明
- API31及以上,
READ_EXTERNAL_STORAGE/WRITE_EXTERNAL_STORAGE仅能访问公共媒体目录(图片、视频等)和下载目录,无法访问SD卡自定义目录,因此必须使用SAF。 - 无需申请任何特殊权限,SAF的授权完全由用户主动触发,符合Google Play隐私政策。
- 持久化权限在应用卸载后会自动失效,重新安装需用户再次授权。
- 可在应用首次启动时添加引导说明,告知用户选择SD卡目录的用途(野外数据存储、便于电脑读取),提升用户体验。
内容的提问来源于stack exchange,提问作者Fanie Lombard
相关产品推荐
相关产品推荐

