API 30及以上版本如何访问与写入外置SD卡文件?
API 30及以上访问外置SD卡文件的解决方案
一、使用Storage Access Framework (SAF)操作外置SD卡文件
1. 唤起SD卡目录选择
启动ACTION_OPEN_DOCUMENT_TREE意图,引导用户选择外置SD卡的目标目录:
private const val REQUEST_CODE_SD_DIR = 1001 val selectDirIntent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) startActivityForResult(selectDirIntent, REQUEST_CODE_SD_DIR)
2. 持久化目录访问权限
用户选择目录后,在回调中获取目录Uri并申请持久化权限,避免每次启动重复选择:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == REQUEST_CODE_SD_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保存到SharedPreferences,后续复用 getSharedPreferences("storage_prefs", MODE_PRIVATE) .edit() .putString("sd_tree_uri", treeUri.toString()) .apply() } } }
3. 读写指定格式文件
写入CSV/PDF文件
通过DocumentFile创建或获取文件,使用ContentResolver的流操作写入内容:
// 从SharedPreferences获取持久化的目录Uri val treeUri = Uri.parse( getSharedPreferences("storage_prefs", MODE_PRIVATE) .getString("sd_tree_uri", "") ) val rootDir = DocumentFile.fromTreeUri(this, treeUri) ?: return // 写入CSV示例 val csvFile = rootDir.createFile("text/csv", "user_data.csv") ?: return contentResolver.openOutputStream(csvFile.uri)?.use { output -> OutputStreamWriter(output).apply { write("ID,Name,Age\n1,Alice,28\n2,Bob,32") flush() } } // 写入PDF示例(假设已有PDF字节流) val pdfFile = rootDir.createFile("application/pdf", "report.pdf") ?: return contentResolver.openOutputStream(pdfFile.uri)?.use { output -> yourPdfByteArray?.let { output.write(it) } }
操作DB文件
外置SD卡上的DB无法直接通过SQLiteOpenHelper访问,需先复制到内部临时目录操作,完成后写回:
// 从SD卡读取DB到内部临时文件 val dbFile = rootDir.findFile("app_data.db") ?: return val tempDbPath = filesDir.path + "/temp_db.db" contentResolver.openInputStream(dbFile.uri)?.use { input -> FileOutputStream(tempDbPath).use { output -> input.copyTo(output) } } // 操作临时DB val db = SQLiteDatabase.openDatabase(tempDbPath, null, SQLiteDatabase.OPEN_READWRITE) db.execSQL("INSERT INTO users(name) VALUES('Charlie')") // ...其他DB操作 db.close() // 将修改后的DB写回SD卡 contentResolver.openOutputStream(dbFile.uri)?.use { output -> FileInputStream(tempDbPath).use { input -> input.copyTo(output) } } // 删除临时文件 File(tempDbPath).delete()
二、MANAGE_EXTERNAL_STORAGE权限的补充说明
如果你的应用属于文件管理器、备份工具等符合Google政策的场景,可申请该权限直接访问外置SD卡所有文件:
- 清单文件添加权限:
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" android:minSdkVersion="30" />
- 动态引导用户授权:
if (!Environment.isExternalStorageManager()) { val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION) intent.data = Uri.parse("package:$packageName") startActivity(intent) }
注意:普通应用使用该权限可能无法通过Google Play审核,需谨慎选择。
关于SAF文档混乱的问题
确实,SAF的官方文档在版本迭代中存在描述不一致、示例缺失的情况,建议优先聚焦API 30+的官方存储章节,同时结合不同品牌设备测试——部分厂商的SD卡挂载逻辑会有差异,实际测试能更快定位问题。
内容的提问来源于stack exchange,提问作者lupaulus
相关产品推荐
相关产品推荐

