279 lines
5.9 KiB
Markdown
279 lines
5.9 KiB
Markdown
|
|
# SharedPreferences
|
|||
|
|
|
|||
|
|
## 目录
|
|||
|
|
- [SharedPreferences原理](#sharedpreferences原理)
|
|||
|
|
- [SharedPreferences使用](#sharedpreferences使用)
|
|||
|
|
- [SharedPreferences性能](#sharedpreferences性能)
|
|||
|
|
- [SharedPreferences线程安全](#sharedpreferences线程安全)
|
|||
|
|
- [SharedPreferences替代方案](#sharedpreferences替代方案)
|
|||
|
|
- [SharedPreferences最佳实践](#sharedpreferences最佳实践)
|
|||
|
|
- [面试常见问题](#面试常见问题)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## SharedPreferences原理
|
|||
|
|
|
|||
|
|
### 存储位置
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// SharedPreferences 存储在 XML 文件中
|
|||
|
|
// 路径:/data/data/<package_name>/shared_prefs/<name>.xml
|
|||
|
|
|
|||
|
|
// 文件内容示例
|
|||
|
|
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
|
|||
|
|
<map>
|
|||
|
|
<string name="username">John</string>
|
|||
|
|
<int name="age" value="25" />
|
|||
|
|
<boolean name="isVip" value="true" />
|
|||
|
|
</map>
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 存储机制
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// SharedPreferences 使用内存缓存 + 文件存储
|
|||
|
|
// 1. 首次读取:从文件加载到内存
|
|||
|
|
// 2. 后续读取:从内存读取(快速)
|
|||
|
|
// 3. 写入:先写入内存,异步写入文件
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## SharedPreferences使用
|
|||
|
|
|
|||
|
|
### 基本使用
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// 获取 SharedPreferences
|
|||
|
|
SharedPreferences prefs = getSharedPreferences("my_prefs", Context.MODE_PRIVATE);
|
|||
|
|
|
|||
|
|
// 写入数据
|
|||
|
|
SharedPreferences.Editor editor = prefs.edit();
|
|||
|
|
editor.putString("username", "John");
|
|||
|
|
editor.putInt("age", 25);
|
|||
|
|
editor.putBoolean("isVip", true);
|
|||
|
|
editor.apply(); // 异步提交
|
|||
|
|
// 或
|
|||
|
|
editor.commit(); // 同步提交
|
|||
|
|
|
|||
|
|
// 读取数据
|
|||
|
|
String username = prefs.getString("username", "default");
|
|||
|
|
int age = prefs.getInt("age", 0);
|
|||
|
|
boolean isVip = prefs.getBoolean("isVip", false);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### apply vs commit
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// apply():异步提交
|
|||
|
|
editor.apply();
|
|||
|
|
// 优点:不阻塞主线程
|
|||
|
|
// 缺点:无法知道是否成功
|
|||
|
|
|
|||
|
|
// commit():同步提交
|
|||
|
|
boolean success = editor.commit();
|
|||
|
|
// 优点:可以知道是否成功
|
|||
|
|
// 缺点:可能阻塞主线程
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 监听数据变化
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// 注册监听器
|
|||
|
|
SharedPreferences.OnSharedPreferenceChangeListener listener =
|
|||
|
|
new SharedPreferences.OnSharedPreferenceChangeListener() {
|
|||
|
|
@Override
|
|||
|
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
|||
|
|
if ("username".equals(key)) {
|
|||
|
|
String username = sharedPreferences.getString(key, "");
|
|||
|
|
// 处理变化
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
prefs.registerOnSharedPreferenceChangeListener(listener);
|
|||
|
|
|
|||
|
|
// 注销监听器
|
|||
|
|
prefs.unregisterOnSharedPreferenceChangeListener(listener);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## SharedPreferences性能
|
|||
|
|
|
|||
|
|
### 性能问题
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// ❌ 问题:频繁读写
|
|||
|
|
for (int i = 0; i < 1000; i++) {
|
|||
|
|
SharedPreferences prefs = getSharedPreferences("prefs", Context.MODE_PRIVATE);
|
|||
|
|
prefs.edit().putString("key" + i, "value" + i).apply();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ✅ 解决:批量写入
|
|||
|
|
SharedPreferences prefs = getSharedPreferences("prefs", Context.MODE_PRIVATE);
|
|||
|
|
SharedPreferences.Editor editor = prefs.edit();
|
|||
|
|
for (int i = 0; i < 1000; i++) {
|
|||
|
|
editor.putString("key" + i, "value" + i);
|
|||
|
|
}
|
|||
|
|
editor.apply(); // 一次性提交
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 性能优化
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// 1. 使用 apply() 而不是 commit()
|
|||
|
|
editor.apply();
|
|||
|
|
|
|||
|
|
// 2. 批量写入
|
|||
|
|
editor.putString("key1", "value1");
|
|||
|
|
editor.putString("key2", "value2");
|
|||
|
|
editor.apply();
|
|||
|
|
|
|||
|
|
// 3. 避免频繁读取
|
|||
|
|
// 缓存到内存变量
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## SharedPreferences线程安全
|
|||
|
|
|
|||
|
|
### 线程安全说明
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// SharedPreferences 是线程安全的
|
|||
|
|
// 多个线程可以同时读取
|
|||
|
|
// 写入操作会加锁
|
|||
|
|
|
|||
|
|
// 读取:线程安全
|
|||
|
|
String value = prefs.getString("key", "default");
|
|||
|
|
|
|||
|
|
// 写入:线程安全
|
|||
|
|
prefs.edit().putString("key", "value").apply();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 并发写入
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// 多个线程同时写入
|
|||
|
|
// SharedPreferences 内部会加锁,保证线程安全
|
|||
|
|
// 但可能导致性能问题
|
|||
|
|
|
|||
|
|
// 建议:避免多线程频繁写入
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## SharedPreferences替代方案
|
|||
|
|
|
|||
|
|
### 1. MMKV
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// MMKV:高性能键值存储
|
|||
|
|
MMKV mmkv = MMKV.defaultMMKV();
|
|||
|
|
mmkv.encode("username", "John");
|
|||
|
|
String username = mmkv.decodeString("username");
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. DataStore
|
|||
|
|
|
|||
|
|
```kotlin
|
|||
|
|
// DataStore:Jetpack 组件
|
|||
|
|
val dataStore: DataStore<Preferences> = context.createDataStore(name = "settings")
|
|||
|
|
|
|||
|
|
// 写入
|
|||
|
|
suspend fun saveUsername(username: String) {
|
|||
|
|
dataStore.edit { preferences ->
|
|||
|
|
preferences[stringPreferencesKey("username")] = username
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 读取
|
|||
|
|
val usernameFlow: Flow<String> = dataStore.data
|
|||
|
|
.map { preferences ->
|
|||
|
|
preferences[stringPreferencesKey("username")] ?: ""
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. Room
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// Room:适合复杂数据
|
|||
|
|
@Database(entities = {User.class}, version = 1)
|
|||
|
|
public abstract class AppDatabase extends RoomDatabase {
|
|||
|
|
public abstract UserDao userDao();
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## SharedPreferences最佳实践
|
|||
|
|
|
|||
|
|
### 1. 使用 apply()
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// ✅ 推荐:使用 apply()
|
|||
|
|
editor.apply();
|
|||
|
|
|
|||
|
|
// ❌ 不推荐:使用 commit()(除非需要知道结果)
|
|||
|
|
editor.commit();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 批量写入
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// ✅ 批量写入
|
|||
|
|
editor.putString("key1", "value1");
|
|||
|
|
editor.putString("key2", "value2");
|
|||
|
|
editor.apply();
|
|||
|
|
|
|||
|
|
// ❌ 多次写入
|
|||
|
|
editor.putString("key1", "value1").apply();
|
|||
|
|
editor.putString("key2", "value2").apply();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 避免存储大量数据
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// ❌ 不推荐:存储大量数据
|
|||
|
|
editor.putString("largeData", largeString);
|
|||
|
|
|
|||
|
|
// ✅ 推荐:使用文件存储
|
|||
|
|
saveToFile(largeData);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 面试常见问题
|
|||
|
|
|
|||
|
|
### Q1: SharedPreferences 的原理?
|
|||
|
|
|
|||
|
|
**答案:**
|
|||
|
|
- 存储在 XML 文件中
|
|||
|
|
- 使用内存缓存 + 文件存储
|
|||
|
|
- 首次读取从文件加载,后续从内存读取
|
|||
|
|
|
|||
|
|
### Q2: apply() 和 commit() 的区别?
|
|||
|
|
|
|||
|
|
**答案:**
|
|||
|
|
- **apply()**:异步提交,不阻塞主线程
|
|||
|
|
- **commit()**:同步提交,返回结果,可能阻塞主线程
|
|||
|
|
|
|||
|
|
### Q3: SharedPreferences 是否线程安全?
|
|||
|
|
|
|||
|
|
**答案:**
|
|||
|
|
- 是线程安全的
|
|||
|
|
- 多个线程可以同时读取
|
|||
|
|
- 写入操作会加锁
|
|||
|
|
|
|||
|
|
### Q4: SharedPreferences 的替代方案?
|
|||
|
|
|
|||
|
|
**答案:**
|
|||
|
|
1. **MMKV**:高性能键值存储
|
|||
|
|
2. **DataStore**:Jetpack 组件
|
|||
|
|
3. **Room**:适合复杂数据
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
*最后更新:2024年*
|