1 2 3 4

安卓导出笔记(未整理)

以下是对 Android 导出权限与存储目录 的系统性总结笔记,涵盖权限要求、适用场景及代码示例。


一、Android 存储目录分类

  1. 应用私有目录(无需权限)

目录 路径示例 特点
内部存储 /data/data/包名/files 空间有限,其他应用不可见,卸载时删除
内部缓存 /data/data/包名/cache 同上,系统可能自动清理
外部私有目录 /storage/emulated/0/Android/data/包名/files 空间较大,无需权限,卸载时删除
外部私有缓存 /storage/emulated/0/Android/data/包名/cache 同上,系统可能自动清理

获取方式:

// 内部存储
context.filesDir
context.cacheDir

// 外部私有目录(推荐用于导出文件)
context.getExternalFilesDir(null)           // 根目录
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS) // 分类子目录(仅逻辑分类)

权限要求:无(Android 4.4+)


  1. 公共存储目录(需要权限或特殊 API)

目录 路径示例 访问方式
Downloads /storage/emulated/0/Download MediaStore API 或 SAF
Documents /storage/emulated/0/Documents MediaStore API 或 SAF
DCIM/Camera /storage/emulated/0/DCIM MediaStore API
Pictures /storage/emulated/0/Pictures MediaStore API
外部存储根目录 /storage/emulated/0 旧版用 WRITE_EXTERNAL_STORAGE(已废弃)

权限要求:

· Android 10 及以下:需 WRITE_EXTERNAL_STORAGE 权限(动态申请)
· Android 11+:无法直接使用文件路径写入,必须通过 MediaStore 或 SAF


二、权限演进历史

Android 版本 特性 说明
4.4 (API 19) 引入外部私有目录免权限 getExternalFilesDir() 无需任何权限
6.0 (API 23) 运行时权限 WRITE_EXTERNAL_STORAGE 需动态申请
10 (API 29) 分区存储(Scoped Storage) 应用只能访问自身私有目录和 MediaStore 中的公共文件
11 (API 30) 强制分区存储 即使有权限也不能直接访问公共目录的文件路径
13 (API 33) 细化媒体权限 新增 READ_MEDIA_IMAGES/AUDIO/VIDEO,READ_EXTERNAL_STORAGE 弱化


三、导出文件的推荐方案

方案 1:写入外部私有目录 + 分享(零权限,推荐)

// 1. 写入
val file = File(context.getExternalFilesDir(null), "export.xlsx")
file.writeBytes(data)

// 2. 分享
val uri = FileProvider.getUriForFile(context, "${context.packageName}.fileprovider", file)
val intent = Intent(Intent.ACTION_SEND).apply {
    type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
    putExtra(Intent.EXTRA_STREAM, uri)
    addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
context.startActivity(Intent.createChooser(intent, "分享文件"))

需要配置 FileProvider(res/xml/file_paths.xml):

<paths>
    <external-files-path name="exports" path="." />
</paths>

方案 2:通过 MediaStore 保存到 Downloads(Android 10+)

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    val resolver = context.contentResolver
    val values = ContentValues().apply {
        put(MediaStore.MediaColumns.DISPLAY_NAME, "export.xlsx")
        put(MediaStore.MediaColumns.MIME_TYPE, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
        put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
    }
    val uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values)
    uri?.let {
        resolver.openOutputStream(it)?.use { outputStream ->
            outputStream.write(data)
        }
    }
}

权限要求:无需 WRITE_EXTERNAL_STORAGE,但会弹出系统授权框(仅首次)

方案 3:旧版直接写公共目录(仅支持 Android 10 以下)

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
    // 需先动态申请 WRITE_EXTERNAL_STORAGE
    val file = File(Environment.getExternalStorageDirectory(), "Download/export.xlsx")
    file.writeBytes(data)
}

四、权限检查清单

操作 权限 备注
读写 getExternalFilesDir() 无 推荐
读写 context.filesDir 无 内部存储
使用 MediaStore 写入公共目录 无危险权限 需 ContentValues 配置
直接读写公共目录路径(Android 10 以下) WRITE_EXTERNAL_STORAGE 需动态申请
直接读写公共目录路径(Android 11+) 不可能 系统禁止,只能用 MediaStore/SAF


五、最佳实践总结

  1. 导出文件优先使用 getExternalFilesDir(null):零权限、简单可靠。
  2. 分享文件时用 FileProvider:不要暴露真实路径给其他应用。
  3. 如需保存到用户可见的 Downloads:使用 MediaStore API(Android 10+ 通用,无需危险权限)。
  4. 不要申请 WRITE_EXTERNAL_STORAGE:除非你需要兼容 Android 10 以下的旧设备且必须写公共目录,否则完全没必要。
  5. CSV 和 XLSX 一样处理:文件类型不影响权限逻辑。

六、快速决策流程图

是否需要用户直接访问文件(如用文件管理器打开)?
├─ 不需要 → 写入 getExternalFilesDir() + 用 FileProvider 分享给其他 App
│           ✅ 无需任何权限
│
└─ 需要(如保存到 Downloads)
    ├─ 最低 API >= 29(Android 10+)→ 使用 MediaStore
    │   ✅ 无需危险权限,仅需系统授权
    │
    └─ 需支持 API <= 28(Android 9 及以下)
        ├─ 放弃直接存 Downloads,改用分享方案
        └─ 或动态申请 WRITE_EXTERNAL_STORAGE(不推荐)

这套笔记涵盖了从权限到目录到代码的全链路,以后遇到导出需求可以直接参考。

posted @ 2026-04-09 10:40  y丶innocence  阅读(72)  评论(0)    收藏  举报