1. 移动应用数据存储的两种基础方案
在移动应用开发中,数据持久化存储是每个开发者必须面对的基础问题。Android平台提供了多种数据存储方案,其中应用专属文件和SharedPreferences是最常用的两种轻量级解决方案。它们就像开发者的"瑞士军刀"——虽然不如数据库强大,但在特定场景下能快速解决问题。
应用专属文件(App-specific files)是存储在设备内部存储空间中的私有文件,只有应用本身可以访问。这就像给你的应用分配了一个上锁的私人储物柜,其他应用无法窥探其中的内容。这类文件适合存储非结构化的数据,比如用户生成的图片、下载的音频或自定义格式的日志文件。
SharedPreferences则是Android提供的键值对存储系统,专门用于保存简单的配置参数和用户偏好设置。想象它是一个迷你记事本,可以快速记录"字体大小=16"、"夜间模式=开启"这类零散信息。它的API设计极其简单,让开发者能用几行代码就实现数据的存储和读取。
这两种方案看似简单,但在实际项目中,我见过太多开发者因为误用它们而导致的性能问题和数据异常。比如有人用SharedPreferences存储上MB的JSON数据,导致界面卡顿;也有人把敏感信息明文存在应用专属文件中,引发安全漏洞。接下来我将结合实战经验,详细解析它们的正确使用姿势。
2. 应用专属文件的深度解析与实战
2.1 内部存储与外部存储的区别
Android的文件存储分为内部存储(Internal Storage)和外部存储(External Storage)两种物理位置。内部存储空间始终可用,且存储在这里的文件默认只能被你的应用访问。当用户卸载应用时,系统会自动删除这些文件。这就像酒店为每个客人提供的保险箱——退房时自动清空。
外部存储则分为两类:专有目录(App-specific directory)和公共目录(Public directory)。专有目录在Android 4.4及以上版本无需权限即可访问,卸载时也会自动清除。而公共目录需要运行时权限,存储的文件在应用卸载后仍会保留。
在代码中获取内部存储路径的方法:
java复制File internalDir = context.getFilesDir(); // /data/data/<package>/files
File cacheDir = context.getCacheDir(); // /data/data/<package>/cache
2.2 文件操作的最佳实践
创建和写入文件的标准做法:
java复制String filename = "user_data.dat";
String content = "Hello World";
try (FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE)) {
fos.write(content.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
这里有几个关键细节需要注意:
- 使用try-with-resources语法确保流自动关闭
- MODE_PRIVATE表示文件私有,其他选项如MODE_APPEND已废弃
- 不要在主线程执行文件IO操作
读取文件的正确姿势:
java复制try (FileInputStream fis = context.openFileInput(filename)) {
InputStreamReader isr = new InputStreamReader(fis);
BufferedReader br = new BufferedReader(isr);
StringBuilder sb = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
sb.append(line);
}
String fileContent = sb.toString();
} catch (IOException e) {
// 处理异常
}
2.3 缓存文件的管理策略
缓存目录(cacheDir)适合存储临时文件,但需要注意:
- 系统可能在存储空间不足时清除缓存
- 应定期手动清理过期缓存
- 缓存文件大小不应超过几十MB
清除缓存的标准方法:
java复制File cacheDir = context.getCacheDir();
File[] files = cacheDir.listFiles();
if (files != null) {
for (File file : files) {
file.delete();
}
}
3. SharedPreferences的进阶用法
3.1 基本使用与性能优化
获取SharedPreferences实例的正确方式:
java复制// 使用应用上下文,避免内存泄漏
SharedPreferences prefs = context.getApplicationContext()
.getSharedPreferences("user_prefs", Context.MODE_PRIVATE);
存储数据的标准方法:
java复制prefs.edit()
.putString("username", "john_doe")
.putInt("login_count", 5)
.putBoolean("dark_mode", true)
.apply(); // 或commit()
apply()与commit()的关键区别:
- apply()异步写入磁盘,无返回值但更高效
- commit()同步写入,返回boolean表示成功与否
- 在UI线程总是使用apply()
3.2 复杂数据的存储方案
虽然SharedPreferences设计用于简单数据,但通过技巧也能存储复杂对象:
存储JSON对象:
java复制JSONObject user = new JSONObject();
try {
user.put("name", "John");
user.put("age", 30);
prefs.edit().putString("user_json", user.toString()).apply();
} catch (JSONException e) {
e.printStackTrace();
}
读取时解析:
java复制String json = prefs.getString("user_json", "");
try {
JSONObject user = new JSONObject(json);
} catch (JSONException e) {
// 处理异常
}
3.3 监听偏好变化
注册变化监听器:
java复制SharedPreferences.OnSharedPreferenceChangeListener listener =
(sharedPreferences, key) -> {
if ("dark_mode".equals(key)) {
// 更新UI
}
};
prefs.registerOnSharedPreferenceChangeListener(listener);
// 记得在适当时候取消注册
prefs.unregisterOnSharedPreferenceChangeListener(listener);
4. 安全与性能的关键考量
4.1 数据加密的必要性
即使使用私有存储,也应加密敏感数据。Android提供了EncryptedSharedPreferences:
java复制String masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC);
EncryptedSharedPreferences encryptedPrefs = (EncryptedSharedPreferences)
EncryptedSharedPreferences.create(
"secret_prefs",
masterKeyAlias,
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);
4.2 大文件存储的替代方案
当需要存储超过1MB的数据时,应考虑其他方案:
- 对于结构化数据:使用Room数据库
- 对于媒体文件:使用外部存储
- 对于键值数据:考虑MMKV等高性能方案
4.3 多进程访问的陷阱
默认情况下,SharedPreferences不支持多进程同步。如果需要跨进程访问:
- 使用MODE_MULTI_PROCESS(已废弃,不推荐)
- 改用ContentProvider封装访问
- 使用跨进程通信机制如AIDL
5. 实际项目中的经验总结
在电商App的开发中,我们曾用SharedPreferences存储用户购物车数据。当商品数量超过200件时,出现了明显的UI卡顿。通过分析发现:
- 每次修改购物车都会触发全量写入
- JSON序列化/反序列化消耗CPU资源
- 频繁触发PreferenceChange监听器
解决方案是:
- 将购物车迁移到Room数据库
- 保留SharedPreferences只存储精简的元数据
- 使用DiffUtil智能更新UI
另一个案例是在社交App中,用户头像缓存文件未及时清理,导致部分用户的缓存目录膨胀到2GB以上。我们最终实现的解决方案包括:
- 基于LRU算法自动清理旧文件
- 为缓存文件设置最大存储限额(如200MB)
- 定期在应用启动时执行存储空间检查
