惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
L
LangChain Blog
WordPress大学
WordPress大学
MyScale Blog
MyScale Blog
The Cloudflare Blog
J
Java Code Geeks
Google DeepMind News
Google DeepMind News
Recent Announcements
Recent Announcements
Microsoft Azure Blog
Microsoft Azure Blog
Y
Y Combinator Blog
有赞技术团队
有赞技术团队
Last Week in AI
Last Week in AI
酷 壳 – CoolShell
酷 壳 – CoolShell
Martin Fowler
Martin Fowler
小众软件
小众软件
量子位
月光博客
月光博客
P
Proofpoint News Feed
IT之家
IT之家
腾讯CDC
博客园 - 三生石上(FineUI控件)
博客园 - 司徒正美
雷峰网
雷峰网
V
Visual Studio Blog

博客园 - y丶innocence

临时111 RecyclerView 数据多时无法滑动:ConstraintLayout 约束高度修复笔记 Android Kotlin OkHttp3 WebSocket 长连接与 Gson 数据解析系统笔记 Android + Kotlin + OkHttp WebSocket 相关概念与使用流程笔记(TLS/证书 + 鉴权/会话) AI对话导出markdown格式流程 代理转发 分享文件 面向全球的app的excel导出和kotlin IO原理 java&kotlin listener 0.7 动画 0.4 View 工作流程 0.3 view 滑动冲突 13. Jetpack 0. 安卓开发艺术探索参考资料 12. Material Design 7. 持久化技术 5. Fragment java 基础 4. UI 开发 3. Activity 2.2 Kotlin 面向对象 2.3 Kotlin高级 2.1 Kotlin基础 1. Android简介 [OpenJudge] 反正切函数的应用 (枚举)(数学) [OpenJudge] 摘花生 (模拟)
安卓导出笔记(未整理)
y丶innocence · 2026-04-09 · via 博客园 - y丶innocence

以下是对 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(不推荐)

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