【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)
    }
}

参考资源

posted @ 2026-03-27 11:17  知码匠  阅读(317)  评论(0)    收藏  举报