You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

多Lottie动画同屏展示引发内存飙升的优化方案及格式选型咨询

多Lottie动画同屏展示引发内存飙升的优化方案及格式选型咨询

你好,针对你在SwiftUI中批量展示Lottie动画遇到的内存和性能问题,我来分享几个实用的优化思路,以及帮你分析下格式选型的问题:

一、先从Lottie本身的优化入手

你的代码里每个LottieView都重新加载解析一遍同一个JSON动画,这是内存飙升的核心原因之一。试试下面这些优化点:

1. 复用Animation实例

Animation对象提前初始化,让所有LottieView共享同一个实例,避免重复解析JSON:

struct StickersPackCollectionView: View {
    private let columns = Array(repeating: GridItem(.flexible(), spacing: 1), count: 5)
    // 全局复用一个Animation实例,只解析一次JSON
    private let sharedAnimation = Animation.named("vault_boy_test")
    
    var body: some View {
        ScrollView {
            LazyVGrid(columns: columns, spacing: 1) {
                stickersCollectionView()
            }
            .padding()
        }
        .background(Color(ColorManager.navigationBarBackgroundColor))
    }
}

extension StickersPackCollectionView {
    private func stickersCollectionView() -> some View {
        ForEach((1..<100)) { index in
            LottieView(animation: sharedAnimation)
                .playbackMode(.playing(.toProgress(1, loopMode: .loop)))
                .resizable()
                .frame(width: 75, height: 75)
        }
    }
}

2. 切换更高效的渲染引擎

Lottie提供了多种渲染引擎,试试CoreAnimation引擎,它基于系统原生动画框架,性能和内存占用会更优:

LottieView(animation: sharedAnimation)
    .renderingEngine(.coreAnimation) // 切换到CoreAnimation引擎
    .playbackMode(.playing(.toProgress(1, loopMode: .loop)))
    .resizable()
    .frame(width: 75, height: 75)

3. 按需播放/暂停动画

利用LazyVGrid的懒加载特性,只在单元格进入可视区域时播放动画,离开时暂停甚至重置,释放资源:

LottieView(animation: sharedAnimation)
    .playbackMode(.playing(.toProgress(1, loopMode: .loop)))
    .resizable()
    .frame(width: 75, height: 75)
    .onAppear {
        // 获取LottieView实例并开始播放(如果用的是lottie-swift的SwiftUI版本,可通过绑定或直接调用play方法)
        // 例如:lottieView.play()
    }
    .onDisappear {
        // 暂停并重置动画,释放渲染资源
        // lottieView.stop()
    }

4. 降低动画渲染分辨率

你的JSON是512x512,但实际只显示75x75,没必要渲染大尺寸的矢量帧。可以:

  • 直接修改JSON文件里的尺寸为75x75(最有效)
  • 或者在初始化Animation时设置缩放比例:
private let sharedAnimation = Animation.named("vault_boy_test", imageProvider: .init(scale: 75/512))

二、格式选型:Lottie vs GIF/WEBP

如果上述优化后还是达不到预期,就需要考虑换格式了,这里给你做个对比:

1. 优先推荐动态WEBP

动态WEBP是目前批量展示贴纸的最优选择:

  • 压缩率远高于GIF,文件体积小,同时支持透明通道和高质量色彩
  • 渲染时直接使用位图,内存占用稳定,100个75x75的WEBP动画的内存占用会远低于Lottie
  • 播放性能更优,系统原生支持,不需要额外的矢量计算

2. 不推荐GIF

GIF的缺点很明显:色彩数最多256色,画质差;压缩率低,文件体积大;内存占用和WEBP差不多,但体验不如WEBP。

3. 什么时候适合保留Lottie

如果你的贴纸需要支持任意缩放不失真、有复杂的路径动画或交互,那Lottie依然是更好的选择,但不适合批量展示100个以上的实例。

三、极端场景的折中方案:预渲染Lottie为位图序列

如果既想保留Lottie的动画效果,又要控制内存,可以把Lottie动画预渲染成固定尺寸的UIImage序列,然后用Image组件循环播放:

// 预渲染Lottie为75x75的帧序列
func preRenderStickerFrames() -> [UIImage] {
    guard let animation = Animation.named("vault_boy_test") else { return [] }
    let lottieView = LottieAnimationView(animation: animation)
    lottieView.frame = CGRect(x: 0, y: 0, width: 75, height: 75)
    lottieView.layoutIfNeeded()
    
    var frames: [UIImage] = []
    let frameRate = animation.frameRate
    for progress in stride(from: 0.0, to: 1.0, by: 1/frameRate) {
        lottieView.currentProgress = progress
        lottieView.layoutIfNeeded()
        if let image = lottieView.asImage() {
            frames.append(image)
        }
    }
    return frames
}

// 用Image播放帧序列
struct AnimatedStickerView: View {
    private let frames: [UIImage]
    @State private var currentFrame = 0
    
    init(frames: [UIImage]) {
        self.frames = frames
    }
    
    var body: some View {
        Image(uiImage: frames[currentFrame])
            .resizable()
            .frame(width: 75, height: 75)
            .onAppear {
                Timer.scheduledTimer(withTimeInterval: 1/30, repeats: true) { _ in
                    currentFrame = (currentFrame + 1) % frames.count
                }
            }
    }
}

这个方案的缺点是预渲染需要时间,且帧序列会占用更多磁盘空间,但内存占用会非常稳定,适合批量展示场景。

最后建议你用Xcode的Instruments工具(Memory和CPU模板)分析一下内存占用的来源,看看是Animation对象、渲染层还是其他部分占了大头,这样优化会更有针对性~

火山引擎 最新活动