新的键值对存储方案 DataStore

SharedPreferences 想必都知道吧,是所有安卓人学数据持久化的入门必修课,它可以通过简单的键值对 (Key-Value) 方式来轻松存储和获取各种基本数据类型,在开发中常用于存储简单的应用配置、登录状态和用户偏好等等。

但它也有许多令人诟病的,比如无论 edit 还是 commit 都有可能引发 ANR ,容易阻塞线程,类型不安全,数据一致性难保等等。

反正我目前是还没遇到过

所以都 2026 了还有人在用 SP 吗?

官方:Welcome Jetpack DataStore, now in alpha - a new and improved data storage solution aimed at replacing SharedPreferences.

??????

介绍

DataStore 是个什么❓

依旧美美照搬

Jetpack DataStore 是一种数据存储解决方案,让您可以使用协议缓冲区存储键值对或类型化对象。DataStore 使用 Kotlin 协程和 Flow 以异步、一致的事务方式存储数据。

DataStore 的分类

DataStore 分为两种,如下:

  • Preferences DataStore : 嗯… 和 SP 类似,就把它当成 SP 改良版吧,它可以用于存储简单的键值对 (Key-Value),不需要预定义架构,也不确保类型安全。
  • Proto DataStore : 用于存储类型化对象,用于存储自定义的强类型对象,使用ProtoBuf 来定义架构,确保类型安全(本文暂不对它展开介绍)。

优势在我:

  • 因为是基于协程的,所以完全异步,主线程安全
  • DataStore 使用 Flow 暴露数据,支持响应式
  • 事务性更新,原子操作
  • 支持 try-catch
  • ……

添加依赖

这个不必多说,在 build.gradle 引入依赖。

1
implementation("androidx.datastore:datastore-preferences:1.2.1")

创建DataStore

可以通过扩展函数属性委托 preferencesDataStore 来创建 DataStore<Preferences> 实例。把它放在文件顶层,以保证全局单例

1
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "XXX")

定义Keys

与 SP 不同直接传字符串不一样,DataStore 需要使用专门的函数来定义 Key 。

1
2
3
4
object PrefsKeys {
val DARK_MODE = booleanPreferencesKey("dark_mode")
val USERNAME = stringPreferencesKey("username")
val AGE = intPreferencesKey("age") }

读取数据

定义好 key 后,就能用 DataStore.data 读取数据啦,它会返回一个 Flow<T> 对象,数据变化时会重新发出。

1
2
3
val userNameFlow: Flow<String> = context.dataStore.data.map { preferences ->
preferences[USER_NAME_KEY] ?: "访客"
}

写入数据

写入时使用的是 DataStore.edit 它是 上吊 挂起 的,所以必须在协程作用域中执行。

1
2
3
4
5
suspend fun saveUserName( name: String) {
context.dataStore.edit { settings ->
settings[PrefsKeys.USERNAME] = name settings
}
}

迁移

如果老项目需要迁移,构建时传入 SharedPreferencesMigration 即可。

温馨提示:新旧请key名保持一致

1
2
3
4
5
6
val Context.dataStore by preferencesDataStore(
name = "new", // DataStore 名字
produceMigrations = { context ->
listOf(SharedPreferencesMigration(context,"old"))//旧的 SP 名字
}
)

……

嗯…好麻烦,还是回去继续接着开心使用SharedPreferences了!

示例

额,说的再多不如直接上代码运行(以下代码由 AI 生成并人工微调,可运行)

DataStoreManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import android.content.Context  
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "user_prefs")
class DataStoreManager(private val context: Context) {
//定义 Keys companion object {
val USER_NAME_KEY = stringPreferencesKey("user_name")
val CLICK_COUNT_KEY = intPreferencesKey("click_count")
}
//读取数据
val userNameFlow: Flow<String> = context.dataStore.data.map { preferences ->
preferences[USER_NAME_KEY] ?: "访客" //默认值为访客
}
val clickCountFlow: Flow<Int> = context.dataStore.data.map { preferences ->
preferences[CLICK_COUNT_KEY] ?: 0
}
// 写入数据
suspend fun saveUserName(name: String) {
context.dataStore.edit { preferences ->
preferences[USER_NAME_KEY] = name
}
}
suspend fun incrementClickCount() {
context.dataStore.edit { preferences ->
val currentCounterValue = preferences[CLICK_COUNT_KEY] ?: 0
preferences[CLICK_COUNT_KEY] = currentCounterValue + 1
}
}
}

ViewModel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import android.app.Application  
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch

class MainViewModel(application: Application) : AndroidViewModel(application) {

private val dataStoreManager = DataStoreManager(application)
val userNameFlow = dataStoreManager.userNameFlow
val clickCountFlow = dataStoreManager.clickCountFlow
//保存名字
fun saveName(name: String) {
viewModelScope.launch {
dataStoreManager.saveUserName(name)
}
}
//增加次数
fun incrementCount() {
viewModelScope.launch {
dataStoreManager.incrementClickCount()
}
}
}

Activity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import androidx.activity.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.launch

class MainActivity : AppCompatActivity() {
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//都2026了还用findViewById?
val tvGreeting = findViewById<TextView>(R.id.tvGreeting)
val tvCounter = findViewById<TextView>(R.id.tvCounter)
val etName = findViewById<EditText>(R.id.etName)
val btnSaveName = findViewById<Button>(R.id.btnSaveName)
val btnIncrement = findViewById<Button>(R.id.btnIncrement)

btnSaveName.setOnClickListener {
val name = etName.text.toString()
if (name.isNotBlank()) {
viewModel.saveName(name)
etName.text.clear()
}
}

btnIncrement.setOnClickListener {
viewModel.incrementCount()
}

//监听DataStore数据的变化,自动更新 UI
lifecycleScope.launch {
//节省资源罢了
repeatOnLifecycle(Lifecycle.State.STARTED) {
// 监听名字变化
launch {
viewModel.userNameFlow.collect { name ->
tvGreeting.text = "你好,$name!"
}
} //监听点击次数变化
launch {
viewModel.clickCountFlow.collect { count ->
tvCounter.text = "点击次数: $count"
}
}
}
}
}
}

XML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp"
android:fitsSystemWindows="true")>

<TextView
android:id="@+id/tvGreeting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="你好,访客!"
android:textSize="24sp"
android:textStyle="bold" />

<TextView
android:id="@+id/tvCounter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="点击次数: 0"
android:textSize="18sp"
android:layout_marginTop="8dp" />

<EditText
android:id="@+id/etName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="输入你的名字"
android:layout_marginTop="32dp" />

<Button
android:id="@+id/btnSaveName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="保存名字" />

<Button
android:id="@+id/btnIncrement"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="增加点击次数"
android:layout_marginTop="16dp" />

</LinearLayout>

附:

官方对比图

img

相关网址

官方介绍DataStore