12、数据存储之SharedPreferences:轻量级数据存储、读写操作、登录状态保存实战
做Android开发这些年,我遇到过不少新手问:“我就想存个用户设置,比如开关状态、登录标记,有必要上SQLite吗?”
我的回答永远是:杀鸡别用牛刀。这种场景,SharedPreferences就是最趁手的工具。
什么是SharedPreferences?
说白了,它就是一个轻量级的键值对存储方案。底层用XML文件存数据,放在/data/data/包名/shared_prefs/目录下。
适合存什么?
- 用户偏好设置(主题、字体大小)
- 登录状态标记
- 简单的计数器或标记位
- 应用配置开关
不适合存什么?
- 大量结构化数据(请用Room或SQLite)
- 敏感信息(密码、Token——除非加密)
- 文件或二进制数据
核心要点:SharedPreferences是Map结构,key为String,value支持:String、int、long、float、boolean、Set<String>。
基本读写操作
先看获取实例的方式。我个人习惯用getSharedPreferences(),因为可以指定文件名,方便模块化管理。
// 获取SharedPreferences实例
SharedPreferences sp = getSharedPreferences("user_config", Context.MODE_PRIVATE);
// 获取编辑器
SharedPreferences.Editor editor = sp.edit();
// 写入数据
editor.putString("username", "张三");
editor.putBoolean("is_login", true);
editor.putInt("age", 28);
editor.apply(); // 异步提交,推荐
// 读取数据
String username = sp.getString("username", "默认用户");
boolean isLogin = sp.getBoolean("is_login", false);
int age = sp.getInt("age", 0);
这里有个细节:apply()和commit()有什么区别?
apply():异步写入,无返回值,速度快,推荐在主线程使用。commit():同步写入,返回boolean表示成功与否,会阻塞主线程。
我在项目中遇到过因为用commit()导致界面卡顿的情况,后来全部换成了apply()。除非你需要在写入后立即判断结果,否则别用commit。
登录状态保存实战
这是SharedPreferences最经典的应用场景。我们来实现一个完整的登录状态管理。
public class LoginManager {
private static final String SP_NAME = "login_sp";
private static final String KEY_IS_LOGIN = "is_login";
private static final String KEY_TOKEN = "token";
private static final String KEY_USER_ID = "user_id";
private SharedPreferences sp;
public LoginManager(Context context) {
sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE);
}
// 保存登录状态
public void saveLoginState(String token, String userId) {
sp.edit()
.putBoolean(KEY_IS_LOGIN, true)
.putString(KEY_TOKEN, token)
.putString(KEY_USER_ID, userId)
.apply();
}
// 检查是否登录
public boolean isLogin() {
return sp.getBoolean(KEY_IS_LOGIN, false);
}
// 获取Token
public String getToken() {
return sp.getString(KEY_TOKEN, "");
}
// 清除登录状态(退出登录)
public void clearLoginState() {
sp.edit()
.putBoolean(KEY_IS_LOGIN, false)
.remove(KEY_TOKEN)
.remove(KEY_USER_ID)
.apply();
}
}
我的经验:退出登录时不要直接调用clear()清空整个SP文件。万一你在这个文件里还存了其他非登录相关的配置,就全没了。用remove()精准删除,更安全。
避坑指南
我曾经踩过一个坑:在多个进程同时读写同一个SharedPreferences文件,结果数据丢失了。
为什么会这样?因为SharedPreferences默认不是线程安全的,更不是进程安全的。
解决方案:
- 如果只是单进程,放心用,没问题。
- 如果多进程场景,可以改用
Context.MODE_MULTI_PROCESS(但已废弃,不推荐)。 - 更好的做法:使用
ContentProvider或MMKV(腾讯开源的高性能KV存储)。
警告:不要在SharedPreferences里存密码!明文存储太危险。如果非要存敏感信息,请使用EncryptedSharedPreferences(AndroidX Security库提供)。
知识体系结构图
下面这张图帮你理清SharedPreferences的核心脉络:
进阶技巧:监听数据变化
有时候我们需要在SharedPreferences数据变化时做响应。比如用户切换了主题,界面要立刻刷新。
SharedPreferences提供了registerOnSharedPreferenceChangeListener接口:
SharedPreferences.OnSharedPreferenceChangeListener listener =
new SharedPreferences.OnSharedPreferenceChangeListener() {
@Override
public void onSharedPreferenceChanged(SharedPreferences sp, String key) {
if ("theme_mode".equals(key)) {
// 切换主题
recreate();
}
}
};
sp.registerOnSharedPreferenceChangeListener(listener);
// 别忘了在不需要时取消注册
// sp.unregisterOnSharedPreferenceChangeListener(listener);
注意:这个回调是在主线程执行的。如果你的处理逻辑比较重,记得开子线程或使用Handler。
性能与最佳实践
SharedPreferences虽然轻量,但也不是无限制用的。我总结了几条经验:
- 单个SP文件不要太大——超过100个key就该考虑拆分文件了。
- 避免频繁写入——每次
apply()都会触发一次文件IO,高频写入会卡UI。 - 善用默认值——读取时给个合理的默认值,避免空指针。
- 模块化存储——登录信息放一个文件,设置放一个文件,互不干扰。
嗯,SharedPreferences就是这么个简单又实用的东西。别小看它,用好了能让你的应用轻快又稳定。下次遇到“存个小数据”的需求,别犹豫,就是它了。
公众号:蓝海资料掘金营,微信deep3321