如何解决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
相关产品推荐
相关产品推荐

