You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 18:07:03