写入基本类型数据:edit{}、putInt/putString 与 Flow 写入注意点
写入操作是 DataStore 偏好设置的核心能力。不同于 SharedPreferences 的直接同步写入,DataStore 采用基于 事务 的异步写入方式,通过挂起函数 edit{} 保证数据一致性。本章围绕原理、API 用法、代码示例、常见错误与性能要点展开。
4.1 写入原理:事务式编辑
DataStore 的写入发生在 DataStore<Preferences> 的 edit{} 扩展函数内。该函数接收一个挂起 lambda,lambda 内暴露 MutablePreferences 对象,调用其 putXxx() 方法完成写入。写入过程具有以下特征:
- 原子性:整个 lambda 块被视为一个事务,要么全部成功,要么全部回滚。
- 同步挂起:
edit{}是挂起函数,必须在协程中调用,写入操作不会阻塞 UI。 - 序列化顺序:所有写入请求按照调用顺序排队执行,后一个事务会等待前一个完成。
核心对比: 与 SharedPreferences 的同步
edit().putXxx().apply() 不同,DataStore 的 edit{} 天然支持单线程写入,无需开发者手动加锁。Flow 在写入完成后自动发射新值。
4.2 实现流程:三步写入
- 获取 DataStore 实例:通过
preferencesDataStore委托创建单例。 - 在协程作用域内调用 edit{}:使用
scope.launch或LifecycleScope。 - 调用对应 put 方法:
putInt(key, value)、putString(key, value)、putBoolean(key, value)、putFloat(key, value)、putLong(key, value)、putDouble(key, value)、putStringSet(key, value)。
| 基本类型 | put 方法 | get 对应方法 |
|---|---|---|
| Int | putInt | intPreferencesKey |
| String | putString | stringPreferencesKey |
| Boolean | putBoolean | booleanPreferencesKey |
| Float | putFloat | floatPreferencesKey |
| Long | putLong | longPreferencesKey |
| Double | putDouble | doublePreferencesKey |
| Set<String> | putStringSet | stringSetPreferencesKey |
4.3 关键 API 详解
4.3.1 edit{} 完整签名
suspend fun DataStore<Preferences>.edit(
transform: suspend (MutablePreferences) -> Unit
): Preferences
transform 是挂起的 lambda,内部可调用任意 putXxx 方法。写入完成后返回新的 Preferences 对象。
4.3.2 写入基本类型示例
// 定义键(通常在顶层或伴生对象中)
val INT_KEY = intPreferencesKey("user_age")
val STRING_KEY = stringPreferencesKey("user_name")
val BOOL_KEY = booleanPreferencesKey("is_logged_in")
// 写入操作(在协程中执行)
suspend fun saveUserData(dataStore: DataStore<Preferences>) {
dataStore.edit { preferences ->
preferences[INT_KEY] = 28
preferences[STRING_KEY] = "Alice"
preferences[BOOL_KEY] = true
}
}
注意:赋值语法 preferences[key] = value 是 putXxx 的简写形式,功能等价。
4.3.3 批量写入与条件写入
dataStore.edit { prefs ->
// 仅当键不存在时写入
val existing = prefs[STRING_KEY]
if (existing == null) {
prefs[STRING_KEY] = "default_name"
}
// 批量更新
prefs[INT_KEY] = prefs[INT_KEY]?.plus(1) ?: 0
}
4.4 Flow 数据写入注意点
DataStore 将读取暴露为 Flow<Preferences> 或通过 data.data 获取。写入时需重点关注以下问题:
- 读取与写入的生命周期分离:
Flow在收集期间自动监听磁盘变化,但写入必须主动调用edit{},不会由 Flow 触发。 - 不要在 Flow 的 collect 块内直接调用 edit{}:会导致重复写入嵌套,推荐将写入逻辑放在独立的
launch中。 - 写入后 Flow 自动更新:
edit{}提交后,DataStore 内部会写入文件并通知所有活跃的 Flow 收集器发射新值,无需手动刷新。 - 避免高频写入:每调用一次
edit{}都会触发射频文件同步,频繁调用可能影响性能。建议将多次修改合并到一个edit{}内。
常见错误: 在协程中调用
edit{} 时忘记处理异常,或使用非挂起上下文(如 GlobalScope)导致写入被忽略。务必使用适当的协程作用域(如 viewModelScope、lifecycleScope)。
4.5 写入流程内联 SVG
4.6 调试与验证
写入后可通过读取 Flow 日志或使用 Android Studio 的 Device File Explorer 查看持久化文件:
// 查看文件位置(模拟器/设备)
// /data/data/包名/files/datastore/包名_preferences_pb
// 日志打印写入结果
dataStore.data.collect { preferences ->
Log.d("DataStore", "age=${preferences[INT_KEY]}, name=${preferences[STRING_KEY]}")
}
4.7 性能与稳定性优化
- 合并写入:将多个
put放在同一个edit{}块内,减少文件 I/O 次数。 - 避免大对象:
putStringSet存储大量字符串集合时注意序列化开销,建议限制单个 value 大小在 1MB 以下。 - 异常处理:对
edit{}使用try-catch捕获IOException,防止写过程中断导致数据丢失。
dataStore.edit { prefs ->
prefs[INT_KEY] = newValue
// 潜在异常:文件写满、磁盘故障等
}.also { result ->
// 成功写入
}.catch { e ->
Log.e("DataStore", "写入失败", e)
// 可考虑重试或回滚逻辑
}
最佳实践: 在
ViewModel 中使用 viewModelScope.launch 调用 edit{},自动跟随 Activity/Fragment 生命周期。UI 层仅负责读取 Flow,不直接写入。