Jetpack DataStore调用count()导致函数执行终止问题求助
问题
我尝试使用Jetpack DataStore保存Android应用的简单状态,希望在指定键无存储值时为其填充默认值。但当我调用方法检查该键是否存在值时,库调用导致整个函数的执行完全停止。
代码示例:
package com.example.spielwiese import android.content.Context import android.os.Bundle import android.util.Log import androidx.activity.enableEdgeToEdge import androidx.appcompat.app.AppCompatActivity import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.stringSetPreferencesKey import androidx.datastore.preferences.preferencesDataStore import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.count import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlinx.coroutines.withContext val Context.dataStore: DataStore<Preferences> by preferencesDataStore("settings") class MainActivity : AppCompatActivity() { private val scopeIo = CoroutineScope(Job() + Dispatchers.IO) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContentView(R.layout.activity_main) ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets -> val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()) v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom) insets } scopeIo.launch { checkPreferences() } } private suspend fun checkPreferences() = withContext(Dispatchers.IO) { Log.d("HUGELBUGEL", "Before dataStore access.") val nothingThere = dataStore.data.map { preferences -> preferences[stringSetPreferencesKey("myKey")] }.count() == 0 // This log doesn't come out Log.d("HUGELBUGEL", "After dataStore access: $nothingThere") // if (nothingThere) putDefaultValueIntoDataStore() } }
运行时第一个日志正常输出,但第二个日志无输出。调试发现执行到count()调用后就再也不返回,且无任何异常(已尝试捕获)。为何会直接停止执行而不抛出异常?是使用DataStore的方式有误吗?该如何解决这个问题?
app模块的build.gradle.kts配置:
plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) } android { namespace = "com.example.spielwiese" compileSdk = 34 defaultConfig { applicationId = "com.example.spielwiese" minSdk = 24 targetSdk = 34 versionCode = 1 versionName = "1.0" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } } compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = "1.8" } } dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) implementation(libs.material) implementation(libs.androidx.activity) implementation(libs.androidx.constraintlayout) implementation(libs.androidx.datastore.preferences) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) }
这是Android Studio创建新项目时的默认配置,仅添加了问题相关的必要代码。
解决思路与修复方案
问题根源
DataStore的data属性返回的是冷流(Cold Flow),它会持续发射数据更新(每次DataStore内容变化都会发射新值)。而count()操作符会一直等待流的所有发射完成,直到流被取消或终止——但DataStore的流永远不会主动终止,所以count()会一直挂起,导致后续代码无法执行。
另外,自定义的scopeIo使用独立Job(),未与Activity生命周期绑定,虽非本次问题直接原因,但可能引发内存泄漏,建议改用生命周期绑定的协程Scope。
修复代码
1. 正确检查键是否存在
弃用count(),改用first()获取流的单次发射值(当前DataStore内容),直接判断目标键是否存在:
private suspend fun checkPreferences() = withContext(Dispatchers.IO) { Log.d("HUGELBUGEL", "Before dataStore access.") // 获取当前DataStore的所有偏好设置 val preferences = dataStore.data.first() val myKey = stringSetPreferencesKey("myKey") // 判断键是否存在(值为null即不存在) val nothingThere = preferences[myKey] == null Log.d("HUGELBUGEL", "After dataStore access: $nothingThere") if (nothingThere) { // 原子性写入默认值 dataStore.edit { settings -> settings[myKey] = setOf("default_value") } } }
2. 改用生命周期绑定的协程Scope
替换自定义scopeIo为Activity自带的lifecycleScope,自动跟随生命周期销毁协程,避免内存泄漏:
// 移除自定义的scopeIo // private val scopeIo = CoroutineScope(Job() + Dispatchers.IO) override fun onCreate(savedInstanceState: Bundle?) { // ...其他代码 // 改用lifecycleScope,指定IO调度器执行 lifecycleScope.launch(Dispatchers.IO) { checkPreferences() } }
补充说明
- DataStore的
data流设计用于监听数据变化,若仅需获取当前值,使用first()即可终止流的监听。 edit函数是DataStore提供的原子性写入方法,确保多线程环境下的数据修改安全。lifecycleScope是AndroidX提供的生命周期绑定协程Scope,会在Activity销毁时自动取消所有协程,避免内存泄漏。
内容的提问来源于stack exchange,提问作者David
相关产品推荐
相关产品推荐

