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

如何解决Jetpack Compose中Lottie与ViewPager的内存溢出问题?

解决Jetpack Compose中Lottie与ViewPager搭配的内存溢出问题

问题场景

在Jetpack Compose中结合Lottie Compose和ViewPager使用时,触发了OutOfMemoryError崩溃。每个页面的Lottie文件大小约4MB,但仍出现内存溢出,推测是ViewPager同时加载多个页面,导致重复创建Lottie实例占用过多内存。

原实现代码:

val composition by rememberLottieComposition(LottieCompositionSpec.Asset(tutorial.lottieSrc))
val progress by animateLottieCompositionAsState(composition)
val lottieAnimatable = rememberLottieAnimatable()

LaunchedEffect(Unit) {
    lottieAnimatable.animate(
        composition,
        iteration = LottieConstants.IterateForever
    )
}

LottieAnimation(
    composition = composition,
    progress = { progress },
    modifier = Modifier
        .size(220.dp)
)

错误日志

首先收到GC提示:

Clamp target GC heap from 51MB to 48MB

随后应用崩溃,错误栈:

java.lang.OutOfMemoryError: Failed to allocate a 12 byte allocation with 0 free bytes and -16B until OOM
                                at androidx.compose.runtime.CompositionImpl.addPendingInvalidationsLocked(Composition.kt:674)
                                at androidx.compose.runtime.CompositionImpl.drainPendingModificationsLocked(Composition.kt:566)
                                at androidx.compose.runtime.CompositionImpl.recordModificationsOf(Composition.kt:656)
                                at androidx.compose.runtime.Recomposer.recordComposerModificationsLocked(Recomposer.kt:417)
                                at androidx.compose.runtime.Recomposer.access$recordComposerModificationsLocked(Recomposer.kt:125)
                                at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2.invokeSuspend(Recomposer.kt:499)
                                at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
                                at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
                                at androidx.compose.ui.platform.AndroidUiDispatcher.performTrampolineDispatch(AndroidUiDispatcher.android.kt:81)
                                at androidx.compose.ui.platform.AndroidUiDispatcher.access$performTrampolineDispatch(AndroidUiDispatcher.android.kt:41)
                                at androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.run(AndroidUiDispatcher.android.kt:57)
                                at android.os.Handler.handleCallback(Handler.java:751)
                                at android.os.Handler.dispatchMessage(Handler.java:95)
                                at android.os.Looper.loop(Looper.java:154)
                                at android.app.ActivityThread.main(ActivityThread.java:6119)
                                at java.lang.reflect.Method.invoke(Native Method)
                                at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
                                at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
                                Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [androidx.compose.runtime.PausableMonotonicFrameClock@b1d76ac, androidx.compose.ui.platform.MotionDurationScaleImpl@abc1f75, StandaloneCoroutine{Cancelling}@d2d3b0a, AndroidUiDispatcher@7fb5f7b]

解决方案

1. 全局复用LottieComposition

原代码中rememberLottieComposition在每个页面Composable内调用,导致每个页面都会重复加载Lottie资源。可以将Composition缓存提升到ViewPager外部,以Lottie文件路径为key实现复用:

// 在ViewPager所在的父Composable中缓存Composition
val tutorials = // 你的页面数据列表
val compositionCache = remember { mutableMapOf<String, LottieComposition?>() }

// 按需加载并缓存所有需要的Composition
LaunchedEffect(tutorials) {
    tutorials.forEach { tutorial ->
        if (!compositionCache.containsKey(tutorial.lottieSrc)) {
            val compositionResult = rememberLottieComposition(LottieCompositionSpec.Asset(tutorial.lottieSrc))
            compositionCache[tutorial.lottieSrc] = compositionResult.value
        }
    }
}

// 页面内直接使用缓存的Composition
HorizontalPager(count = tutorials.size) { page ->
    val tutorial = tutorials[page]
    val composition = compositionCache[tutorial.lottieSrc]
    
    if (composition != null) {
        val lottieAnimatable = rememberLottieAnimatable()
        val isCurrentPage = pagerState.currentPage == page
        
        LaunchedEffect(isCurrentPage, composition) {
            if (isCurrentPage) {
                lottieAnimatable.animate(
                    composition,
                    iteration = LottieConstants.IterateForever
                )
            } else {
                lottieAnimatable.cancel()
            }
        }
        
        LottieAnimation(
            composition = composition,
            progress = { lottieAnimatable.progress },
            modifier = Modifier.size(220.dp)
        )
    }
}

2. 限制ViewPager的页面缓存数量

Accompanist Pager默认会缓存当前页面前后各1个页面,通过调整pageLimit可以减少同时驻留内存的页面数,降低内存占用:

val pagerState = rememberPagerState()

HorizontalPager(
    count = tutorials.size,
    state = pagerState,
    pageLimit = PagerDefaults.defaultPageLimit(0) // 仅保留当前页面在内存
) { page ->
    // 页面内容
}

3. 按需控制Lottie动画播放

当页面不可见时,暂停或停止动画,避免不必要的资源消耗。通过Pager的当前页面状态判断,控制动画的启停:

val pagerState = rememberPagerState()
val isCurrentPage = pagerState.currentPage == page
val lottieAnimatable = rememberLottieAnimatable()

LaunchedEffect(isCurrentPage, composition) {
    if (isCurrentPage && composition != null) {
        // 当前页面可见时播放动画
        lottieAnimatable.animate(
            composition,
            iteration = LottieConstants.IterateForever
        )
    } else {
        // 页面不可见时停止动画并重置进度
        lottieAnimatable.cancel()
        lottieAnimatable.snapTo(0f)
    }
}

4. 优化Lottie资源本身

  • 如果Lottie包含图片资源,指定imageAssetsFolder统一管理,避免重复加载图片资源
  • 联系设计人员,适当降低动画的帧率、分辨率或减少不必要的图层,缩小Lottie文件的内存占用

内容的提问来源于stack exchange,提问作者c-an

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 02:20:46