Android对象存储进阶:SharedPreferences深度实践指南
2025.09.19 11:53浏览量:0简介:本文详细阐述在Android开发中如何将复杂对象安全、高效地存储到SharedPreferences中,通过序列化、Gson转换和封装工具类等方法,解决原生不支持对象存储的痛点,并提供代码示例和最佳实践。
Android对象存储进阶:SharedPreferences深度实践指南
在Android开发中,SharedPreferences
作为轻量级数据存储方案,广泛应用于用户偏好设置、应用状态管理等场景。然而,其原生设计仅支持基本数据类型(如String
、int
、boolean
等),面对复杂对象(如自定义类实例、集合等)的存储需求时,开发者常陷入困境。本文将系统探讨如何通过序列化、JSON转换等技术手段,实现对象到SharedPreferences
的安全存储,并提供可复用的解决方案。
一、SharedPreferences原生限制与对象存储需求
1.1 原生支持的局限性
SharedPreferences
通过Editor
类提供的putString()
、putInt()
等方法,仅支持以下基础类型:
String
int
/long
/float
/boolean
Set<String>
(需通过putStringSet()
存储)
对于自定义对象(如User
类、Config
配置类等),直接调用putObject()
会触发编译错误,因为该方法不存在。
1.2 对象存储的典型场景
- 用户会话数据:存储登录后的用户信息(含ID、昵称、头像URL等)
- 应用主题配置:保存包含颜色、字体等属性的主题对象
- 离线缓存数据:临时存储从网络获取的复杂结构化数据
二、对象存储的核心技术方案
2.1 序列化(Serializable)方案
原理:将对象转换为字节流,存储为String
或ByteArray
。
实现步骤:
对象实现
Serializable
接口:public class User implements Serializable {
private String name;
private int age;
// 必须声明serialVersionUID
private static final long serialVersionUID = 1L;
// 构造方法、getter/setter省略
}
序列化与反序列化工具类:
public class SerializationUtils {
// 对象转字节数组
public static byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
return baos.toByteArray();
}
// 字节数组转对象
public static Object deserialize(byte[] data) throws IOException, ClassNotFoundException {
ByteArrayInputStream bais = new ByteArrayInputStream(data);
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
}
}
存储到SharedPreferences:
```java
// 存储对象
User user = new User(“Alice”, 25);
byte[] userBytes = SerializationUtils.serialize(user);
String encoded = Base64.encodeToString(userBytes, Base64.DEFAULT);
preferences.edit().putString(“user_key”, encoded).apply();
// 读取对象
String encodedData = preferences.getString(“user_key”, null);
if (encodedData != null) {
byte[] userBytes = Base64.decode(encodedData, Base64.DEFAULT);
User user = (User) SerializationUtils.deserialize(userBytes);
}
**优缺点**:
- ✅ 无需依赖第三方库
- ❌ 性能较低(涉及I/O操作)
- ❌ 安全性风险(反序列化可能执行恶意代码)
### 2.2 JSON转换(Gson/Moshi)方案
**原理**:将对象转换为JSON字符串,利用`putString()`存储。
**实现步骤**:
1. **添加Gson依赖**:
```gradle
implementation 'com.google.code.gson:gson:2.10.1'
对象与JSON互转:
public class JsonUtils {
private static final Gson gson = new Gson();
public static String toJson(Object obj) {
return gson.toJson(obj);
}
public static <T> T fromJson(String json, Class<T> classOfT) {
return gson.fromJson(json, classOfT);
}
}
存储到SharedPreferences:
```java
// 存储对象
User user = new User(“Bob”, 30);
String userJson = JsonUtils.toJson(user);
preferences.edit().putString(“user_key”, userJson).apply();
// 读取对象
String json = preferences.getString(“user_key”, null);
if (json != null) {
User user = JsonUtils.fromJson(json, User.class);
}
**优缺点**:
- ✅ 性能优于序列化
- ✅ 可读性强(JSON格式)
- ✅ 类型安全(编译时检查)
- ❌ 需处理循环引用等复杂结构
### 2.3 封装工具类(推荐实践)
综合上述方案,封装一个通用工具类:
```java
public class SharedPrefsHelper {
private final SharedPreferences prefs;
private final Gson gson;
public SharedPrefsHelper(Context context, String prefName) {
this.prefs = context.getSharedPreferences(prefName, Context.MODE_PRIVATE);
this.gson = new Gson();
}
// 存储对象
public void putObject(String key, Object object) {
String json = gson.toJson(object);
prefs.edit().putString(key, json).apply();
}
// 获取对象
public <T> T getObject(String key, Class<T> classOfT) {
String json = prefs.getString(key, null);
return json == null ? null : gson.fromJson(json, classOfT);
}
// 其他基础类型方法省略...
}
使用示例:
// 初始化
SharedPrefsHelper prefsHelper = new SharedPrefsHelper(context, "my_app_prefs");
// 存储
User user = new User("Charlie", 28);
prefsHelper.putObject("current_user", user);
// 读取
User restoredUser = prefsHelper.getObject("current_user", User.class);
三、最佳实践与注意事项
3.1 性能优化
- 批量操作:使用
apply()
而非commit()
避免阻塞UI线程 - 缓存策略:对频繁读取的对象实施内存缓存
- 数据压缩:对大对象使用GZIP压缩后存储
3.2 安全性加固
敏感数据加密:存储前对JSON字符串进行AES加密
public class CryptoUtils {
private static final String ALGORITHM = "AES";
private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
public static String encrypt(String input, SecretKey key, IvParameterSpec iv)
throws Exception {
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] encrypted = cipher.doFinal(input.getBytes());
return Base64.encodeToString(encrypted, Base64.DEFAULT);
}
}
3.3 版本兼容性
- 迁移策略:当对象结构变更时,通过版本号字段实现数据迁移
public class AppConfig {
private int version;
private String theme;
// 版本1新增字段
private boolean isDarkMode;
// 版本2新增字段
private int fontSize;
}
3.4 测试验证
- 单元测试:验证序列化/反序列化的完整性
@Test
public void testUserSerialization() {
User original = new User("Test", 99);
String json = JsonUtils.toJson(original);
User restored = JsonUtils.fromJson(json, User.class);
assertEquals(original.getName(), restored.getName());
}
四、替代方案对比
方案 | 适用场景 | 存储大小 | 读写速度 | 复杂度 |
---|---|---|---|---|
SharedPreferences | 简单配置、少量数据 | 小 | 快 | 低 |
Room数据库 | 结构化数据、查询需求 | 大 | 中等 | 中 |
DataStore | 类型安全、异步操作 | 中等 | 快 | 高 |
决策建议:
- 对象≤10个字段且无需查询 →
SharedPreferences
- 对象包含列表/关系 → 考虑
Room
或DataStore
- 需要跨进程访问 → 使用
ContentProvider
五、总结与展望
通过JSON转换方案,开发者可以高效地将复杂对象存储到SharedPreferences
中,兼顾性能与可维护性。未来随着Android Jetpack的演进,DataStore
提供的类型安全API可能成为更优选择,但在当前版本中,本文介绍的方案仍具有极高的实用价值。建议开发者根据项目需求,在简单性与扩展性之间做出合理权衡。
发表评论
登录后可评论,请前往 登录 或 注册