Android DataStore 替代 SharedPreferences
FreeGuideOnline
最新
2026-07-12
groovy dependencies { // Preferences DataStore (仅键值对) implementation "androidx.datastore:datastore-preferences:1.1.1"
// Proto DataStore (结构化数据)
implementation "androidx.datastore:datastore-core:1.1.1"
implementation "com.google.protobuf:protobuf-javalite:3.25.3"
// 协程核心库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1"
}
> 注意:如果同时使用 Proto DataStore,需要在项目根 `build.gradle` 添加 Protobuf 插件。
## 快速上手 Preferences DataStore
它提供了与 SharedPreferences 类似的键值对模式,但 API 完全基于 Flow。
### 1. 创建 DataStore 实例
使用属性委托 `preferencesDataStore` 避免多次创建同一实例:
```kotlin
val Context.dataStore by preferencesDataStore(name = "settings")
推荐将实例置于顶层扩展属性,如 SettingsRepository 中。
2. 定义键 (Key)
使用 preferencesKey<T>() 指定键名和明确类型:
object PreferencesKeys {
val USER_NAME = stringPreferencesKey("user_name")
val AGE = intPreferencesKey("age")
val SHOW_NOTIFICATIONS = booleanPreferencesKey("show_notifications")
}
3. 写入数据
所有写操作都是挂起函数,通过 edit 完成,在事务中原子提交:
suspend fun saveUserName(name: String) {
context.dataStore.edit { preferences ->
preferences[PreferencesKeys.USER_NAME] = name
}
}
4. 读取数据
返回 Flow<T?>,可观察变化,读取不会阻塞线程:
val userNameFlow: Flow<String> = context.dataStore.data
.map { preferences ->
preferences[PreferencesKeys.USER_NAME] ?: "未设置"
}
在 Activity/Fragment 中收集:
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
userNameFlow.collect { name ->
textView.text = name
}
}
}
5. 捕获异常
DataStore 抛出 IOException 当底层文件访问失败,可使用 catch 操作符优雅处理:
val safeFlow: Flow<String> = context.dataStore.data
.catch { exception ->
if (exception is IOException) {
emit(emptyPreferences()) // 发送空值,让下游使用默认值
} else {
throw exception
}
}
.map { preferences ->
preferences[PreferencesKeys.USER_NAME] ?: "未知"
}
Proto DataStore:结构化首选
当需要存储复杂对象(如用户配置、应用状态)时,使用 Protocol Buffers 获得编译时类型安全。
1. 定义 .proto 文件
在 app/src/main/proto/ 下创建 user_prefs.proto:
syntax = "proto3";
option java_package = "com.example.app.datastore";
option java_multiple_files = true;
message UserPreferences {
string name = 1;
int32 age = 2;
repeated string interests = 3; // 列表
bool has_pro_account = 4;
}
构建项目,编译器将自动生成 Java/Kotlin 数据类。
2. 实现序列化器
将 proto 对象与磁盘格式相互转换:
object UserPreferencesSerializer : Serializer<UserPreferences> {
override val defaultValue: UserPreferences = UserPreferences.getDefaultInstance()
override suspend fun readFrom(input: InputStream): UserPreferences {
try {
return UserPreferences.parseFrom(input)
} catch (exception: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", exception)
}
}
override suspend fun writeTo(t: UserPreferences, output: OutputStream) {
t.writeTo(output)
}
}
3. 创建 Proto DataStore
val Context.userPreferencesDataStore by dataStore(
fileName = "user_prefs.pb",
serializer = UserPreferencesSerializer
)
4. 读写操作
写入通过 updateData,它提供了当前对象以便部分修改:
suspend fun updateUserAge(newAge: Int) {
context.userPreferencesDataStore.updateData { currentPreferences ->
currentPreferences.toBuilder()
.setAge(newAge)
.build()
}
}
读取仍然是 Flow:
val userPreferencesFlow: Flow<UserPreferences> = context.userPreferencesDataStore.data
从 SharedPreferences 平稳迁移
DataStore 提供了一次性导入机制,将旧数据自动转移至新存储。
1. 创建带迁移的 DataStore
通过 SharedPreferencesMigration 指定旧 SP 文件名:
val Context.dataStore by preferencesDataStore(
name = "settings",
produceMigrations = { context ->
listOf(SharedPreferencesMigration(context, "legacy_prefs"))
}
)
构建 DataStore 时,如果旧 SP 存在且未迁移,它的所有键值对会被复制到 DataStore,之后旧文件被删除。
2. 手动处理键映射(可选)
如果新旧键名不同或需要类型转换,可提供映射函数:
SharedPreferencesMigration(
context,
"legacy_prefs",
shouldRunMigration = { true }, // 可根据条件判断是否迁移
keyMapper = { oldKey, _ ->
// 将旧键映射为新键
when (oldKey) {
"old_name_key" -> USER_NAME.name
else -> null // 忽略该键
}
}
)