【Android】常见路径详解:权限与用途指南
Android系统在版本迭代中,既要兼顾旧版本,又要引入更安全的文件访问机制。在App中,往往需要针对不同的系统,不同的路径采取不同的权限,读取方式,进行文件操作。
一、Android 存储体系概览
Android 的存储系统可以分为两大类:
┌─────────────────────────────────────────────────────────┐
│ Android 存储体系 │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ 内部存储 │ │ 外部存储 │ │
│ │ (Internal) │ │ (External) │ │
│ └─────────────────┘ └─────────────────┘ │
│ │ │ │
│ ├─ 应用私有目录 ├─ 应用私有目录 │
│ │ /data/data/pkg │ /sdcard/Android │
│ │ │ │
│ └─ 系统保护目录 └─ 公共目录 │
│ /sdcard/DCIM等 │
│ │
└─────────────────────────────────────────────────────────┘
关键概念
- 内部存储:设备内置存储空间,其他应用无法访问
- 外部存储:可以是内置存储分区或 SD 卡,其他应用可能访问
- 分区存储(Scoped Storage):Android 10 引入的新存储机制
二、内部存储路径
2.1 应用私有目录(无需权限)
| 路径 | 获取方式 | 用途 | 清理策略 |
|---|---|---|---|
/data/data/<package_name>/ |
context.dataDir |
应用根目录 | 卸载时 |
/data/data/<package_name>/files/ |
context.filesDir |
持久化文件 | 卸载时 |
/data/data/<package_name>/cache/ |
context.cacheDir |
缓存文件 | 系统自动清理 |
/data/data/<package_name>/databases/ |
- | 数据库文件 | 卸载时 |
/data/data/<package_name>/shared_prefs/ |
- | SharedPreferences | 卸载时 |
/data/data/<package_name>/code_cache/ |
context.codeCacheDir |
代码缓存 | 系统自动清理 |
代码示例:
// 获取内部存储路径
val filesDir = context.filesDir // /data/data/com.example.app/files
val cacheDir = context.cacheDir // /data/data/com.example.app/cache
// 在内部存储创建文件
val file = File(filesDir, "my_file.txt")
file.writeText("Hello World")
// 缓存目录(系统可能自动清理)
val cacheFile = File(cacheDir, "temp_cache.tmp")
各目录详细说明:
| 目录 | 适用场景 | 空间限制 |
|---|---|---|
filesDir |
用户数据、配置文件、数据库等需要持久化保存的内容 | 设备内部存储配额 |
cacheDir |
临时数据、网络缓存、图片缓存等可以重新生成的内容 | 通常无明确限制,但大了会被清理 |
codeCacheDir |
编译后的代码、WebView 缓存等 | 通常无明确限制 |
shared_prefs |
SharedPreferences 配置文件 | 空间很小 |
databases |
SQLite 数据库文件 | 空间有限 |
选择建议:
// 1. 需要持久保存的数据(用户配置、数据库)→ filesDir
val userPrefsFile = File(context.filesDir, "user_data.json")
// 2. 临时缓存数据(网络请求缓存、缩略图)→ cacheDir
val thumbnailFile = File(context.cacheDir, "thumb_${id}.jpg")
// 系统存储紧张时会被自动删除
// 3. 需要数据库存储的结构化数据 → databases 目录
// 使用 Room 或直接操作 SQLite
// 4. 轻量级配置数据 → shared_prefs
val prefs = context.getSharedPreferences("config", Context.MODE_PRIVATE)
特点:
- ✅ 完全私密,其他应用无法访问
- ✅ 不需要任何权限
- ✅ 应用卸载时自动删除
- ⚠️ 空间有限,不宜存储大文件
- ⚠️
cacheDir可能会被系统自动清理,不能用于存储重要数据
2.2 系统保护目录
| 路径 | 说明 | 权限 |
|---|---|---|
/system/ |
系统分区(只读) | 🔒 需要 ROOT |
/vendor/ |
厂商分区 | 🔒 需要 ROOT |
/data/app/ |
应用安装目录 | 🔒 系统权限 |
/data/system/ |
系统数据 | 🔒 系统权限 |
三、外部存储 - 应用私有目录
3.1 应用专属目录(无需权限)
| 路径 | 获取方式 | 用途 | 清理策略 |
|---|---|---|---|
/sdcard/Android/data/<pkg>/files/ |
getExternalFilesDir() |
外部存储私有文件 | 卸载时 |
/sdcard/Android/data/<pkg>/cache/ |
externalCacheDir |
外部缓存 | 系统自动清理 |
*注:Android 10+ 使用分区存储,无需权限;Android 9 及以下可能需要存储权限
各目录详细说明:
| 目录 | 适用场景 | 空间限制 |
|---|---|---|
getExternalFilesDir() |
大文件、媒体文件、用户导出内容 | 使用外部存储空间,较大 |
externalCacheDir |
大型缓存(离线内容、临时大文件) | 使用外部存储空间 |
选择建议:
// 1. 大文件或需要分享的文件 → getExternalFilesDir()
// 适合:下载的文档、导出文件、拍照保存到本应用
val exportFile = context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)
val myPdf = File(exportFile, "export_${System.currentTimeMillis()}.pdf")
// 2. 大型临时缓存 → externalCacheDir
// 适合:离线地图数据、大量缩略图缓存
val offlineMap = File(context.externalCacheDir, "offline_map_${region}.dat")
// 优点:空间大,系统紧张时会自动清理,不影响内部存储
// 3. 媒体文件(图片、音频、视频)
val pictures = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
val cameraPhoto = File(pictures, "IMG_${System.currentTimeMillis()}.jpg")
内部存储 vs 外部存储 选择对比:
| 因素 | 内部存储 (filesDir) | 外部存储 (getExternalFilesDir) |
|---|---|---|
| 空间大小 | 较小(几百MB) | 较大(几GB) |
| 访问速度 | 稍快 | 稍慢 |
| 数据安全 | 更安全(其他应用无法访问) | 相对安全 |
| 卸载行为 | 应用卸载后删除 | 应用卸载后删除 |
| 系统清理 | 不会被自动清理 | cache 会被清理 |
| 推荐使用 | 小型数据、敏感数据 | 大型文件、媒体内容 |
代码示例:
// 获取外部存储私有目录
val externalFilesDir = context.getExternalFilesDir(null) // files/
val externalCacheDir = context.externalCacheDir // cache/
// 获取特定类型的目录
val musicDir = context.getExternalFilesDir(Environment.DIRECTORY_MUSIC)
val picturesDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
val moviesDir = context.getExternalFilesDir(Environment.DIRECTORY_MOVIES)
// 示例:保存图片到应用专属目录
val imageFile = File(picturesDir, "photo_${System.currentTimeMillis()}.jpg")
特点:
- ✅ Android 10+ 无需权限
- ✅ 不占用内部存储空间
- ✅ 应用卸载时自动删除
- ⚠️ 其他应用在 Android 10 前可以访问(需要权限)
3.2 常用目录类型常量
// Environment 常量
Environment.DIRECTORY_MUSIC // Music
Environment.DIRECTORY_PODCASTS // Podcasts
Environment.DIRECTORY_RINGTONES // Ringtones
Environment.DIRECTORY_ALARMS // Alarms
Environment.DIRECTORY_NOTIFICATIONS // Notifications
Environment.DIRECTORY_PICTURES // Pictures
Environment.DIRECTORY_MOVIES // Movies
Environment.DIRECTORY_DOWNLOADS // Download
Environment.DIRECTORY_DCIM // DCIM
Environment.DIRECTORY_DOCUMENTS // Documents
四、外部存储 - 公共目录
4.1 媒体公共目录(需权限或 MediaStore)
| 路径 | 用途 | Android 9- | Android 10+ |
|---|---|---|---|
/sdcard/DCIM/ |
相机照片 | WRITE_EXTERNAL_STORAGE |
MediaStore API |
/sdcard/Pictures/ |
图片 | WRITE_EXTERNAL_STORAGE |
MediaStore API |
/sdcard/Music/ |
音乐 | WRITE_EXTERNAL_STORAGE |
MediaStore API |
/sdcard/Movies/ |
视频 | WRITE_EXTERNAL_STORAGE |
MediaStore API |
/sdcard/Downloads/ |
下载文件 | WRITE_EXTERNAL_STORAGE |
MediaStore API |
/sdcard/Documents/ |
文档 | WRITE_EXTERNAL_STORAGE |
MediaStore API |
4.2 Android 10+ 公共目录访问方式
// ❌ Android 10+ 直接路径访问(已废弃)
// val file = File("/sdcard/DCIM/photo.jpg") // 不推荐
// ✅ 使用 MediaStore API 访问
val contentValues = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, "photo_${System.currentTimeMillis()}.jpg")
put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
put(MediaStore.Images.Media.RELATIVE_PATH, "${Environment.DIRECTORY_DCIM}/MyApp")
}
val uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
uri?.let {
contentResolver.openOutputStream(it).use { outputStream ->
// 写入图片数据
}
}
4.3 权限对照表
| API 级别 | 公共目录访问方式 | 所需权限 |
|---|---|---|
| Android 9 (API 28) 及以下 | 直接路径访问 | READ/WRITE_EXTERNAL_STORAGE |
| Android 10 (API 29) | MediaStore API | 无需权限(媒体文件) |
| Android 11 (API 30)+ | MediaStore + 分区存储 | MANAGE_EXTERNAL_STORAGE(全部访问) |
<!-- AndroidManifest.xml 权限声明 -->
<!-- Android 9 及以下:读写外部存储 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- Android 10+:访问媒体文件(可选) -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<!-- Android 11+:管理所有文件(特殊权限) -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
五、外部存储 - 根目录
5.1 根目录访问(需特殊权限)
| 路径 | 获取方式 | 权限要求 |
|---|---|---|
/sdcard/ |
Environment.getExternalStorageDirectory() |
READ/WRITE_EXTERNAL_STORAGE |
Android 版本差异:
// Android 9 及以下:可直接访问
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
val sdcard = Environment.getExternalStorageDirectory()
// 需要申请运行时权限
}
// Android 10+:访问受限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// 使用 MediaStore 或 SAF (Storage Access Framework)
}
六、存储访问框架(SAF)
6.1 SAF 简介
Storage Access Framework 是 Android 4.4 引入的文件访问方式,允许用户选择文件/目录:
// 选择文件
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "image/*"
}
startActivityForResult(intent, REQUEST_CODE_OPEN)
// 创建文件
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "text/plain"
putExtra(Intent.EXTRA_TITLE, "new_file.txt")
}
startActivityForResult(intent, REQUEST_CODE_CREATE)
// 选择目录(Android 5.0+)
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
startActivityForResult(intent, REQUEST_CODE_CHOOSE_DIR)
优点:
- ✅ 无需存储权限
- ✅ 用户完全控制访问范围
- ✅ 跨应用共享文件
七、路径获取方法汇总
7.1 内部存储路径
// 应用私有目录(内部存储)
context.filesDir // /data/data/<pkg>/files
context.cacheDir // /data/data/<pkg>/cache
context.codeCacheDir // /data/data/<pkg>/code_cache
context.dataDir // /data/data/<pkg>
// 特定类型目录
context.getFileStreamPath("filename") // /data/data/<pkg>/files/filename
7.2 外部存储路径
// 检查外部存储是否可用
fun isExternalStorageAvailable(): Boolean {
return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
}
// 应用专属目录(外部存储)
context.getExternalFilesDir(null) // /sdcard/Android/data/<pkg>/files
context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) // .../files/Pictures
context.externalCacheDir // /sdcard/Android/data/<pkg>/cache
// 外部存储根目录(已废弃)
Environment.getExternalStorageDirectory() // /sdcard(Android 10+ 受限)
// 公共目录路径(Android 9 及以下)
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)
八、权限申请完整示例
8.1 Android 9 及以下
// AndroidManifest.xml
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
// 运行时权限申请
private fun requestStoragePermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
this,
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
),
REQUEST_CODE_STORAGE
)
}
}
8.2 Android 10 (API 29)
<!-- AndroidManifest.xml -->
<application
android:requestLegacyExternalStorage="true"
...>
// 使用 MediaStore API,无需权限
val cursor = contentResolver.query(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
arrayOf(MediaStore.Images.Media._ID, MediaStore.Images.Media.DISPLAY_NAME),
null, null, null
)
8.3 Android 11+ (API 30+)
// 申请管理所有文件的权限(特殊情况)
private fun requestAllFilesAccess() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (!Environment.isExternalStorageManager()) {
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION).apply {
data = Uri.parse("package:$packageName")
}
startActivity(intent)
}
}
}
九、最佳实践
9.1 选择正确的存储位置
┌─────────────────────────────────────────────────────────────┐
│ 存储位置选择指南 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 需要存储什么? │
│ │ │
│ ├─── 应用私有数据 ──> 内部存储 filesDir │
│ │ ✅ 无需权限 │
│ │ ✅ 完全私密 │
│ │ │
│ ├─── 临时缓存 ──> cacheDir 或 externalCacheDir │
│ │ ✅ 无需权限 │
│ │ ⚠️ 系统可能自动清理 │
│ │ │
│ ├─── 用户文件(图片、音乐等) │
│ │ │ │
│ │ ├─── 其他应用需要访问 ──> MediaStore │
│ │ │ (Android 10+) │
│ │ │ │
│ │ └─── 仅本应用使用 ──> externalFilesDir │
│ │ ✅ 无需权限 │
│ │ │
│ └─── 用户手动选择的文件 ──> SAF │
│ ✅ 无需权限 │
│ ✅ 用户控制 │
│ │
└─────────────────────────────────────────────────────────────┘
9.2 版本适配建议
// 推荐的文件存储工具类
object StorageUtils {
/**
* 获取合适的文件存储目录
*/
fun getAppropriateStorageDir(context: Context, fileName: String): File {
// 优先使用外部存储(空间更大)
val externalDir = context.getExternalFilesDir(null)
return if (externalDir != null && isExternalStorageAvailable()) {
File(externalDir, fileName)
} else {
// 外部存储不可用时,回退到内部存储
File(context.filesDir, fileName)
}
}
/**
* 检查外部存储是否可用
*/
private fun isExternalStorageAvailable(): Boolean {
return Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
}
/**
* 获取缓存目录
*/
fun getCacheDir(context: Context): File {
// 优先外部缓存(空间更大,不影响应用数据大小)
return context.externalCacheDir ?: context.cacheDir
}
}
十、常见问题与解决方案
Q1: 为什么 Android 10+ 无法访问 /sdcard/ 根目录?
A: Android 10 引入分区存储(Scoped Storage),限制应用对外部存储的访问。解决方案:
- 使用应用专属目录:
context.getExternalFilesDir() - 使用 MediaStore API 访问媒体文件
- 使用 SAF 让用户选择文件
Q2: 如何判断应该使用内部存储还是外部存储?
A:
- 内部存储:私密数据、配置文件、小型数据库
- 外部存储:用户文件、媒体文件、大文件缓存
Q3: 应用卸载后文件还在吗?
| 存储位置 | 卸载后 | 说明 |
|---|---|---|
内部存储 (filesDir) |
❌ 删除 | 系统自动清理 |
外部应用目录 (externalFilesDir) |
❌ 删除 | 系统自动清理 |
| 公共目录 (MediaStore 写入) | ✅ 保留 | 用户文件保留 |
| SAF 选择的目录 | ✅ 保留 | 用户完全控制 |
Q4: 如何兼容不同 Android 版本?
fun saveImage(context: Context, bitmap: Bitmap, fileName: String): Uri? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Android 10+: 使用 MediaStore
saveImageWithMediaStore(context, bitmap, fileName)
} else {
// Android 9 及以下:直接写入文件
saveImageDirectly(context, bitmap, fileName)
}
}

浙公网安备 33010602011771号