文件存储:内部存储与外部存储
背景
前面聊过的 SharedPreferences 和 Room 擅长管理键值对与结构化数据。但实际开发中还有一类需求它们搞不定:存储图片、日志文件、下载的文档、缓存的 JSON 数据等------这些场景就需要用到 Android 的文件存储。
Android 文件存储分为两大块:内部存储(Internal Storage)和外部存储(External Storage)。别被名字误导------它们的区别不只是物理位置,权限要求、生命周期、安全隔离才是关键。选错存储位置,轻则功能不生效,重则数据泄露或丢失。
核心概念
内部存储
内部存储是每个应用私有的沙盒目录,位于 /data/data/<包名>/files/。特点:
- 默认私有:其他应用无法直接访问,用户也看不到。
- 随应用卸载删除:卸载后数据一并清空。
- 无需运行时权限:从 API 1 就在用,任何版本都没加过权限限制。
- 空间有限:和系统分区共享,不适合存大文件。
典型用途:敏感配置、数据库备份、私密密钥等。获取目录的常用 API:
kotlin
context.filesDir // /data/data/<包名>/files
context.cacheDir // /data/data/<包名>/cache(系统可能自动清理)
context.getDir("custom", MODE_PRIVATE) // /data/data/<包名>/app_custom
外部存储
Android 10(API 29)引入**分区存储(Scoped Storage)**后,外部存储分两层理解:
- 应用专属外部目录 :
getExternalFilesDir()返回/sdcard/Android/data/<包名>/files/,同样是私有、无需权限、随卸载删除。 - 共享存储:通过 MediaStore API 访问公共目录(Pictures、Downloads、Music),需要声明权限。
重点记忆:filesDir(内部)和 getExternalFilesDir()(应用专属外部)都不需要运行时权限,都是应用私有。区别在于后者可能位于可插拔存储设备,空间通常更大。
代码实战(Kotlin)
封装一个文件管理工具类,覆盖写入、读取、删除、缓存清理:
kotlin
import android.content.Context
import java.io.File
object FileHelper {
// 写入文本到内部存储
fun writeToInternal(context: Context, filename: String, content: String) {
val file = File(context.filesDir, filename)
file.writeText(content) // Kotlin 扩展,默认 UTF-8
}
// 从内部存储读取文本
fun readFromInternal(context: Context, filename: String): String? {
val file = File(context.filesDir, filename)
return if (file.exists()) file.readText() else null
}
// 写入到应用专属外部存储
fun writeToExternalAppDir(context: Context, filename: String, content: String) {
val dir = context.getExternalFilesDir(null)
dir?.let {
if (!it.exists()) it.mkdirs()
File(it, filename).writeText(content)
}
}
// 按类型获取外部缓存子目录(如图片)
fun getImageCacheDir(context: Context): File? {
return context.externalCacheDir?.let {
val imgDir = File(it, "images")
if (!imgDir.exists()) imgDir.mkdirs()
imgDir
}
}
// 删除单个文件
fun deleteFile(context: Context, filename: String): Boolean {
return File(context.filesDir, filename).delete()
}
// 计算并清理缓存
fun clearCache(context: Context): Long {
var totalSize = 0L
context.cacheDir.walkTopDown().forEach { file ->
if (file.isFile) {
totalSize += file.length()
file.delete()
}
}
return totalSize // 返回清理掉的总字节数
}
}
Activity 调用示例:
kotlin
class FileDemoActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_file_demo)
// 写入配置到内部存储
FileHelper.writeToInternal(this, "user_config.txt", "darkMode=true")
// 写入日志到外部专属目录
FileHelper.writeToExternalAppDir(this, "log.txt",
"app started at ${System.currentTimeMillis()}")
// 读取
val config = FileHelper.readFromInternal(this, "user_config.txt")
Log.d("FileDemo", "读取配置: $config")
// 清理缓存
val freed = FileHelper.clearCache(this)
Log.d("FileDemo", "清理缓存: $freed bytes")
}
}
避坑指南
坑一:混淆"外部存储"和"SD 卡"。 getExternalFilesDir() 通常指向机身存储的模拟 SD 卡分区,不一定是物理 SD 卡。不要把路径写死为 /sdcard/。
坑二:直接用文件路径字符串拼接。 永远用 context.filesDir、context.cacheDir、getExternalFilesDir() 等 API 获取路径,兼容不同厂商和 Android 版本。
坑三:不检查外部存储可用性。 外部存储可能被卸载或只读:
kotlin
val isAvailable = Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
坑四:大文件放内部存储。 图片、视频、日志等大文件应放到外部存储,内部存储空间有限且会影响系统性能。
坑五:忽略 Android 10+ 分区存储。 targetSdkVersion >= 29 时,访问共享目录必须用 MediaStore 或 SAF(Storage Access Framework),传统文件路径操作会失效。
坑六:忘记清理缓存。 cacheDir 和 externalCacheDir 系统可能自动清理,但不要依赖这一点------在合适时机主动调用 clearCache() 是良好习惯。
总结
Android 文件存储的选择逻辑很简单:私密小文件放内部(filesDir),大文件放应用专属外部目录(getExternalFilesDir),共享媒体用 MediaStore。 内部存储和专属外部目录都无需权限、都是私有;缓存目录(cacheDir / externalCacheDir)适合临时文件,系统可能自动回收。
把文件存储和前面学过的 SharedPreferences、Room 放在一起看,Android 数据持久化体系就完整了:键值对用 SharedPreferences / DataStore,结构化数据用 Room,二进制和大文件用文件存储。 根据数据特征选对存储方式,比炫技代码重要得多。