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

使用Animation List实现启动页动画时出现OutOfMemoryError问题

解决Animation List导致的OutOfMemoryError问题

嘿,这个问题我之前做启动页动画的时候也碰到过!Animation List虽然上手简单,但它的核心机制是一次性把所有帧对应的Drawable全部加载到内存中——如果你的帧数量多(比如几十上百张)、单帧图片分辨率又高,内存直接就扛不住了,OutOfMemoryError自然就找上门来。下面给你几个亲测有效的解决方案:

1. 手动逐帧加载+资源回收,替代Animation List

放弃AnimationList的自动加载逻辑,改用ImageView配合定时器手动控制帧的切换,每次只加载当前需要的帧,并且及时回收上一帧的资源,让内存里始终只保留少量帧:

Kotlin示例代码:

class SplashActivity : AppCompatActivity() {
    private lateinit var splashIv: ImageView
    private val frameCount = 100 // 替换成你的实际总帧数
    private var currentFrame = 0
    private val handler = Handler(Looper.getMainLooper())
    private val frameRunnable = object : Runnable {
        override fun run() {
            // 回收上一帧的Bitmap资源
            splashIv.drawable?.let { drawable ->
                if (drawable is BitmapDrawable) {
                    drawable.bitmap.recycle()
                }
            }
            // 加载当前帧(适配你命名的帧文件格式)
            val frameResId = resources.getIdentifier(
                "part_${currentFrame.toString().padStart(5, '0')}",
                "drawable",
                packageName
            )
            splashIv.setImageResource(frameResId)
            currentFrame++
            if (currentFrame < frameCount) {
                handler.postDelayed(this, 35) // 和你原来的单帧duration一致
            } else {
                // 动画结束,跳转主页面
                navigateToMain()
            }
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_splash)
        splashIv = findViewById(R.id.splash_iv)
        // 启动帧动画
        handler.post(frameRunnable)
        // 后台并行执行初始化操作
        doInitInBackground()
    }

    private fun doInitInBackground() {
        lifecycleScope.launch(Dispatchers.IO) {
            // 这里放登录、SDK初始化等耗时操作
            delay(2000) // 模拟耗时
            // 初始化完成后,若动画未结束则提前终止并跳转
            handler.post {
                if (currentFrame < frameCount) {
                    handler.removeCallbacks(frameRunnable)
                    navigateToMain()
                }
            }
        }
    }

    private fun navigateToMain() {
        startActivity(Intent(this, MainActivity::class.java))
        finish()
    }

    override fun onDestroy() {
        super.onDestroy()
        handler.removeCallbacks(frameRunnable)
        // 最后回收当前帧资源
        splashIv.drawable?.let { drawable ->
            if (drawable is BitmapDrawable) {
                drawable.bitmap.recycle()
            }
        }
    }
}

2. 压缩优化帧图片资源

这是最基础但见效快的一步:

  • 调整图片尺寸:把所有帧图的分辨率压缩到和启动页容器(比如ImageView)的实际显示尺寸一致,不需要用远超屏幕的大图。比如你的设备是1080p,就把帧图控制在1080×1920以内。
  • 压缩图片质量:用tinypng、Photoshop等工具对每张帧图进行无损/轻度有损压缩,减少单张图片的体积。
  • 合理放置资源目录:根据屏幕密度把图片放到对应的drawable-mdpidrawable-hdpi等目录,避免系统自动缩放导致内存占用额外增加。

3. 改用视频播放实现动画

把所有帧序列转换成MP4/WebM格式的视频,用VideoView或者ExoPlayer播放——视频是流式加载的,内存占用比逐帧图低得多:

简单示例(VideoView):

<!-- 布局文件 -->
<VideoView
    android:id="@+id/splash_video"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scaleType="centerCrop"/>
class SplashActivity : AppCompatActivity() {
    private lateinit var videoView: VideoView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_splash)
        videoView = findViewById(R.id.splash_video)

        // 设置raw目录下的视频路径
        val videoUri = Uri.parse("android.resource://$packageName/${R.raw.splash_animation}")
        videoView.setVideoURI(videoUri)
        // 设置循环播放
        videoView.setOnPreparedListener { it.isLooping = true }
        videoView.start()

        // 后台执行初始化
        doInitInBackground()
    }

    private fun doInitInBackground() {
        lifecycleScope.launch(Dispatchers.IO) {
            // 模拟初始化耗时
            delay(3000)
            runOnUiThread {
                videoView.stopPlayback()
                navigateToMain()
            }
        }
    }

    private fun navigateToMain() {
        startActivity(Intent(this, MainActivity::class.java))
        finish()
    }
}

4. 用Lottie动画替代逐帧图

如果你的动画是设计师制作的,可以让设计师用After Effects导出Lottie动画(JSON格式),再用Lottie库播放:

  • Lottie动画文件体积小,不需要大量图片帧
  • 内存占用极低,性能更优
  • 支持缩放不失真

集成步骤:

  1. 在build.gradle中添加依赖:
implementation "com.airbnb.android:lottie:6.4.0"
  1. 布局中添加LottieView:
<com.airbnb.lottie.LottieAnimationView
    android:id="@+id/lottie_splash"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:lottie_fileName="splash_animation.json"
    app:lottie_loop="true"
    app:lottie_autoPlay="true"/>
  1. 代码中控制动画和初始化逻辑的思路和前面一致,初始化完成后停止动画并跳转即可。

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

火山引擎 最新活动