内存溢出(Out Of Memory, OOM)是 Android 开发中常见且棘手的问题,尤其在处理大图、复杂数据或内存泄漏时。本文将通过 Kotlin 代码示例 和工具使用,提供一套比较完整的排查与优化方案。
一、检测工具:定位内存问题根源
1. Android Profiler 实时监控
-
操作流程:
- 打开 Android Studio → Run App → 点击底部 Profiler 选项卡。
- 选择 Memory 模块,观察内存波动,点击 Dump Heap 生成堆快照。
- 使用 Allocation Tracking 记录对象分配。
-
关键指标:
- Java Heap:关注持续增长的未回收对象。
- Native Heap:排查 JNI 或第三方库泄漏。
2. 使用 LeakCanary 自动化检测
- 集成与使用:
LeakCanary 会自动检测// build.gradle dependencies { debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.12' } // Application 类中初始化 class MyApp : Application() { override fun onCreate() { super.onCreate() if (LeakCanary.isInAnalyzerProcess(this)) return LeakCanary.config = LeakCanary.config.copy(retainedVisibleThreshold = 1) } }
Activity
/Fragment
泄漏并生成报告。
二、代码陷阱:Kotlin 中常见内存泄漏场景
1. Lambda 与匿名内部类泄漏
-
错误代码:
class MyActivity : AppCompatActivity() { private val heavyData = HeavyData() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 匿名内部类隐式持有 MyActivity 引用 button.setOnClickListener { heavyData.process() // 导致 Activity 无法回收 } } }
-
修复方案:
class SafeClickListener( private val weakActivity: WeakReference<MyActivity> ) : View.OnClickListener { override fun onClick(v: View?) { weakActivity.get()?.heavyData?.process() } } // 在 Activity 中使用 button.setOnClickListener(SafeClickListener(WeakReference(this)))
2. 静态变量持有 Context 或 View
-
错误代码:
object GlobalHolder { var currentActivity: Activity? = null // 静态变量持有 Activity } class MyActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) GlobalHolder.currentActivity = this // 导致泄漏 } }
-
修复方案:
object GlobalHolder { private val weakContext = WeakReference<Context>(null) fun setContext(context: Context) { weakContext.clear() weakContext.get() = context.applicationContext // 使用 Application Context } }
三、优化实践:关键代码示例
1. Bitmap 内存优化
- 加载大图的正确方式:
fun loadOptimizedBitmap(context: Context, resId: Int): Bitmap { val options = BitmapFactory.Options().apply { inJustDecodeBounds = true // 先读取尺寸 BitmapFactory.decodeResource(context.resources, resId, this) inSampleSize = calculateInSampleSize(this, 300, 300) // 计算缩放比例 inJustDecodeBounds = false inPreferredConfig = Bitmap.Config.RGB_565 // 减少内存占用 50% } return BitmapFactory.decodeResource(context.resources, resId, options) } private fun calculateInSampleSize(options: Bitmap.Options, reqWidth: Int, reqHeight: Int): Int { val (height, width) = options.run { outHeight to outWidth } var inSampleSize = 1 if (height > reqHeight || width > reqWidth) { val halfHeight = height / 2 val halfWidth = width / 2 while (halfHeight / inSampleSize >= reqHeight && halfWidth / inSampleSize >= reqWidth) { inSampleSize *= 2 } } return inSampleSize }
2. 使用协程避免生命周期泄漏
- ViewModel 中正确使用协程:
class MyViewModel : ViewModel() { private val _data = MutableStateFlow<List<Data>>(emptyList()) val data: StateFlow<List<Data>> = _data fun loadData() { viewModelScope.launch { // 自动跟随 ViewModel 生命周期 val result = withContext(Dispatchers.IO) { fetchDataFromNetwork() // 模拟耗时操作 } _data.value = result } } }
3. 集合与缓存清理
- 及时释放无用数据:
class ImageCache { private val cache = LruCache<String, Bitmap>(maxMemory / 8) // LRU 缓存 fun addBitmap(key: String, bitmap: Bitmap) { cache.put(key, bitmap) } fun clearUnused() { cache.evictAll() // 主动清理 } } // 在 Activity 的 onDestroy() 中调用 override fun onDestroy() { super.onDestroy() imageCache.clearUnused() }
四、高级技巧:Native 内存与性能分析
1. 追踪 Native 内存泄漏
- 使用 Android Profiler 的 Native Memory 跟踪:
- 连接设备并启动 Profiler。
- 进入 Memory 模块,选择 Native Memory。
- 点击 Start Recording 记录 Native 内存分配。
2. 启用 StrictMode 检测
- 在 Application 中配置:
class MyApp : Application() { override fun onCreate() { super.onCreate() StrictMode.setVmPolicy( StrictMode.VmPolicy.Builder() .detectActivityLeaks() // Activity 泄漏 .detectLeakedClosableObjects() // 未关闭的 Closeable .penaltyLog() .build() ) } }
五、完整流程图:OOM 排查步骤
1. 复现问题 → 2. Android Profiler 监控内存 → 3. 生成 Heap Dump → 4. 分析大对象/重复对象 →
5. LeakCanary 检测泄漏 → 6. 检查代码陷阱(静态变量/Lambda/Bitmap) →
7. 优化数据结构/分页加载 → 8. 回归测试验证
六、总结与最佳实践
- 关键原则:
- 早检测:集成 LeakCanary,开发阶段发现问题。
- 轻量化:使用 RGB_565 Bitmap、SparseArray 等优化内存。
- 生命周期感知:通过
viewModelScope
/lifecycleScope
管理协程。 - 及时释放:在
onDestroy()
中清理集合、关闭资源。
通过工具分析、代码审查与优化策略的结合,可显著降低 OOM 发生概率。建议在代码 Review 中重点关注静态变量、匿名内部类和大图处理,同时定期使用 Profiler 进行性能巡检。