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

在ComposeView悬浮窗中收集DataStore<Preference>作为状态时的内存泄漏

前台服务悬浮视图结合DataStore引发OOM问题

问题现象

开发前台服务加载悬浮ComposeView,视图依赖Preference DataStore的配置数据,同时通过每秒更新多次的time流触发重组。初始运行正常,但几分钟后程序因OutOfMemoryError崩溃。Android Studio Profiler显示内存占用持续上升,但未检测到内存泄漏。

相关GC日志:

I Background concurrent mark compact GC freed 20MB AllocSpace bytes, 0(0B) LOS objects, 45% free, 29MB/53MB, paused 372us,2.483ms total 112.599ms
I Background concurrent mark compact GC freed 20MB AllocSpace bytes, 0(0B) LOS objects, 42% free, 32MB/56MB, paused 290us,1.993ms total 121.798ms
I Background concurrent mark compact GC freed 20MB AllocSpace bytes, 0(0B) LOS objects, 39% free, 36MB/60MB, paused 165us,1.812ms total 118.597ms
I Background concurrent mark compact GC freed 20MB AllocSpace bytes, 0(0B) LOS objects, 37% free, 39MB/63MB, paused 185us,2.268ms total 156.244ms

测试验证:

  • 仅保留time流的收集,内存占用稳定,无OOM问题
  • 将相同ComposeView逻辑放在Activity中,无法复现该问题

核心代码片段

ComposeView内容:

// val dataStore: DataStore<Preferences> is a singleton
val settings by dataStore.data.map { /* whatever */ }.collectAsStateWithLifecycle(initialValue, context = context)

// Variable that is updated very often just to force recomposition
val time by time.collectAsStateWithLifecycle(context = context)

Text("${settings.test} $time")

完整复现代码

MainActivity

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Since this is a test app, we just assume the overlay and notification permissions are already granted
        // Don't forget to declare the permissions and the service in the manifest
        ContextCompat.startForegroundService(this, Intent(this, MyService::class.java))
    }
}

MyService

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

class MyService : LifecycleService(), SavedStateRegistryOwner {

    companion object {
        private const val STATUS_NOTIFICATION_ID = 1
        private const val NOTIFICATION_CHANNEL_ID = "overlay_indicator"

        private val TEST = booleanPreferencesKey("test")
    }

    private data class Settings(val test: Boolean = true)

    private val job = SupervisorJob()
    private val scope = CoroutineScope(Dispatchers.Main + job)
    private val savedStateRegistryController = SavedStateRegistryController.create(this)
    private val time = MutableStateFlow(0L)

    private lateinit var view: ComposeView

    override fun onCreate() {
        super.onCreate()
        savedStateRegistryController.performAttach()
        savedStateRegistryController.performRestore(null)

        view = ComposeView(this).apply {
            setViewTreeSavedStateRegistryOwner(this@MyService)
            setViewTreeLifecycleOwner(this@MyService)
            setContent {
                CompositionLocalProvider(LocalLifecycleOwner provides this@MyService) {
                    val context = Dispatchers.Main + job
                    val settings by dataStore.data.map { Settings(it[TEST] ?: true) }.collectAsStateWithLifecycle(Settings(), context = context)
                    val time by time.collectAsStateWithLifecycle(context = context)

                    Text("${settings.test} $time")
                }
            }
        }
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        super.onStartCommand(intent, flags, startId)
        startForeground()

        val layoutParams = WindowManager.LayoutParams(
            WRAP_CONTENT,
            WRAP_CONTENT,
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                TYPE_APPLICATION_OVERLAY
            } else {
                @Suppress("deprecation") TYPE_SYSTEM_ALERT
            },
            FLAG_NOT_FOCUSABLE,
            PixelFormat.TRANSLUCENT,
        ).apply { gravity = Gravity.BOTTOM }

        val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
        try {
            windowManager.addView(view, layoutParams)
        } catch (_: Exception) {

        }


        scope.launch {
            var now = TimeSource.Monotonic.markNow()
            while (true) {
                val newNow = TimeSource.Monotonic.markNow()
                time.value += (newNow - now).inWholeMicroseconds
                now = newNow
                delay(timeMillis = 1)
            }
        }

        return START_NOT_STICKY
    }

    override fun onDestroy() {
        val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
        windowManager.removeView(view)

        super.onDestroy()
        job.cancel()
    }

    override val savedStateRegistry: SavedStateRegistry
        get() = savedStateRegistryController.savedStateRegistry


    private fun startForeground() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                NOTIFICATION_CHANNEL_ID, "Channel", NotificationManager.IMPORTANCE_MIN
            )
            val manager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
            manager.createNotificationChannel(channel)
        }

        val notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID).build()
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            startForeground(
                STATUS_NOTIFICATION_ID, notification, FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
            )
        } else {
            startForeground(STATUS_NOTIFICATION_ID, notification)
        }
    }
}

内容的提问来源于stack exchange,提问作者E__Man

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 12:56:00