Appearance
Android 内存管理深度指南
本章节涵盖: Android 内存模型、ART 虚拟机内存管理、LruCache、MemoryCache、Bitmap 内存管理、内存泄漏检测、内存优化技巧及面试考点。建议配合实际项目练习,深入理解内存管理的底层原理。
一、Android 内存模型
1.1 内存区域划分
Android 基于 Linux 内核,其内存管理继承并扩展了 Linux 的虚拟内存机制。理解 Android 内存区域划分是掌握内存管理的基础。
1.1.1 物理内存与虚拟内存
物理内存(Physical Memory):设备实际的 RAM 芯片,是真正的硬件资源。
虚拟内存(Virtual Memory):操作系统为每个进程提供的独立地址空间,进程看到的地址是虚拟地址,通过页表映射到物理地址。
Android 进程虚拟地址空间示意图(64 位系统):
┌─────────────────────────────────────────────┐
│ 高地址区域 │
├─────────────────────────────────────────────┤
│ 内核空间 (Kernel Space) │ ← 不可访问
│ - 内核代码 │
│ - 内核数据结构 │
│ - 设备映射 │
├─────────────────────────────────────────────┤
│ 用户空间 (User Space) │
├─────────────────────────────────────────────┤
│ │ 堆 (Heap) │ ← 动态分配
│ │ - Java 对象 │
│ │ - Native 堆内存 │
├─────────────────────────────────────────────┤
│ │ 共享库 (Shared Libraries) │
│ │ - ART 运行时库 │
│ │ - 系统库 │
├─────────────────────────────────────────────┤
│ │ 数据段 (Data Segment) │
│ │ - 全局变量 │
│ │ - 静态变量 │
├─────────────────────────────────────────────┤
│ │ 代码段 (Code Segment / Text) │
│ │ - 可执行代码 │
├─────────────────────────────────────────────┤
│ │ 栈 (Stack) │ ← 线程私有
│ │ - 局部变量 │
│ │ - 方法调用栈帧 │
│ │ - 寄存器上下文 │
├─────────────────────────────────────────────┤
│ 低地址区域 │
└─────────────────────────────────────────────┘1.1.2 关键内存区域详解
堆内存(Heap)
堆是 Java 对象和 Native 对象的主要存放区域,由垃圾回收器(GC)管理。
kotlin
// Java 堆内存示例
class User {
var name: String? = null
var email: String? = null
var friends: List<User> = mutableListOf()
}
// 创建对象时,User 实例分配在堆上
val user = User()
user.name = "Alice" // String 对象也在堆上栈内存(Stack)
每个线程都有独立的栈,存储局部变量和方法调用信息。
kotlin
fun calculate(data: List<Int>): Int {
var sum = 0 // 局部变量在栈上
for (item in data) {
sum += item // 循环变量在栈上
}
return sum
}
// 调用栈帧结构:
// +------------------+
// | 返回地址 |
// | 参数 data |
// | 局部变量 sum |
// | 局部变量 item |
// +------------------+共享库(Shared Libraries)
存放系统库和应用的 Native 库,被多个进程共享。
共享库示例:
- libart.so (ART 虚拟机库)
- libc.so (C 标准库)
- libdvm.so (Dalvik 虚拟机库 - 旧版本)
- libsqlite3.so (SQLite 数据库)
- 应用的 .so 文件1.2 Android 内存限制
Android 对每个进程的内存使用有限制,理解这些限制对开发至关重要。
1.2.1 内存限制机制
kotlin
// 获取应用最大内存(KB)
val maxMemory = Runtime.getRuntime().maxMemory()
Log.d("Memory", "Max memory: ${maxMemory / 1024} KB")
// 获取实际可用内存(KB)
val memInfo = ActivityManager.MemoryInfo()
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
activityManager.getMemoryInfo(memInfo)
val availableMemory = memInfo.availMem / 1024
Log.d("Memory", "Available memory: ${availableMemory} KB")1.2.2 不同设备的内存限制
| 设备类型 | 典型内存限制 | 备注 |
|---|---|---|
| 低端设备 | 15-25 MB | 32 位架构 |
| 中端设备 | 25-50 MB | 64 位架构 |
| 高端设备 | 50-128 MB | 64 位架构,大内存模式 |
| 平板 | 64-256 MB | 更宽松的内存限制 |
kotlin
// 根据设备类型获取合适的图片加载大小
fun getTargetSize(context: Context): Int {
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val memInfo = ActivityManager.MemoryInfo()
activityManager.getMemoryInfo(memInfo)
// 根据总内存判断设备等级
val totalMem = memInfo.totalMem
return when {
totalMem < 512L * 1024 * 1024 -> 320 * 320 // 低端
totalMem < 1024L * 1024 * 1024 -> 640 * 640 // 中端
else -> 1024 * 1024 // 高端
}
}1.2.3 内存警告与 OOM
当应用内存使用超过限制时,系统会触发内存警告或 OOM(Out Of Memory)错误。
kotlin
class MemoryWarningReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == Intent.ACTION_MEMORY_USAGE_CHANGED) {
// 收到内存警告,需要释放内存
releaseMemory()
}
}
private fun releaseMemory() {
// 1. 清除缓存
imageCache.clear()
// 2. 释放 Bitmap
bitmap?.recycle()
bitmap = null
// 3. 停止后台任务
backgroundTask.cancel()
// 4. 释放单例引用
singleton?.close()
}
}Manifest 配置内存警告接收器:
xml
<receiver android:name=".MemoryWarningReceiver">
<intent-filter>
<action android:name="android.intent.action.MEMORY_USAGE_CHANGED" />
</intent-filter>
</receiver>1.3 内存分配策略
1.3.1 堆内存分配
TLAB(Thread Local Allocation Buffer)
ART 为每个线程分配本地分配缓冲区,减少多线程分配时的锁竞争。
TLAB 分配流程:
1. 线程需要分配对象
2. 首先从 TLAB 分配(无锁)
3. TLAB 耗尽时,从主堆分配并填充 TLAB
4. 主堆耗尽时触发 GC对象分配优化
kotlin
// 优化前:频繁的临时对象创建
fun process(data: List<Int>): List<Int> {
val result = mutableListOf<Int>()
for (item in data) {
// 每次都创建新的对象
val temp = TempObject(item)
val doubled = temp.process()
result.add(doubled)
}
return result
}
// 优化后:对象复用
class TempObject {
var value: Int = 0
fun process(): Int {
value *= 2
return value
}
fun reset(value: Int) {
this.value = value
}
}
fun processOptimized(data: List<Int>): List<Int> {
val result = mutableListOf<Int>()
val temp = TempObject() // 复用同一个对象
for (item in data) {
temp.reset(item)
result.add(temp.process())
}
return result
}1.4 内存统计与监控
1.4.1 运行时内存信息
kotlin
class MemoryMonitor {
companion object {
private val runtime = Runtime.getRuntime()
fun logMemoryInfo(tag: String = "Memory") {
val used = runtime.totalMemory() - runtime.freeMemory()
val total = runtime.totalMemory()
val max = runtime.maxMemory()
Log.d(tag, """
|Heap 使用统计:
| Used: ${(used / 1024 / 1024)} MB
| Total: ${(total / 1024 / 1024)} MB
| Max: ${(max / 1024 / 1024)} MB
| Usage: ${String.format("%.2f", (used.toDouble() / max) * 100)}%
""".trimMargin())
}
}
}1.4.2 系统级内存监控
kotlin
class SystemMemoryMonitor(private val context: Context) {
private val activityManager =
context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
fun logSystemMemory() {
val memInfo = ActivityManager.MemoryInfo()
activityManager.getMemoryInfo(memInfo)
val totalMem = memInfo.totalMem / 1024 / 1024
val availMem = memInfo.availMem / 1024 / 1024
val lowMemory = memInfo.lowMemory
Log.d("SystemMemory", """
|系统内存状态:
| Total: $totalMem MB
| Available: $availMem MB
| Low Memory: $lowMemory
""".trimMargin())
}
}二、Dalvik/ART虚拟机内存管理
2.1 Dalvik vs ART
Android 4.4 之前使用 Dalvik 虚拟机,4.4 开始引入 ART(Android Runtime)作为可选项,5.0 之后 ART 成为唯一运行时。
2.1.1 核心差异
| 特性 | Dalvik | ART |
|---|---|---|
| 执行方式 | JIT(即时编译) | AOT(提前编译)+ JIT |
| 启动速度 | 较慢 | 更快 |
| 运行时性能 | 一般 | 更优 |
| 安装速度 | 快 | 慢(需要编译) |
| 内存占用 | 较低 | 较高 |
| GC 策略 | 复制算法为主 | 并发标记清理 |
Dalvik 执行流程:
Smali 代码 → JIT 编译 → 机器码 → 执行
ART 执行流程:
Smali 代码 → AOT 编译 → 机器码 → 执行
↓
JIT 优化(运行时)2.1.2 ART 架构
ART 运行时架构:
┌──────────────────────────────────────────┐
│ 应用层 │
│ - Java/Kotlin 代码 │
├──────────────────────────────────────────┤
│ ART 运行时 │
│ ├─ 编译器 │
│ │ ├─ AOT 编译器(安装时) │
│ │ └─ JIT 编译器(运行时) │
│ ├─ 垃圾回收器 │
│ │ ├─ 分代 GC │
│ │ └─ 并发 GC │
│ ├─ 类加载器 │
│ └─ 运行时库 │
├──────────────────────────────────────────┤
│ Android 框架层 │
├──────────────────────────────────────────┤
│ Linux 内核 │
├──────────────────────────────────────────┤
│ 硬件抽象层 (HAL) │
├──────────────────────────────────────────┤
│ 硬件层 │
└──────────────────────────────────────────┘2.2 ART 内存布局
2.2.1 堆内存分区
ART 将堆内存划分为多个区域,每个区域有不同的管理策略。
ART 堆内存布局:
┌─────────────────────────────────────┐
│ 对象空间 (Object Space) │
├─────────────────────────────────────┤
│ 老年代 (Old Generation) │ ← 长生命周期对象
│ - 标记清理算法 │
│ - 并发收集 │
├─────────────────────────────────────┤
│ 新生代 (Young Generation) │ ← 短生命周期对象
│ ├─ 分配缓冲区 (Allocation Buffer) │
│ └─ 非分配缓冲区 (Non-Alloc Buffer) │
├─────────────────────────────────────┤
│ 原生堆 (Native Heap) │
│ - 线程栈 │
│ - JNI 对象 │
│ - Native 库数据 │
├─────────────────────────────────────┤
│ 线程本地分配缓冲区 (TLAB) │
├─────────────────────────────────────┤
│ 类空间 (Class Space) │
│ - 类元数据 │
│ - 方法代码 │
└─────────────────────────────────────┘2.2.2 分代垃圾回收
ART 使用分代垃圾回收策略,根据对象生命周期划分不同区域。
新生代(Young Generation)
存放新创建的对象,大部分对象在此区域死亡。
kotlin
// 新生代特点:
// - 对象存活时间短
// - 使用复制算法
// - 回收速度快
// - Stop-The-World 时间短
// 示例:临时对象在新生代创建和销毁
fun createTempObjects(): List<Int> {
val tempObjects = mutableListOf<Int>()
for (i in 1..1000) {
tempObjects.add(i) // 大量短生命周期对象
}
// 方法结束后,这些对象可以被快速回收
return tempObjects
}老年代(Old Generation)
存放长期存活的对象,经过多次 GC 后晋升到老年代。
kotlin
// 老年代特点:
// - 对象存活时间长
// - 使用标记清理或标记整理算法
// - 回收速度慢但频率低
// - 支持并发收集
// 示例:长期存活的对象
class ApplicationData {
private val userCache = WeakHashMap<String, UserData>()
private val config: Config = Config.load()
// 这些对象生命周期与应用相同,会晋升到老年代
fun getUser(key: String): UserData? {
return userCache[key]
}
}2.3 垃圾回收机制
2.3.1 GC 算法
ART 实现了多种 GC 算法:
1. 复制算法(Copying)
用于新生代,将存活的对象复制到新的空间。
复制算法流程:
Eden 区 Survivor 区 A Survivor 区 B
┌──────┐ ┌──────┐ ┌──────┐
│ 新对象 │ │ 空 │ │ 空 │
└──────┘ └──────┘ └──────┘
↓ GC 后
┌──────┐ ┌──────┐ ┌──────┐
│ 空 │ │ 存活 │ │ 空 │
└──────┘ └──────┘ └──────┘2. 标记清理(Mark-Sweep)
用于老年代,标记存活对象,清理未标记对象。
标记清理流程:
Step 1: 标记阶段
┌──────┐ ┌──────┐ ┌──────┐
│ √ 对象 │ │ × 对象 │ │ √ 对象 │ → 标记存活
└──────┘ └──────┘ └──────┘
Step 2: 清理阶段
┌──────┐ ┌──────┐ ┌──────┐
│ 对象 │ │ 空 │ │ 对象 │ → 回收未标记
└──────┘ └──────┘ └──────┘3. 标记整理(Mark-Compact)
标记存活对象,并将它们移动到一端,解决内存碎片问题。
标记整理流程:
Step 1: 标记
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│ √ 对象 │ │ × 对象 │ │ √ 对象 │ │ × 对象 │
└──────┘ └──────┘ └──────┘ └──────┘
Step 2: 整理
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│ 对象 │ │ 对象 │ │ 空 │ │ 空 │
└──────┘ └──────┘ └──────┘ └──────┘2.3.2 GC 类型
ART 提供多种 GC 类型,针对不同场景优化。
1. Concurrent Copying GC
并发复制垃圾回收,在后台线程执行大部分工作。
kotlin
// 适用于新生代,大部分工作并发执行
// 减少主线程停顿时间2. Concurrent Mark Compact GC
并发标记整理垃圾回收,解决内存碎片问题。
kotlin
// 适用于老年代,支持并发标记和整理
// 避免 Stop-The-World 长时间停顿3. Background Mark Compact GC
后台标记整理垃圾回收,完全在后台执行。
kotlin
// 完全后台执行,对应用性能影响最小
// 但可能产生内存碎片2.3.3 GC 触发条件
kotlin
// GC 可能触发的场景:
// 1. 堆内存不足
fun allocateLargeObject() {
val data = ByteArray(10 * 1024 * 1024) // 分配 10MB
// 如果堆空间不足,触发 GC
}
// 2. 手动请求(不保证立即执行)
fun requestGC() {
System.gc() // 建议 JVM 执行 GC
// 注意:这只是建议,JVM 可能忽略
}
// 3. 分配失败
fun createObjectsUntilGC() {
val objects = mutableListOf<Any>()
while (true) {
objects.add(Any())
// 分配失败时触发 GC
}
}
// 4. 弱引用对象
class WeakContainer {
val weakRef = WeakReference(Any())
// GC 时,弱引用对象可能被回收
}
// 5. 软引用对象(内存不足时)
class SoftContainer {
val softRef = SoftReference(Any())
// 内存不足时,软引用对象优先被回收
}
// 6. 虚引用对象(用于通知)
class VirtualContainer : ReferenceQueue<Any>() {
val virtualRef = PhantomReference(Any(), this)
// GC 后,虚引用被加入队列,可以收到通知
}2.4 内存屏障与可见性
2.4.1 内存屏障
内存屏障确保多线程环境下的内存可见性。
kotlin
// 内存屏障类型:
// 1. LoadLoad 屏障
// 2. StoreStore 屏障
// 3. LoadStore 屏障
// 4. StoreLoad 屏障
// volatile 关键字包含内存屏障
class Counter {
@Volatile var count: Int = 0
fun increment() {
count++ // 包含 LoadStore 和 StoreLoad 屏障
}
}2.4.2 并发问题示例
kotlin
// 错误示例:没有内存屏障
class BadCounter {
var count: Int = 0 // 没有 volatile
fun increment() {
count++ // 不是原子操作
}
}
// 正确示例:使用 volatile 和原子操作
class GoodCounter {
@Volatile var count: Int = 0
fun increment() {
count = count + 1 // volatile 保证可见性
}
}
// 更好的示例:使用原子类
class BestCounter {
private val count = AtomicInteger(0)
fun increment() {
count.incrementAndGet() // 原子操作
}
fun getCount(): Int {
return count.get()
}
}2.5 ART 优化技术
2.5.1 Profile 引导优化
ART 收集运行时性能配置文件,优化热点代码。
kotlin
// Profile 收集:
// 1. 运行应用
// 2. ART 记录方法调用频率
// 3. 下次安装时使用 Profile 优化
// 生成 Profile 文件
adb shell dumpsys gfxinfo <package_name>
// 应用 Profile
apksigner sign --ks key.jks --profile profile.txt app.apk2.5.2 方法内联优化
ART 将频繁调用的小方法内联到调用处。
kotlin
// 适合内联的小方法
class Calculator {
fun add(a: Int, b: Int): Int {
return a + b // 可能被内联
}
fun calculate(x: Int, y: Int, z: Int): Int {
// add 方法可能被内联到这里
return add(x, add(y, z))
}
}
// 不适合内联的大方法
class Processor {
fun process(data: List<Int>): List<Int> {
// 方法体较大,不会被内联
return data.map { it * 2 }
}
}2.5.3 逃逸分析
ART 分析对象是否逃逸出方法,决定分配策略。
kotlin
// 不逃逸的对象(可栈分配)
fun createLocalObject(): Int {
val local = LocalObject()
local.value = 42
return local.value
}
class LocalObject {
var value: Int = 0
}
// 逃逸的对象(必须堆分配)
fun createEscapedObject(): LocalObject {
val escaped = LocalObject()
escaped.value = 42
return escaped // 返回给调用者,逃逸
}三、LruCache 原理和使用
3.1 LruCache 概述
LruCache 是 Android 提供的高效内存缓存实现,基于 LinkedHashMap 和 LRU(Least Recently Used,最近最少使用)算法。
3.1.1 核心特性
- 自动管理内存:根据最大容量自动淘汰最久未使用的条目
- 线程安全:所有方法都进行了同步处理
- 高性能:O(1) 时间复杂度的 put 和 get 操作
- 容量可控:通过 maxSize 参数控制缓存大小
3.1.2 LRU 算法原理
LRU 算法核心思想:
1. 最近使用的数据放在缓存的头部
2. 最久未使用的数据放在缓存的尾部
3. 当缓存满时,从尾部淘汰数据
示例:容量为 3 的 LRU 缓存
初始状态:[]
put(1, "A") → [1-A]
put(2, "B") → [1-A, 2-B]
put(3, "C") → [1-A, 2-B, 3-C]
get(1) → [2-B, 3-C, 1-A] // 访问 1,移到头部
put(4, "D") → [3-C, 1-A, 4-D] // 淘汰 2-B3.2 LruCache 源码分析
3.2.1 核心数据结构
java
public class LruCache<K, V> {
// 最大容量(单位由构造函数决定)
private final int maxSize;
// 当前缓存大小
private long size;
// 条目数量
private int entries;
// 核心数据结构:LinkedHashMap
// accessOrder=true 表示按访问顺序排序
// 实现了 LRU 淘汰策略
private final LinkedHashMap<K, V> map;
// 同步锁
private final Object lock = new Object();
}3.2.2 LinkedHashMap 实现
java
// LinkedHashMap 关键特性
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V> {
// 双向链表节点
private static class Entry<K,V> extends HashMap.Node<K,V> {
Entry<K,V> before, after;
}
// accessOrder=true 表示按访问顺序
// 用于实现 LRU 策略
private final boolean accessOrder;
// put 方法:更新访问顺序
V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean eviction) {
// ... 省略部分代码
if (accessOrder) {
afterLast(e); // 将节点移到链表尾部
}
// ...
}
// get 方法:更新访问顺序
V get(Object key) {
V value = super.get(key);
if (accessOrder && value != null) {
afterLast(getNode(key)); // 更新访问顺序
}
return value;
}
}3.2.3 淘汰机制
java
// 检查是否需要淘汰条目
void trimToSize(int maxSize) {
while (true) {
LinkedHashMap.Entry<K, V> toEvict = map.eldest();
if (toEvict == null) {
break; // 缓存未满
}
// 移除最旧的条目
map.remove(toEvict.getKey());
size -= sizeOf(toEvict.getKey(), toEvict.getValue());
entries--;
// 允许子类自定义回收逻辑
entryRemoved(true, toEvict.getKey(), toEvict.getValue(),
map.get(toEvict.getKey()));
if (size <= maxSize) {
break;
}
}
}3.3 LruCache 使用指南
3.3.1 基本使用
kotlin
// 1. 创建 LruCache 实例
class ImageCache : LruCache<String, Bitmap>(16) {
// maxSize = 16MB
override fun sizeOf(key: String, value: Bitmap): Int {
// 返回条目实际占用的内存大小
return value.byteCount
}
override fun entryRemoved(
evicted: Boolean,
key: String,
oldValue: Bitmap,
newValue: Bitmap?
) {
// 条目被移除时的回调
if (evicted) {
oldValue.recycle() // 释放 Bitmap 内存
}
}
}
// 2. 使用缓存
val imageCache = ImageCache()
val bitmap = loadBitmapFromNetwork(url)
imageCache.put(url, bitmap)
// 3. 获取缓存
val cachedBitmap = imageCache.get(url)3.3.2 计算合适的缓存大小
kotlin
object CacheSizeCalculator {
/**
* 计算合适的 LruCache 大小
* 通常使用系统最大内存的 1/8 到 1/16
*/
fun calculateMaxCacheSize(context: Context): Int {
val runtime = Runtime.getRuntime()
val maxMemory = runtime.maxMemory()
// 获取可用内存
val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val memInfo = ActivityManager.MemoryInfo()
activityManager.getMemoryInfo(memInfo)
val availableMemory = memInfo.availMem
// 使用较小值:最大内存的 1/8 或可用内存的 50%
return minOf(
maxMemory / 8,
availableMemory / 2
).toInt()
}
}
// 使用
val maxCacheSize = CacheSizeCalculator.calculateMaxCacheSize(context)
val imageCache = object : LruCache<String, Bitmap>(maxCacheSize) {
override fun sizeOf(key: String, value: Bitmap): Int {
return value.byteCount
}
}3.3.3 高级使用场景
场景 1:图片缓存
kotlin
class ImageCache(private val context: Context) {
private val cache: LruCache<String, Bitmap>
init {
val maxMemory = Runtime.getRuntime().maxMemory() / 8
cache = object : LruCache<String, Bitmap>(maxMemory) {
override fun sizeOf(key: String, value: Bitmap): Int {
return value.byteCount
}
override fun entryRemoved(
evicted: Boolean,
key: String,
oldValue: Bitmap,
newValue: Bitmap?
) {
if (evicted && !oldValue.isRecycled) {
oldValue.recycle()
}
}
}
}
fun getBitmap(url: String): Bitmap? {
return cache.get(url)
}
fun putBitmap(url: String, bitmap: Bitmap) {
cache.put(url, bitmap)
}
fun clear() {
cache.evictAll()
}
fun size(): Int {
return cache.size()
}
fun cacheSize(): Long {
return cache.size
}
}场景 2:数据缓存
kotlin
class DataCache {
private val cache = object : LruCache<String, Data>(10 * 1024 * 1024) {
override fun sizeOf(key: String, value: Data): Int {
// 计算 Data 对象的大小
return value.estimatedSize()
}
}
inner class Data(
val id: String,
val name: String,
val description: String
) {
fun estimatedSize(): Int {
return id.length + name.length + description.length
}
}
fun getData(id: String): Data? {
return cache.get(id)
}
fun putData(id: String, data: Data) {
cache.put(id, data)
}
}场景 3:多级别缓存
kotlin
class MultiLevelCache {
// L1: 内存缓存(最快)
private val memoryCache = LruCache<String, Any>(10 * 1024 * 1024) {
override fun sizeOf(key: String, value: Any): Int {
return value.estimatedSize()
}
}
// L2: 磁盘缓存(持久化)
private val diskCache: DiskLruCache
init {
val cacheDir = ContextUtil.cacheDir / "disk"
diskCache = DiskLruCache.open(cacheDir, 1, 1, 50 * 1024 * 1024)
}
fun get(key: String): Any? {
// 先查内存
var result = memoryCache.get(key)
if (result != null) return result
// 再查磁盘
result = readFromDisk(key)
if (result != null) {
memoryCache.put(key, result)
}
return result
}
fun put(key: String, value: Any) {
memoryCache.put(key, value)
writeToDisk(key, value)
}
private fun readFromDisk(key: String): Any? {
// 实现从磁盘读取
return null
}
private fun writeToDisk(key: String, value: Any) {
// 实现写入磁盘
}
}3.4 LruCache 最佳实践
3.4.1 容量设置原则
kotlin
// 原则 1:不超过系统最大内存的 1/8
val maxSize = Runtime.getRuntime().maxMemory() / 8
// 原则 2:考虑设备内存差异
val maxCacheSize = when {
isLowEndDevice() -> 10 * 1024 * 1024 // 10MB
isMidRangeDevice() -> 20 * 1024 * 1024 // 20MB
else -> 30 * 1024 * 1024 // 30MB
}
// 原则 3:根据缓存类型调整
val bitmapCacheSize = maxMemory / 8 // 图片缓存
val dataCacheSize = maxMemory / 16 // 数据缓存
val networkCacheSize = maxMemory / 32 // 网络缓存3.4.2 内存管理
kotlin
class ManagedCache : LruCache<String, Bitmap>(16 * 1024 * 1024) {
override fun sizeOf(key: String, value: Bitmap): Int {
return value.byteCount
}
override fun entryRemoved(
evicted: Boolean,
key: String,
oldValue: Bitmap,
newValue: Bitmap?
) {
// 确保 Bitmap 被正确回收
if (evicted && !oldValue.isRecycled) {
oldValue.recycle()
}
}
override fun beforeEviction(maxSize: Int, size: Int): Int {
// 在淘汰前调用,可以调整最大容量
return maxSize
}
// 主动释放缓存
fun releaseMemory() {
evictAll()
}
}3.4.3 线程安全
kotlin
// LruCache 是线程安全的,但需要注意:
// 1. put 和 get 操作是原子的
val bitmap = cache.get(url) ?: loadBitmap(url).also {
cache.put(url, it)
}
// 2. 批量操作需要加锁
synchronized(cache) {
cache.evictAll()
cache.putAll(newCacheItems)
}
// 3. 统计操作
val cacheStats = StatsHolder()
synchronized(cache) {
cacheStats.size = cache.size()
cacheStats.cacheSize = cache.size
}3.5 自定义 LruCache 扩展
3.5.1 添加过期时间
kotlin
class ExpiringLruCache<K, V>(maxSize: Int) : LruCache<K, V>(maxSize) {
private data class CacheEntry<T>(
val value: T,
val expireTime: Long
)
private val expireTimes = ConcurrentHashMap<K, Long>()
fun putWithExpiry(key: K, value: V, expireSeconds: Long) {
val expireTime = System.currentTimeMillis() + expireSeconds * 1000
expireTimes[key] = expireTime
super.put(key, value)
}
override fun get(key: K): V? {
val expireTime = expireTimes[key]
if (expireTime != null && System.currentTimeMillis() > expireTime) {
// 已过期,移除
remove(key)
expireTimes.remove(key)
return null
}
return super.get(key)
}
fun isExpired(key: K): Boolean {
val expireTime = expireTimes[key]
return expireTime != null && System.currentTimeMillis() > expireTime
}
fun clearExpired() {
val expiredKeys = expireTimes.entries
.filter { System.currentTimeMillis() > it.value }
.map { it.key }
for (key in expiredKeys) {
remove(key)
expireTimes.remove(key)
}
}
}3.5.2 添加统计功能
kotlin
class StatsLruCache<K, V>(maxSize: Int) : LruCache<K, V>(maxSize) {
private var hitCount = 0L
private var missCount = 0L
private val lock = Object()
override fun get(key: K): V? {
val value = super.get(key)
synchronized(lock) {
if (value != null) {
hitCount++
} else {
missCount++
}
}
return value
}
fun getStats(): CacheStats {
synchronized(lock) {
return CacheStats(
hits = hitCount,
misses = missCount,
hitRate = if (hitCount + missCount > 0) {
hitCount.toDouble() / (hitCount + missCount)
} else 0.0
)
}
}
data class CacheStats(
val hits: Long,
val misses: Long,
val hitRate: Double
)
}四、MemoryCache 实现
4.1 MemoryCache 设计模式
MemoryCache 是基于 LruCache 的高级缓存实现,提供额外的功能如过期时间、优先级等。
4.1.1 基本设计
kotlin
interface MemoryCache<K, V> {
// 基本操作
fun get(key: K): V?
fun put(key: K, value: V): V?
fun remove(key: K): V?
// 批量操作
fun putAll(map: Map<K, V>)
fun removeAll(keys: Collection<K>)
// 统计信息
val size: Int
val cacheSize: Long
// 管理操作
fun clear()
fun evictAll()
}4.1.2 完整实现
kotlin
class SimpleMemoryCache<K, V>(
private val maxSize: Long
) : MemoryCache<K, V> {
private val cache = LinkedHashMap<K, V>(16, 0.75f, true)
private var currentSize = 0L
private val sizeCalculator: (K, V) -> Long
init {
// 默认大小计算
this.sizeCalculator = { _, value ->
when (value) {
is Bitmap -> value.byteCount.toLong()
is ByteArray -> value.size.toLong()
else -> Runtime.getRuntime().objectSize(value)
}
}
}
@Synchronized
override fun get(key: K): V? {
val value = cache[key]
return value
}
@Synchronized
override fun put(key: K, value: V): V? {
val oldSize = sizeCalculator(key, value)
// 移除旧的条目
val oldValue = cache[key]
if (oldValue != null) {
currentSize -= sizeCalculator(key, oldValue)
}
// 添加新条目
cache[key] = value
currentSize += oldSize
// 淘汰旧条目
trimToSize()
return oldValue
}
@Synchronized
override fun remove(key: K): V? {
val value = cache.remove(key)
if (value != null) {
currentSize -= sizeCalculator(key, value)
}
return value
}
@Synchronized
override fun putAll(map: Map<K, V>) {
for ((key, value) in map) {
put(key, value)
}
}
@Synchronized
override fun removeAll(keys: Collection<K>) {
for (key in keys) {
remove(key)
}
}
@Synchronized
override fun clear() {
cache.clear()
currentSize = 0
}
@Synchronized
override fun evictAll() {
cache.clear()
currentSize = 0
}
override val size: Int
get() = cache.size
override val cacheSize: Long
get() = currentSize
// 淘汰策略
private fun trimToSize() {
while (currentSize > maxSize && cache.isNotEmpty()) {
// 移除最旧的条目(LRU)
val eldest = cache.keys.first()
val value = cache.remove(eldest)
currentSize -= sizeCalculator(eldest, value!!)
}
}
}4.2 高级 MemoryCache 实现
4.2.1 带过期时间的缓存
kotlin
class ExpiringMemoryCache<K, V>(
maxSize: Long,
private val defaultExpireSeconds: Long = 60 * 60 // 默认 1 小时
) : MemoryCache<K, V> {
private data class CacheEntry<T>(
val value: T,
val expireTime: Long
)
private val cache = LinkedHashMap<K, CacheEntry<V>>(16, 0.75f, true)
private val expireMap = ConcurrentHashMap<K, Long>()
private var currentSize = 0L
private val sizeCalculator: (K, V) -> Long = { _, value ->
when (value) {
is Bitmap -> value.byteCount.toLong()
else -> 1024L // 默认估计
}
}
override fun get(key: K): V? {
synchronized(this) {
val entry = cache[key]
if (entry != null && !isExpired(entry.expireTime)) {
return entry.value
}
// 过期则移除
if (entry != null) {
remove(key)
}
return null
}
}
override fun put(key: K, value: V): V? {
return putWithExpiry(key, value, defaultExpireSeconds)
}
fun putWithExpiry(key: K, value: V, expireSeconds: Long): V? {
synchronized(this) {
val expireTime = System.currentTimeMillis() + expireSeconds * 1000
val oldEntry = cache[key]
if (oldEntry != null) {
currentSize -= sizeCalculator(key, oldEntry.value)
}
val newEntry = CacheEntry(value, expireTime)
cache[key] = newEntry
expireMap[key] = expireTime
currentSize += sizeCalculator(key, value)
trimToSize()
return oldEntry?.value
}
}
override fun remove(key: K): V? {
synchronized(this) {
val entry = cache.remove(key)
expireMap.remove(key)
if (entry != null) {
currentSize -= sizeCalculator(key, entry.value)
}
return entry?.value
}
}
override fun clear() {
synchronized(this) {
cache.clear()
expireMap.clear()
currentSize = 0
}
}
override val size: Int
get() = cache.size
override val cacheSize: Long
get() = currentSize
// 清理所有过期条目
fun clearExpired() {
synchronized(this) {
val currentTime = System.currentTimeMillis()
val expiredKeys = cache.entries
.filter { it.value.expireTime < currentTime }
.map { it.key }
for (key in expiredKeys) {
val entry = cache.remove(key)
if (entry != null) {
currentSize -= sizeCalculator(key, entry.value)
}
expireMap.remove(key)
}
}
}
// 设置特定条目的过期时间
fun setExpiry(key: K, expireSeconds: Long) {
synchronized(this) {
val expireTime = System.currentTimeMillis() + expireSeconds * 1000
val entry = cache[key]
if (entry != null) {
expireMap[key] = expireTime
}
}
}
// 检查条目是否过期
fun isExpired(key: K): Boolean {
val expireTime = expireMap[key]
return expireTime != null && System.currentTimeMillis() > expireTime
}
private fun isExpired(expireTime: Long): Boolean {
return System.currentTimeMillis() > expireTime
}
private fun trimToSize() {
while (currentSize > maxSize && cache.isNotEmpty()) {
val eldest = cache.keys.first()
val entry = cache.remove(eldest)
if (entry != null) {
currentSize -= sizeCalculator(eldest, entry.value)
}
expireMap.remove(eldest)
}
}
}4.2.2 优先级缓存
kotlin
class PriorityMemoryCache<K, V>(
maxSize: Long
) : MemoryCache<K, V> {
enum class Priority {
LOW, NORMAL, HIGH
}
private data class PriorityEntry<T>(
val value: T,
val priority: Priority
)
private val lowPriorityCache = LinkedHashMap<K, V>(16, 0.75f, true)
private val normalPriorityCache = LinkedHashMap<K, V>(16, 0.75f, true)
private val highPriorityCache = LinkedHashMap<K, V>(16, 0.75f, true)
private var lowPrioritySize = 0L
private var normalPrioritySize = 0L
private var highPrioritySize = 0L
private val sizeCalculator: (K, V) -> Long = { _, value ->
when (value) {
is Bitmap -> value.byteCount.toLong()
is ByteArray -> value.size.toLong()
else -> 1024L
}
}
@Synchronized
override fun get(key: K): V? {
return highPriorityCache[key]
?: normalPriorityCache[key]
?: lowPriorityCache[key]
}
@Synchronized
override fun put(key: K, value: V): V? {
return put(key, value, Priority.NORMAL)
}
fun put(key: K, value: V, priority: Priority): V? {
synchronized(this) {
val size = sizeCalculator(key, value)
var oldValue: V? = null
return when (priority) {
Priority.LOW -> {
// 检查是否有旧条目需要移除
oldValue = removeKeyFromAllCaches(key)
addToCache(lowPriorityCache, key, value, size)
lowPrioritySize += size
trimLowPriority()
}
Priority.NORMAL -> {
oldValue = removeKeyFromAllCaches(key)
addToCache(normalPriorityCache, key, value, size)
normalPrioritySize += size
trimNormalPriority()
}
Priority.HIGH -> {
oldValue = removeKeyFromAllCaches(key)
addToCache(highPriorityCache, key, value, size)
highPrioritySize += size
trimHighPriority()
}
}
}
}
@Synchronized
override fun remove(key: K): V? {
return removeKeyFromAllCaches(key)
}
@Synchronized
override fun clear() {
lowPriorityCache.clear()
normalPriorityCache.clear()
highPriorityCache.clear()
lowPrioritySize = 0
normalPrioritySize = 0
highPrioritySize = 0
}
override val size: Int
get() = lowPriorityCache.size + normalPriorityCache.size + highPriorityCache.size
override val cacheSize: Long
get() = lowPrioritySize + normalPrioritySize + highPrioritySize
// 辅助方法
private fun removeKeyFromAllCaches(key: K): V? {
var value: V? = null
value = highPriorityCache.remove(key)?.also {
highPrioritySize -= sizeCalculator(key, it)
}
if (value == null) {
value = normalPriorityCache.remove(key)?.also {
normalPrioritySize -= sizeCalculator(key, it)
}
}
if (value == null) {
value = lowPriorityCache.remove(key)?.also {
lowPrioritySize -= sizeCalculator(key, it)
}
}
return value
}
private fun addToCache(cache: LinkedHashMap<K, V>, key: K, value: V, size: Long) {
cache[key] = value
}
// 淘汰策略:优先淘汰低优先级
private fun trimLowPriority() {
val totalSize = lowPrioritySize + normalPrioritySize + highPrioritySize
while (totalSize > maxSize && lowPriorityCache.isNotEmpty()) {
val eldest = lowPriorityCache.keys.first()
val value = lowPriorityCache.remove(eldest)
lowPrioritySize -= sizeCalculator(eldest, value!!)
}
}
private fun trimNormalPriority() {
val totalSize = lowPrioritySize + normalPrioritySize + highPrioritySize
if (totalSize > maxSize) {
trimLowPriority()
if (totalSize > maxSize && normalPriorityCache.isNotEmpty()) {
val eldest = normalPriorityCache.keys.first()
val value = normalPriorityCache.remove(eldest)
normalPrioritySize -= sizeCalculator(eldest, value!!)
}
}
}
private fun trimHighPriority() {
val totalSize = lowPrioritySize + normalPrioritySize + highPrioritySize
if (totalSize > maxSize) {
trimLowPriority()
trimNormalPriority()
if (totalSize > maxSize && highPriorityCache.isNotEmpty()) {
val eldest = highPriorityCache.keys.first()
val value = highPriorityCache.remove(eldest)
highPrioritySize -= sizeCalculator(eldest, value!!)
}
}
}
}4.3 双缓存实现
kotlin
class DoubleCache<K, V>(
memoryCacheSize: Long,
diskCacheDir: File
) {
private val memoryCache = SimpleMemoryCache<K, V>(memoryCacheSize)
private val diskCache = DiskLruCache(diskCacheDir)
fun get(key: K): V? {
// 先查内存缓存
var value = memoryCache.get(key)
if (value != null) return value
// 再查磁盘缓存
value = diskCache.read(key)?.let { deserialize(it) }
if (value != null) {
// 写入内存缓存
memoryCache.put(key, value)
}
return value
}
fun put(key: K, value: V) {
// 写入内存缓存
memoryCache.put(key, value)
// 异步写入磁盘缓存
AsyncTask.execute {
diskCache.write(key, serialize(value))
}
}
fun clear() {
memoryCache.clear()
diskCache.clear()
}
// 序列化和反序列化
private fun serialize(value: V): ByteArray {
// 实现序列化逻辑
return byteArrayOf()
}
private fun deserialize(data: ByteArray): V? {
// 实现反序列化逻辑
return null
}
}五、Bitmap 内存管理
5.1 Bitmap 基础
5.1.1 Bitmap 内存计算
kotlin
// Bitmap 内存计算公式:
// 内存 = 宽 * 高 * 每个像素的字节数
class BitmapMemoryCalculator {
companion object {
/**
* 计算 Bitmap 占用的内存大小
* @param width 宽度
* @param height 高度
* @param config 配置(决定每个像素的字节数)
*/
fun calculateBitmapSize(
width: Int,
height: Int,
config: Bitmap.Config = Bitmap.Config.ARGB_8888
): Int {
val bytesPerPixel = when (config) {
Bitmap.Config.ALPHA_8 -> 1
Bitmap.Config.ARGB_4444,
Bitmap.Config.RGB_565 -> 2
Bitmap.Config.ARGB_8888,
Bitmap.Config.RGBA_F16,
Bitmap.Config.HARDWARE -> 4
else -> 4
}
return width * height * bytesPerPixel
}
/**
* 计算 Bitmap 内存(MB)
*/
fun calculateBitmapSizeMB(
width: Int,
height: Int,
config: Bitmap.Config = Bitmap.Config.ARGB_8888
): Double {
return calculateBitmapSize(width, height, config) /
(1024.0 * 1024.0)
}
}
}
// 示例:计算不同分辨率的 Bitmap 内存占用
fun printBitmapMemoryExamples() {
val sizes = listOf(
480 to 320, // WVGA
1280 to 720, // HD
1920 to 1080, // Full HD
3840 to 2160 // 4K
)
for ((width, height) in sizes) {
val sizeBytes = BitmapMemoryCalculator.calculateBitmapSize(width, height)
val sizeMB = BitmapMemoryCalculator.calculateBitmapSizeMB(width, height)
Log.d("BitmapSize", "${width}x${height}: ${sizeBytes / 1024}KB = $sizeMB MB")
}
}5.1.2 Bitmap 配置选择
kotlin
// 不同配置的选择策略
class BitmapConfigSelector {
companion object {
/**
* 根据需求选择合适的 Bitmap 配置
*/
fun selectConfig(requirements: ConfigRequirements): Bitmap.Config {
return when {
// 只需要透明度,不需要颜色
requirements.needsTransparency && !requirements.needsColor ->
Bitmap.Config.ALPHA_8
// 需要颜色但不需要透明度,且颜色要求不高
requirements.needsColor && !requirements.needsTransparency &&
!requirements.highColorQuality ->
Bitmap.Config.RGB_565
// 需要颜色和透明度,但不需要高质量
requirements.needsColor && requirements.needsTransparency &&
!requirements.highColorQuality ->
Bitmap.Config.ARGB_4444
// 默认高质量配置
else -> Bitmap.Config.ARGB_8888
}
}
}
data class ConfigRequirements(
val needsColor: Boolean = true,
val needsTransparency: Boolean = false,
val highColorQuality: Boolean = true
)
}5.2 Bitmap 加载优化
5.2.1 解码选项
kotlin
class BitmapDecoder {
companion object {
/**
* 计算合适的采样率
*/
fun calculateSampleSize(
options: BitmapFactory.Options,
reqWidth: Int,
reqHeight: Int
): Int {
val height = options.outHeight
val width = options.outWidth
var sampleSize = 1
if (height > reqHeight || width > reqWidth) {
val halfHeight = height / 2
val halfWidth = width / 2
while (halfHeight / sampleSize >= reqHeight &&
halfWidth / sampleSize >= reqWidth) {
sampleSize *= 2
}
}
return sampleSize
}
/**
* 加载缩小后的 Bitmap
*/
fun loadScaledBitmap(
context: Context,
resourceId: Int,
reqWidth: Int,
reqHeight: Int
): Bitmap? {
// 第一步:仅解码图片大小
val options = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
BitmapFactory.decodeResource(
context.resources,
resourceId,
options
)
// 第二步:计算采样率
val sampleSize = calculateSampleSize(options, reqWidth, reqHeight)
// 第三步:使用采样率解码
options.inSampleSize = sampleSize
options.inJustDecodeBounds = false
return BitmapFactory.decodeResource(
context.resources,
resourceId,
options
)
}
}
}5.2.2 使用 Recycle 机制
kotlin
class BitmapLifecycleManager {
/**
* 加载 Bitmap 并管理生命周期
*/
fun loadBitmapWithLifecycle(
bitmap: Bitmap,
onViewDisappear: () -> Unit
) {
// 设置回调
bitmap.setHasAlpha(true) // 如果需要透明度
// 在 View 消失时回收
onViewDisappear = {
if (!bitmap.isRecycled) {
bitmap.recycle()
}
}
}
/**
* 安全的 Bitmap 回收
*/
fun safeRecycle(bitmap: Bitmap?) {
if (bitmap != null && !bitmap.isRecycled) {
bitmap.recycle()
}
}
/**
* 使用 WeakReference 管理 Bitmap
*/
fun loadBitmapWithWeakReference(): WeakReference<Bitmap> {
val bitmap = BitmapFactory.decodeFile(path)
return WeakReference(bitmap)
}
}5.3 Bitmap 内存池
kotlin
class BitmapPool(private val maxSizeBytes: Int) {
private val pool = SynchronizedLinkedList<Bitmap>()
private var currentSize = 0
/**
* 从池中获取 Bitmap
*/
fun getBitmap(width: Int, height: Int, config: Bitmap.Config): Bitmap? {
synchronized(pool) {
val targetSize = width * height * bytesPerPixel(config)
// 查找合适大小的 Bitmap
for (bitmap in pool) {
if (bitmap.byteCount >= targetSize) {
pool.remove(bitmap)
currentSize -= bitmap.byteCount
// 如果太大,创建新的合适大小的 Bitmap
if (bitmap.width != width || bitmap.height != height) {
val newBitmap = Bitmap.createBitmap(width, height, config)
bitmap.recycle()
return newBitmap
}
return bitmap
}
}
// 没有找到合适的,创建新的
return Bitmap.createBitmap(width, height, config)
}
}
/**
* 将 Bitmap 放回池中
*/
fun putBitmap(bitmap: Bitmap) {
if (bitmap.isRecycled) return
synchronized(pool) {
val newSize = currentSize + bitmap.byteCount
if (newSize <= maxSizeBytes) {
pool.addFirst(bitmap)
currentSize = newSize
} else {
// 池已满,回收 Bitmap
bitmap.recycle()
}
}
}
/**
* 清除所有缓存
*/
fun clear() {
synchronized(pool) {
for (bitmap in pool) {
if (!bitmap.isRecycled) {
bitmap.recycle()
}
}
pool.clear()
currentSize = 0
}
}
private fun bytesPerPixel(config: Bitmap.Config): Int {
return when (config) {
Bitmap.Config.ALPHA_8 -> 1
Bitmap.Config.ARGB_4444,
Bitmap.Config.RGB_565 -> 2
else -> 4
}
}
}5.4 Bitmap 内存泄漏检测
kotlin
class BitmapLeakDetector {
/**
* 检测潜在的 Bitmap 泄漏
*/
fun detectLeaks(): List<LeakedBitmap> {
val leakedBitmaps = mutableListOf<LeakedBitmap>()
// 使用 RefWatcher(来自 LeakCanary)
val refWatcher = RefWatcher.create()
// 检查所有 Bitmap 引用
checkBitmapReferences(refWatcher, leakedBitmaps)
return leakedBitmaps
}
/**
* 使用 Debug 模式检查 Bitmap
*/
fun checkInDebugMode(context: Context) {
if (BuildConfig.DEBUG) {
val memInfo = ActivityManager.MemoryInfo()
val activityManager = context.getSystemService(
Context.ACTIVITY_SERVICE
) as ActivityManager
activityManager.getMemoryInfo(memInfo)
val usedMem = memInfo.totalMem - memInfo.availMem
val threshold = 50 * 1024 * 1024 // 50MB
if (usedMem > threshold) {
Log.w("BitmapLeak", "High memory usage: ${usedMem / 1024 / 1024} MB")
triggerGCAndCheck()
}
}
}
private fun triggerGCAndCheck() {
Runtime.getRuntime().gc()
Thread.sleep(100)
Runtime.getRuntime().gc()
}
data class LeakedBitmap(
val bitmap: Bitmap,
val size: Int,
val location: String
)
}5.5 Bitmap 最佳实践
kotlin
class BitmapBestPractices {
companion object {
/**
* 实践 1: 使用合适的大小
*/
fun loadAppropriateBitmap(context: Context, imageView: ImageView): Bitmap {
val options = BitmapFactory.Options().apply {
inJustDecodeBounds = true
}
BitmapFactory.decodeFile(imagePath, options)
val targetW = imageView.measuredWidth.coerceAtLeast(1)
val targetH = imageView.measuredHeight.coerceAtLeast(1)
options.inSampleSize = calculateSampleSize(
options.outWidth,
options.outHeight,
targetW,
targetH
)
options.inJustDecodeBounds = false
return BitmapFactory.decodeFile(imagePath, options)
}
/**
* 实践 2: 及时释放
*/
fun useBitmapSafely(bitmap: Bitmap, work: (Bitmap) -> Unit) {
try {
work(bitmap)
} finally {
if (!bitmap.isRecycled) {
bitmap.recycle()
}
}
}
/**
* 实践 3: 使用硬件加速
*/
fun createHardwareBitmap(width: Int, height: Int): Bitmap {
return Bitmap.createBitmap(
width,
height,
Bitmap.Config.HARDWARE
)
}
/**
* 实践 4: 避免大图
*/
fun resizeLargeBitmap(bitmap: Bitmap, maxSize: Int): Bitmap {
val width = bitmap.width
val height = bitmap.height
if (width <= maxSize && height <= maxSize) {
return bitmap
}
val scale = Math.min(maxSize.toFloat() / width,
maxSize.toFloat() / height)
val newWidth = (width * scale).toInt()
val newHeight = (height * scale).toInt()
return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true)
}
}
}六、内存泄漏检测
6.1 常见内存泄漏场景
6.1.1 静态引用泄漏
kotlin
// 错误示例:静态变量持有 Activity 引用
class BadSingleton {
companion object {
private lateinit var activity: Activity // 泄漏!
}
fun setActivity(activity: Activity) {
this.activity = activity
}
}
// 正确示例:使用弱引用
class GoodSingleton {
companion object {
private var activityRef: WeakReference<Activity>? = null
val activity: Activity?
get() = activityRef?.get()
}
fun setActivity(activity: Activity) {
activityRef = WeakReference(activity)
}
}6.1.2 线程未注销
kotlin
// 错误示例:后台线程持有 Activity 引用
class BadAsyncTask : AsyncTask<Void, Void, Void>() {
private lateinit var activity: Activity
override fun doInBackground(vararg params: Void): Void {
// 长时间运行
Thread.sleep(10000)
return null
}
override fun onPostExecute(result: Void) {
// Activity 可能已经销毁
activity.showToast("Done") // 泄漏!
}
}
// 正确示例:使用弱引用
class GoodAsyncTask(activity: Activity) {
private val activityRef = WeakReference(activity)
fun execute() {
Thread {
// 长时间运行
Thread.sleep(10000)
val activity = activityRef.get()
if (activity != null && !activity.isFinishing) {
activity.runOnUiThread {
activity.showToast("Done")
}
}
}.start()
}
}6.1.3 内部类泄漏
kotlin
// 错误示例:非静态内部类持有外部类引用
class LeakActivity : Activity() {
class BadInnerClass {
fun doSomething() {
// 隐式持有 LeakActivity 的引用
LeakActivity.log("Something")
}
}
}
// 正确示例:使用静态内部类
class LeakActivity : Activity() {
companion object {
fun log(message: String) {
Log.d("LeakActivity", message)
}
}
class GoodInnerClass(private val activityRef: WeakReference<Activity>) {
fun doSomething() {
val activity = activityRef.get() ?: return
activity.runOnUiThread {
LeakActivity.log("Something")
}
}
}
}6.1.4 资源未关闭
kotlin
// 错误示例:未关闭资源
class ResourceLeak {
var cursor: Cursor? = null
fun queryDatabase() {
cursor = db.query(...) // 未关闭
}
}
// 正确示例:及时关闭资源
class ResourceManaged {
var cursor: Cursor? = null
fun queryDatabase() {
cursor = db.query(...)
try {
// 使用 cursor
while (cursor?.moveToNext() == true) {
// 处理数据
}
} finally {
cursor?.close()
cursor = null
}
}
}6.1.5 监听器泄漏
kotlin
// 错误示例:未注销监听器
class BadActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
eventBus.register(this) // 注册监听器
// 忘记注销
}
// 忘记在 onStop/onDestroy 中注销
// override fun onDestroy() {
// eventBus.unregister(this)
// super.onDestroy()
// }
}
// 正确示例:及时注销
class GoodActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
eventBus.register(this)
}
override fun onDestroy() {
eventBus.unregister(this)
super.onDestroy()
}
}6.2 LeakCanary 使用
6.2.1 集成配置
gradle
// build.gradle
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.14'
}kotlin
// Application 类
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
LeakCanary.install(this)
}
}
}6.2.2 自定义检测
kotlin
// 检测特定对象
class LeakDetector {
fun detect(activity: Activity) {
if (BuildConfig.DEBUG) {
val refWatcher = RefWatcher.create()
refWatcher.watch(activity, "Activity")
}
}
}6.3 MAT 分析工具
6.3.1 生成 Hprof 文件
kotlin
// 手动生成 Hprof 文件
fun dumpHprof(context: Context, fileName: String = "heap.hprof") {
val file = File(context.externalCacheDir, fileName)
try {
val fos = FileOutputStream(file)
Debug.dumpHprofData(file.absolutePath)
fos.close()
Log.d("Hprof", "Dumped to: ${file.absolutePath}")
} catch (e: IOException) {
e.printStackTrace()
}
}
// 使用 ADB 命令获取
// adb shell dumpsys meminfo <package_name>
// adb shell run-as <package_name> dumpsys gc6.3.2 分析泄漏路径
泄漏路径示例:
com.example.LeakedActivity @ 0x12345678
├─ activityThread: android.app.ActivityThread
│ └─ views: android.view.View
│ └─ mListeners: java.util.ArrayList
│ └─ item: com.example.BadListener
│ └─ activityRef: com.example.LeakedActivity ← 泄漏点6.4 Android Studio Profiler
6.4.1 内存分析步骤
kotlin
// 1. 启动应用并进入调试模式
// 2. 打开 Profiler (View -> Tool Windows -> Profiler)
// 3. 选择 Memory Profiler
// 4. 执行可能导致泄漏的操作
// 5. 点击 GC 按钮触发垃圾回收
// 6. 分析内存变化
// 关键指标:
// - Heap: 堆内存使用情况
// - Alloc: 内存分配速率
// - GC: 垃圾回收事件6.4.2 查找泄漏对象
kotlin
// 使用 Profiler 的查找功能:
// 1. 点击 "Start Java Object Monitor"
// 2. 输入要监控的对象类名
// 3. 执行操作
// 4. 查看对象的引用链七、内存优化技巧
7.1 代码级优化
7.1.1 对象复用
kotlin
// 优化前:每次创建新对象
fun processText(text: String): String {
val sb = StringBuilder()
for (char in text) {
sb.append(char.uppercaseChar())
}
return sb.toString()
}
// 优化后:对象复用
class TextProcessor {
private val sb = StringBuilder()
fun processText(text: String): String {
sb.clear()
for (char in text) {
sb.append(char.uppercaseChar())
}
return sb.toString()
}
}
// 更好的方案:使用线程本地变量
class ThreadLocalTextProcessor {
private val sbLocal = ThreadLocal { StringBuilder() }
fun processText(text: String): String {
val sb = sbLocal.get()
sb.clear()
for (char in text) {
sb.append(char.uppercaseChar())
}
return sb.toString()
}
}7.1.2 减少临时对象
kotlin
// 优化前:大量临时对象
fun sumSquares(numbers: List<Int>): Int {
return numbers
.map { it * it } // 创建中间集合
.sum()
}
// 优化后:使用迭代
fun sumSquaresOptimized(numbers: List<Int>): Int {
var sum = 0
for (number in numbers) {
sum += number * number
}
return sum
}7.1.3 使用基本类型
kotlin
// 优化前:使用包装类型
fun calculate(ints: List<Int>): List<Int> {
return ints.map { it * 2 }
}
// 优化后:使用基本类型数组
fun calculateOptimized(ints: IntArray): IntArray {
val result = IntArray(ints.size)
for (i in ints.indices) {
result[i] = ints[i] * 2
}
return result
}7.2 架构级优化
7.2.1 懒加载
kotlin
// 懒加载数据
class LazyDataLoader {
private var data: List<Item>? = null
val dataList: List<Item>
get() {
if (data == null) {
data = loadFromDatabase()
}
return data!!
}
private fun loadFromDatabase(): List<Item> {
// 加载数据
return listOf()
}
}7.2.2 分页加载
kotlin
class PaginatedLoader {
private val pageSize = 20
private var currentPage = 0
private val items = mutableListOf<Item>()
fun loadMore(): List<Item> {
val newItems = loadPage(currentPage, pageSize)
items.addAll(newItems)
currentPage++
return newItems
}
fun releaseOldData() {
// 只保留最近两页
val keepCount = pageSize * 2
if (items.size > keepCount) {
items.subList(0, items.size - keepCount).clear()
}
}
private fun loadPage(page: Int, size: Int): List<Item> {
// 从数据源加载
return listOf()
}
}7.2.3 缓存策略
kotlin
class SmartCache {
private val memoryCache = LruCache<String, Any>(10 * 1024 * 1024)
private val diskCache = DiskLruCache(50 * 1024 * 1024)
fun get(key: String): Any? {
// L1: 内存缓存
var result = memoryCache.get(key)
if (result != null) return result
// L2: 磁盘缓存
result = diskCache.read(key)
if (result != null) {
memoryCache.put(key, result)
}
return result
}
fun put(key: String, value: Any) {
memoryCache.put(key, value)
diskCache.write(key, value)
}
}7.3 配置优化
7.3.1 AndroidManifest 配置
xml
<!-- 开启大内存模式 -->
<application
android:largeHeap="true"
... >
</application>
<!-- 配置内存警告接收器 -->
<receiver android:name=".MemoryWarningReceiver">
<intent-filter>
<action android:name="android.intent.action.MEMORY_USAGE_CHANGED" />
</intent-filter>
</receiver>7.3.2 gradle 配置
gradle
// 启用 R8 混淆优化
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
}
}
}7.4 工具链优化
7.4.1 内存监控工具
kotlin
class MemoryMonitor {
companion object {
private val TAG = "MemoryMonitor"
fun startMonitoring() {
Thread {
while (true) {
logMemoryUsage()
Thread.sleep(1000)
}
}.start()
}
private fun logMemoryUsage() {
val runtime = Runtime.getRuntime()
val used = (runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024
val total = runtime.totalMemory() / 1024 / 1024
val max = runtime.maxMemory() / 1024 / 1024
Log.d(TAG, "Memory: ${used}/${total}/${max} MB")
}
}
}7.4.2 性能测试
kotlin
class MemoryBenchmark {
fun runBenchmark() {
// 1. 清除缓存
Runtime.getRuntime().gc()
// 2. 记录初始内存
val initialMemory = getMemoryUsage()
// 3. 执行测试操作
performOperations()
// 4. 触发 GC
Runtime.getRuntime().gc()
Thread.sleep(100)
Runtime.getRuntime().gc()
// 5. 记录最终内存
val finalMemory = getMemoryUsage()
// 6. 计算内存增量
val memoryDelta = finalMemory - initialMemory
Log.d("Benchmark", "Memory delta: $memoryDelta KB")
}
private fun getMemoryUsage(): Long {
val runtime = Runtime.getRuntime()
return runtime.totalMemory() - runtime.freeMemory()
}
private fun performOperations() {
// 测试代码
}
}八、面试考点
8.1 基础考点
8.1.1 概念理解
Q1: 解释 Android 内存模型
答案要点:
1. 虚拟内存与物理内存的区别
2. 进程地址空间划分(堆、栈、共享库等)
3. Java 堆和 Native 堆的区别
4. ART 的分代内存管理Q2: 什么是 LRU 算法?
答案要点:
1. Least Recently Used(最近最少使用)
2. 核心思想:淘汰最久未使用的数据
3. 实现:LinkedHashMap + 双向链表
4. 时间复杂度:O(1)Q3: Bitmap 内存如何计算?
答案要点:
内存 = 宽 × 高 × 每个像素的字节数
ARGB_8888: 4 字节/像素
RGB_565: 2 字节/像素
ALPHA_8: 1 字节/像素8.1.2 代码分析
Q4: 以下代码有什么内存问题?
kotlin
class BadActivity : Activity() {
private val imageCache = mutableMapOf<String, Bitmap>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
for (i in 1..100) {
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.img)
imageCache[i.toString()] = bitmap
}
}
// 忘记释放
}问题分析:
1. Bitmap 未释放,导致内存泄漏
2. 没有使用 LruCache,可能导致 OOM
3. Activity 销毁后,Map 仍持有 Bitmap 引用
正确做法:
1. 使用 LruCache 管理 Bitmap
2. 在 onDestroy 中清除缓存
3. 使用 WeakReference 或 SoftReference8.2 进阶考点
8.2.1 场景设计
Q5: 如何设计一个图片加载库的内存缓存?
答案要点:
1. 使用 LruCache 作为内存缓存基础
2. 根据设备内存动态设置缓存大小
3. 支持图片尺寸缩放,避免加载大图
4. 使用内存池复用 Bitmap 对象
5. 支持缓存过期时间
6. 支持取消正在加载的任务
7. 双缓存策略(内存 + 磁盘)Q6: 如何处理大图导致的 OOM?
答案要点:
1. 使用 inSampleSize 缩放加载
2. 使用 inJustDecodeBounds 先获取图片信息
3. 使用 BitmapRegionDecoder 加载部分区域
4. 使用 LruCache 限制缓存大小
5. 考虑使用第三方库(Glide、Picasso)
6. 监控内存使用情况8.3 高级考点
8.3.1 深度分析
Q7: ART 的 GC 机制是怎样的?
答案要点:
1. 分代 GC 策略
- 新生代:复制算法
- 老年代:标记 - 清理/整理算法
2. GC 类型
- Concurrent Copying GC
- Concurrent Mark Compact GC
- Background Mark Compact GC
3. GC 触发条件
- 内存不足
- 分配失败
- 手动请求
4. Stop-The-World 优化
- 并发 GC 减少停顿
- 后台 GC 完全并发Q8: 如何分析内存泄漏?
答案要点:
1. 使用工具
- LeakCanary(开发期)
- Android Studio Profiler(调试期)
- MAT(生产期)
2. 分析步骤
- 生成 Hprof 文件
- 查找支配树(Dominators)
- 分析引用链(Reference Chain)
- 定位泄漏点
3. 常见泄漏类型
- 静态引用
- 未注销监听器
- 后台线程未停止
- 内部类引用外部类8.3.2 性能优化
Q9: 如何优化应用内存占用?
答案要点:
1. 代码层面
- 对象复用
- 减少临时对象创建
- 使用基本类型
- 及时释放资源
2. 架构层面
- 懒加载
- 分页加载
- 合理的缓存策略
- 异步加载
3. 配置层面
- 优化资源文件
- 使用 WebP 格式
- 启用 ProGuard/R8
4. 工具层面
- 定期内存分析
- 性能测试
- 持续监控Q10: LruCache 和 WeakReference 有什么区别?
答案要点:
1. 管理机制
- LruCache: 主动管理,基于容量淘汰
- WeakReference: 被动管理,由 GC 决定
2. 使用场景
- LruCache: 缓存热点数据
- WeakReference: 不强制持有对象
3. 性能
- LruCache: O(1) 访问,可控
- WeakReference: 不确定回收时机
4. 最佳实践
- 结合使用:LruCache + WeakReference
- 根据场景选择九、实战案例
9.1 图片加载器实现
kotlin
class SimpleImageLoader(private val context: Context) {
private val memoryCache: LruCache<String, Bitmap>
private val diskCache: DiskLruCache
private val tasks = ConcurrentHashMap<String, AsyncTask<*, *, Bitmap>>()
init {
// 内存缓存
val maxMemory = Runtime.getRuntime().maxMemory() / 8
memoryCache = object : LruCache<String, Bitmap>(maxMemory.toInt()) {
override fun sizeOf(key: String, value: Bitmap): Int {
return value.byteCount
}
override fun entryRemoved(
evicted: Boolean,
key: String,
oldValue: Bitmap,
newValue: Bitmap?
) {
if (evicted && !oldValue.isRecycled) {
oldValue.recycle()
}
}
}
// 磁盘缓存
diskCache = DiskLruCache.open(
context.cacheDir.resolve("images"),
1, 1, 50 * 1024 * 1024
)
}
fun loadBitmap(
url: String,
imageView: ImageView,
maxWidth: Int = 0,
maxHeight: Int = 0
) {
// 取消之前的任务
tasks[url]?.cancel(true)
// 检查内存缓存
val cachedBitmap = memoryCache.get(url)
if (cachedBitmap != null) {
imageView.setImageBitmap(cachedBitmap)
return
}
// 创建加载任务
val task = object : AsyncTask<*>() {
override fun doInBackground(vararg params: Any?): Bitmap {
// 检查磁盘缓存
var bitmap = diskCache.read(url)
if (bitmap == null) {
// 从网络加载
bitmap = loadImageFromNetwork(url)
// 保存到磁盘
diskCache.write(url, bitmap)
}
// 缩放
return scaleBitmap(bitmap, maxWidth, maxHeight)
}
override fun onPostExecute(bitmap: Bitmap) {
// 检查任务是否被取消
if (task.isCancelled) return
// 保存到内存缓存
memoryCache.put(url, bitmap)
// 设置图片
imageView.setImageBitmap(bitmap)
}
}
tasks[url] = task
task.execute()
}
private fun loadImageFromNetwork(url: String): Bitmap {
// 实现网络加载
return Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)
}
private fun scaleBitmap(
bitmap: Bitmap,
maxWidth: Int,
maxHeight: Int
): Bitmap {
if (maxWidth == 0 && maxHeight == 0) return bitmap
val width = bitmap.width
val height = bitmap.height
val scale = when {
maxWidth == 0 -> maxHeight.toFloat() / height
maxHeight == 0 -> maxWidth.toFloat() / width
else -> minOf(
maxWidth.toFloat() / width,
maxHeight.toFloat() / height
)
}
return Bitmap.createScaledBitmap(
bitmap,
(width * scale).toInt(),
(height * scale).toInt(),
true
)
}
fun clearCache() {
memoryCache.evictAll()
diskCache.clear()
}
}9.2 内存监控组件
kotlin
class MemoryMonitorService : Service() {
private val handler = Handler(Looper.getMainLooper())
private val memoryThreshold = 0.8 // 80%
private var isMonitoring = false
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
startMonitoring()
return START_STICKY
}
override fun onBind(intent: Intent?): IBinder? = null
private fun startMonitoring() {
if (isMonitoring) return
isMonitoring = true
handler.postDelayed(MonitorRunnable(), 1000)
}
private inner class MonitorRunnable : Runnable {
override fun run() {
checkMemoryUsage()
handler.postDelayed(this, 1000)
}
}
private fun checkMemoryUsage() {
val runtime = Runtime.getRuntime()
val used = runtime.totalMemory() - runtime.freeMemory()
val max = runtime.maxMemory()
val usage = used.toDouble() / max
if (usage > memoryThreshold) {
handleHighMemory()
}
}
private fun handleHighMemory() {
// 释放内存
releaseMemory()
// 通知用户
sendNotification()
}
private fun releaseMemory() {
// 清除缓存
// 释放 Bitmap
// 停止后台任务
}
private fun sendNotification() {
// 发送通知
}
override fun onDestroy() {
isMonitoring = false
handler.removeCallbacksAndMessages(null)
super.onDestroy()
}
}十、总结与最佳实践
10.1 核心要点
- 理解内存模型:掌握 Android 内存区域划分和 ART 内存管理机制
- 合理使用缓存:根据场景选择 LruCache、WeakReference 等方案
- 优化 Bitmap 加载:使用合适的尺寸和配置,及时释放
- 检测内存泄漏:使用 LeakCanary、Profiler 等工具
- 持续优化:定期分析内存使用情况,持续优化代码
10.2 检查清单
kotlin
// 内存优化检查清单
class MemoryOptimizationChecklist {
companion object {
// 1. 缓存管理
const val CHECK_CACHE_SIZE = "是否设置了合理的缓存大小?"
const val CHECK_CACHE_POLICY = "是否使用了合适的缓存淘汰策略?"
// 2. Bitmap 管理
const val CHECK_BITMAP_SIZE = "是否使用了合适的 Bitmap 尺寸?"
const val CHECK_BITMAP_CONFIG = "是否选择了合适的 Bitmap 配置?"
const val CHECK_BITMAP_RELEASE = "是否及时释放了 Bitmap?"
// 3. 内存泄漏
const val CHECK_STATIC_REF = "是否存在静态引用导致的泄漏?"
const val CHECK_LISTENER = "是否及时注销了监听器?"
const val CHECK_THREAD = "后台线程是否正确停止?"
// 4. 资源管理
const val CHECK_CURSOR = "是否关闭了 Cursor?"
const val CHECK_STREAM = "是否关闭了 InputStream/OutputStream?"
// 5. 性能优化
const val CHECK_OBJECT_REUSE = "是否复用了对象?"
const val CHECK_TEMP_OBJECT = "是否减少了临时对象创建?"
}
}10.3 推荐资源
官方文档
- Android Developers: Memory Management
- ART 源码分析
工具
- LeakCanary
- Android Studio Profiler
- MAT (Memory Analyzer Tool)
书籍
- 《Android 性能优化实战》
- 《深入理解 Android 虚拟机》
附录
A. 常用内存单位换算
1 KB = 1024 Bytes
1 MB = 1024 KB = 1,048,576 Bytes
1 GB = 1024 MB = 1,073,741,824 BytesB. 常见 Bitmap 配置
| 配置 | 字节/像素 | 说明 |
|---|---|---|
| ALPHA_8 | 1 | 只有透明度 |
| RGB_565 | 2 | 无透明度,16 位色 |
| ARGB_4444 | 2 | 有透明度,低质量 |
| ARGB_8888 | 4 | 有透明度,高质量 |
C. 内存错误代码
| 错误 | 含义 | 解决方案 |
|---|---|---|
| OOM | 内存溢出 | 减少内存使用,优化缓存 |
| FATAL EXCEPTION: java.lang.OutOfMemoryError | 严重 OOM | 检查 Bitmap 加载和缓存策略 |
| Allocation failed | 分配失败 | 检查可用内存,触发 GC |
本文档持续更新,欢迎贡献和反馈
版本: 1.0
作者: Android 面试指南团队
最后更新: 2024 年