Android界面绘制时Lottie动画冻结问题及线程方案咨询
解决Lottie动画在界面绘制时冻结的问题
问题根源
你的代码中使用了LottieCompositionFactory.fromJsonInputStreamSync这个同步解析方法,如果在主线程执行,会直接阻塞UI线程。当收到后台响应后,界面本身需要进行重绘操作,此时主线程又被Lottie Composition的解析占用,导致Lottie动画无法及时更新帧,最终出现冻结。
核心结论
不能把LottieDrawable的播放逻辑放到工作线程(因为Drawable的绘制、动画更新都依赖UI线程),但可以将Lottie Composition的解析操作移到后台线程,这才是解决冻结的关键。
解决方案
1. 使用Lottie自带的异步API解析(Java)
将同步解析替换为异步回调,让解析过程在后台线程执行,完成后回到主线程设置Drawable:
// 定义加载回调接口 public interface OnLottieLoadedListener { void onLoaded(LottieDrawable drawable); void onFailed(Exception e); } // 异步加载LottieDrawable的方法 public static void getLottieFromInputStreamAsync(@NonNull InputStream inputStream, @NonNull OnLottieLoadedListener listener) { LottieCompositionFactory.fromJsonInputStream(inputStream, null) .addListener(composition -> { LottieDrawable drawable = new LottieDrawable(); drawable.enableMergePathsForKitKatAndAbove(true); drawable.setComposition(composition); drawable.playAnimation(); listener.onLoaded(drawable); }) .addFailureListener(listener::onFailed); } // 调用示例 try { InputStream inputStream = pContext.getAssets().open(pImageName); getLottieFromInputStreamAsync(inputStream, new OnLottieLoadedListener() { @Override public void onLoaded(LottieDrawable drawable) { // 主线程中设置布局背景 layout.setBackground(drawable); } @Override public void onFailed(Exception e) { // 处理加载失败逻辑,比如设置默认背景 } }); } catch (IOException e) { e.printStackTrace(); }
2. 使用Kotlin协程优化(推荐)
如果项目使用Kotlin,协程可以更简洁地实现后台解析+主线程更新:
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import androidx.lifecycle.lifecycleScope // 后台解析LottieComposition suspend fun loadLottieDrawable(inputStream: InputStream): LottieDrawable? { return withContext(Dispatchers.IO) { val compositionResult = LottieCompositionFactory.fromJsonInputStream(inputStream, null).await() compositionResult.value?.let { composition -> LottieDrawable().apply { enableMergePathsForKitKatAndAbove(true) setComposition(composition) playAnimation() } } } } // 在Activity/Fragment中调用 lifecycleScope.launch { try { val inputStream = pContext.assets.open(pImageName) val lottieDrawable = loadLottieDrawable(inputStream) lottieDrawable?.let { layout.background = it } } catch (e: IOException) { e.printStackTrace() } }
3. 额外优化:缓存解析后的Composition
如果同一个动画会被多次使用,可以缓存解析好的Composition,避免重复解析提升性能:
private static final HashMap<String, LottieComposition> COMPOSITION_CACHE = new HashMap<>(); public static void getLottieFromInputStreamAsync(@NonNull String imageName, @NonNull InputStream inputStream, @NonNull OnLottieLoadedListener listener) { // 先从缓存取,避免重复解析 if (COMPOSITION_CACHE.containsKey(imageName)) { LottieComposition composition = COMPOSITION_CACHE.get(imageName); LottieDrawable drawable = new LottieDrawable(); drawable.enableMergePathsForKitKatAndAbove(true); drawable.setComposition(composition); drawable.playAnimation(); listener.onLoaded(drawable); return; } LottieCompositionFactory.fromJsonInputStream(inputStream, null) .addListener(composition -> { COMPOSITION_CACHE.put(imageName, composition); LottieDrawable drawable = new LottieDrawable(); drawable.enableMergePathsForKitKatAndAbove(true); drawable.setComposition(composition); drawable.playAnimation(); listener.onLoaded(drawable); }) .addFailureListener(listener::onFailed); }
关键注意事项
- LottieDrawable的
playAnimation()、setComposition()必须在主线程调用,不能放到后台线程。 - 异步解析完成后,一定要回到主线程更新UI(上述代码的回调/协程切换已经处理了这一点)。
内容的提问来源于stack exchange,提问作者Vijayadhas Chandrasekaran
相关产品推荐
相关产品推荐

