跳到主要内容

内存优化策略

问题

Android 应用有哪些系统化的内存优化策略?

答案

整体优化策略

1. 减少内存分配

// ❌ 循环中创建大量临时对象
fun formatList(items: List<Item>): List<String> {
return items.map { "${it.name} - ${it.price}" } // 每次 map 创建 String
}

// ✅ StringBuilder 复用
fun formatList(items: List<Item>): List<String> {
val sb = StringBuilder()
return items.map { item ->
sb.clear()
sb.append(item.name).append(" - ").append(item.price)
sb.toString()
}
}

// ✅ 使用 SparseArray 替代 HashMap<Int, V>(减少自动装箱)
val map = SparseArray<User>()
// 而非 HashMap<Int, User>()

2. 响应系统内存警告

class MyApp : Application(), ComponentCallbacks2 {
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
when (level) {
TRIM_MEMORY_UI_HIDDEN -> {
// 应用进入后台,释放 UI 相关资源
clearImageCache()
}
TRIM_MEMORY_RUNNING_LOW,
TRIM_MEMORY_RUNNING_CRITICAL -> {
// 系统内存紧张
clearAllCaches()
}
TRIM_MEMORY_COMPLETE -> {
// 系统即将杀死后台进程
releaseEverything()
}
}
}
}

3. LruCache 控制缓存

// 基于可用内存的 1/8 作为缓存大小
val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
val cacheSize = maxMemory / 8

val bitmapCache = object : LruCache<String, Bitmap>(cacheSize) {
override fun sizeOf(key: String, bitmap: Bitmap): Int {
return bitmap.byteCount / 1024
}

override fun entryRemoved(evicted: Boolean, key: String, oldValue: Bitmap, newValue: Bitmap?) {
// 可以将被淘汰的 Bitmap 加入复用池
}
}

4. 使用 @JvmStaticobject 减少对象分配

// ✅ companion object 中的常量不会创建额外对象
class Constants {
companion object {
@JvmStatic val MAX_RETRY = 3
}
}

优化效果验证

# 前后对比内存
adb shell dumpsys meminfo com.example.app

# 或使用 Macrobenchmark
@Test
fun memoryBenchmark() = macrobenchmarkRule.measureRepeated(
metrics = listOf(MemoryUsageMetric(MemoryUsageMetric.Mode.Max)),
iterations = 5
) {
startActivityAndWait()
// 操作场景
}

常见面试问题

Q1: ArrayMap / SparseArray 和 HashMap 的区别?

答案

特性HashMapArrayMap / SparseArray
数据结构数组 + 链表/红黑树两个数组
内存占用大(Entry 对象开销)
查找速度O(1)O(log n)(二分查找)
适用场景大量数据千条以内的小数据

Android 官方建议在数据量 < 1000 时使用 ArrayMap/SparseArray。

Q2: 如何检测内存抖动?

答案

内存抖动是指短时间内大量分配并回收对象,导致频繁 GC。

检测方法:

  1. Memory Profiler 中观察锯齿状内存曲线
  2. 使用 Allocation Tracking 定位高频分配点
  3. Systrace/Perfetto 中检查 GC 频率

常见原因:循环中创建对象、onDraw() 中分配对象。

相关链接