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

推荐订阅源

人人都是产品经理
人人都是产品经理
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
P
Privacy International News Feed
Simon Willison's Weblog
Simon Willison's Weblog
I
Intezer
Spread Privacy
Spread Privacy
The Hacker News
The Hacker News
P
Palo Alto Networks Blog
TaoSecurity Blog
TaoSecurity Blog
S
Secure Thoughts
Google Online Security Blog
Google Online Security Blog
H
Heimdal Security Blog
N
News | PayPal Newsroom
Attack and Defense Labs
Attack and Defense Labs
Recent Commits to openclaw:main
Recent Commits to openclaw:main
博客园 - 【当耐特】
Webroot Blog
Webroot Blog
小众软件
小众软件
Help Net Security
Help Net Security
D
Darknet – Hacking Tools, Hacker News & Cyber Security
N
News and Events Feed by Topic
Hacker News - Newest:
Hacker News - Newest: "LLM"
PCI Perspectives
PCI Perspectives
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The Cloudflare Blog
Cloudbric
Cloudbric
AI
AI
WordPress大学
WordPress大学
博客园 - 聂微东
Jina AI
Jina AI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 三生石上(FineUI控件)
Hacker News: Ask HN
Hacker News: Ask HN
H
Hacker News: Front Page
博客园 - Franky
V
V2EX
Schneier on Security
Schneier on Security
G
GRAHAM CLULEY
S
SegmentFault 最新的问题
有赞技术团队
有赞技术团队
H
Help Net Security
量子位
S
Security @ Cisco Blogs
大猫的无限游戏
大猫的无限游戏
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Recorded Future
Recorded Future
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
J
Java Code Geeks
C
Cisco Blogs
S
Security Affairs

博客园 - 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(不推荐)

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