跳到主要内容

文件存储

问题

Android 文件存储有哪些方式?Scoped Storage 带来了什么变化?

答案

存储区域分类

存储区域路径权限卸载清除适用
内部存储/data/data/<pkg>/files/无需权限配置、缓存
内部缓存/data/data/<pkg>/cache/无需权限临时文件
外部应用专属/sdcard/Android/data/<pkg>/无需权限大文件
共享存储/sdcard/Pictures/需权限媒体文件

Scoped Storage(分区存储)

Android 10 引入、Android 11 强制执行。限制 App 直接访问外部共享存储:

使用 MediaStore 保存图片

suspend fun saveImage(context: Context, bitmap: Bitmap): Uri? {
return withContext(Dispatchers.IO) {
val contentValues = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, "photo_${System.currentTimeMillis()}.jpg")
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
put(MediaStore.Images.Media.IS_PENDING, 1)
}
}

val uri = context.contentResolver.insert(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues
) ?: return@withContext null

context.contentResolver.openOutputStream(uri)?.use { stream ->
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, stream)
}

// 标记写入完成
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
contentValues.clear()
contentValues.put(MediaStore.Images.Media.IS_PENDING, 0)
context.contentResolver.update(uri, contentValues, null, null)
}

uri
}
}

SAF 文件选择

// 注册 ActivityResultContract
val pickFile = registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
uri?.let { readFileContent(it) }
}

// 打开文件选择器
pickFile.launch(arrayOf("application/pdf", "text/plain"))

private fun readFileContent(uri: Uri) {
// 持久化 URI 权限(跨生命周期)
contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
contentResolver.openInputStream(uri)?.use { stream ->
// 读取文件内容
}
}
requestLegacyExternalStorage

android:requestLegacyExternalStorage="true" 仅在 targetSdk 29 时有效。targetSdk 30+ 必须使用 Scoped Storage API。不要依赖此属性。


常见面试问题

Q1: 内部存储和外部存储的区别?

答案

  • 内部存储:App 私有,其他应用无法访问,无需申请权限,卸载时自动清除。适合敏感数据。
  • 外部应用专属存储:同样无需权限且卸载时清除,但在物理上位于外部存储(SD 卡或内置存储公共区域),空间更大。适合大文件如下载的视频。
  • 外部共享存储:所有应用可通过 MediaStore 访问的公共区域,卸载后文件保留。

Q2: FileProvider 是什么?什么时候需要用?

答案

Android 7.0+ 禁止通过 file:// URI 跨应用共享文件,必须使用 FileProvider 生成 content:// URI。常见场景:调用系统相机拍照后获取照片、安装 APK、分享文件给其他应用。

val uri = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file)
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, "image/jpeg")
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}

相关链接