使用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-mdpi、drawable-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动画文件体积小,不需要大量图片帧
- 内存占用极低,性能更优
- 支持缩放不失真
集成步骤:
- 在build.gradle中添加依赖:
implementation "com.airbnb.android:lottie:6.4.0"
- 布局中添加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"/>
- 代码中控制动画和初始化逻辑的思路和前面一致,初始化完成后停止动画并跳转即可。
内容的提问来源于stack exchange,提问作者Keselme




