Skip to content

Android 内存优化

目录

  1. 内存管理基础
  2. [内存泄漏检测与 LeakCanary](#2-内存泄漏检测与 leakcanary)
  3. Bitmap 内存优化
  4. View 内存优化
  5. 集合类内存优化
  6. 内存优化最佳实践
  7. 面试考点总结

1. 内存管理基础

1.1 Android 内存模型

Dalvik/ART 虚拟机内存结构

Android 应用的运行依赖于 ART(Android Runtime)虚拟机,早期版本使用的是 Dalvik。理解虚拟机的内存结构是进行内存优化的基础。

ART 内存区域划分:
┌─────────────────────────────────────┐
│           Heap (堆内存)              │
│  ┌──────────────┬─────────────────┐ │
│  │ Young Gen    │ Old Gen         │ │
│  │ (新生代)     │ (老年代)        │ │
│  └──────────────┴─────────────────┘ │
├─────────────────────────────────────┤
│           Stack (栈内存)             │
│  方法栈、线程栈、局部变量           │
├─────────────────────────────────────┤
│           Method Area (方法区)       │
│  类信息、常量、静态变量             │
├─────────────────────────────────────┤
│           Direct Memory (直接内存)   │
│  Native 堆外内存                    │
└─────────────────────────────────────┘

内存分配策略

新生代(Young Gen)

  • 存放新创建的对象
  • 采用复制算法进行垃圾回收
  • 分为 Eden 区和两个 Survivor 区(From、To)
  • 回收频繁,速度快(Minor GC)

老年代(Old Gen)

  • 存放长期存活的对象
  • 采用标记 - 整理算法
  • 经过多次 Minor GC 存活的对象晋升到此区域
  • 回收次数少,但耗时长(Major GC/Full GC)

代码示例:对象生命周期演示

kotlin
// 新生代对象示例
fun createTransientObject(): Any {
    // 这些对象会在新生代快速创建和销毁
    val tempString = StringBuilder().apply {
        repeat(1000) { append("test") }
    }.toString()
    return tempString // 方法返回后可能被回收
}

// 可能晋升到老年代的对象
class LongLivedManager {
    private val dataCache = mutableListOf<Any>()
    
    fun loadData() {
        // 持续添加数据可能导致对象晋升
        repeat(10000) {
            dataCache.add(DataObject(it))
        }
    }
}

Android 内存限制

不同设备、不同 Android 版本对应用内存限制不同:

kotlin
// 获取应用可用内存
class MemoryInfoHelper {
    fun getAppMemoryInfo(context: Context): MemoryInfo {
        val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
        val memoryInfo = ActivityManager.MemoryInfo()
        activityManager.getMemoryInfo(memoryInfo)
        return memoryInfo
    }
    
    fun getProcessMemoryInfo(): Long {
        val debugMemoryInfo = android.os.Debug.MemoryInfo()
        android.os.Debug.getProcessMemoryInfo(android.os.Process.myPid(), arrayOf(debugMemoryInfo))
        // 返回总内存 (KB)
        return debugMemoryInfo.total PSS
    }
    
    fun getMaxMemory(): Long {
        // 返回 JVM 允许使用的最大内存 (字节)
        return Runtime.getRuntime().maxMemory()
    }
}

// 不同 Android 版本的内存限制(参考值)
// Android 5.0: 约 150-200MB
// Android 6.0: 约 180-250MB
// Android 7.0+: 约 200-300MB
// 高端设备:可达 512MB 或更高

1.2 垃圾回收机制(GC)

GC 算法原理

标记 - 清除(Mark-Sweep)

过程:
1. 标记阶段:从 GC Roots 开始,标记所有可达对象
2. 清除阶段:清除未被标记的对象

优点:实现简单
缺点:产生内存碎片,效率低

标记 - 复制(Mark-Copy)

过程:
1. 将内存分为两块,每次只使用一块
2. 标记存活对象
3. 将存活对象复制到另一块
4. 清空当前块

优点:无内存碎片,效率高
缺点:空间利用率低(50%)
适用:新生代(对象大多朝生夕死)

标记 - 整理(Mark-Compact)

过程:
1. 标记存活对象
2. 将存活对象向一端移动
3. 清除端边界外的对象

优点:无内存碎片
缺点:移动对象成本高
适用:老年代(对象存活率高)

GC Roots

GC Roots 是垃圾回收的起点,以下对象被视为 GC Roots:

kotlin
// 1. 虚拟机栈中引用的对象
fun stackRootExample() {
    val localObj = SomeObject() // 局部变量
    use(localObj)
}

// 2. 方法区中类静态属性引用的对象
class StaticRootExample {
    companion object {
        val staticObj = SomeObject() // 静态变量
    }
}

// 3. 方法区中类常量引用的对象
class ConstantRootExample {
    companion object {
        const val CONSTANT = "constant" // 常量
    }
}

// 4. 本地方法栈中 JNI 引用的对象
class JniRootExample {
    external fun nativeMethod() // native 方法中的引用
}

// 5. 线程本地变量(ThreadLocal)
class ThreadLocalExample {
    private val threadLocal = ThreadLocal<SomeObject> {
        SomeObject()
    }
}

GC 触发条件

kotlin
// 1. 堆内存不足时
fun triggerGCByMemory() {
    val objects = mutableListOf<Any>()
    try {
        while (true) {
            objects.add(ByteArray(1024 * 1024)) // 1MB
        }
    } catch (e: OutOfMemoryError) {
        // 触发 GC
    }
}

// 2. 调用 System.gc()(建议)
fun suggestGC() {
    System.gc() // 只是建议,JVM 不一定执行
}

// 3. 分配失败且 Survivor 区溢出
// 4. 大对象直接进入老年代导致空间不足

GC 日志分析

bash
# 查看 GC 日志( adb shell)
adb shell dumpsys meminfo com.example.app

# 输出示例:
#                       dalvik
#                      heap    internal    native    other    TOTAL
#                   ---------------- ---------------- ----------------
# Pss                 45.8M        12.3M        8.2M     25.1M     91.4M
# Private Dirty       42.1M        11.8M        7.9M     22.3M     84.1M
# Private Clean        3.7M        0.5M        0.3M      2.8M      7.3M
# Shared Dirty        12.5M        1.2M        0.8M      3.1M     17.6M
# Shared Clean        95.2M       15.3M       12.1M     68.4M    191.0M
# swap                 0.0M        0.0M        0.0M      0.0M      0.0M

1.3 内存泄漏与内存溢出

内存泄漏(Memory Leak)

定义:对象已经不再使用,但由于被引用而无法被 GC 回收

常见场景

kotlin
// 1. 静态集合类导致泄漏
class MemoryLeakExample1 {
    companion object {
        // 静态集合持有对象引用,无法回收
        private val objectList = mutableListOf<Any>()
    }
    
    fun addObject(obj: Any) {
        objectList.add(obj) // 对象永远无法被回收
    }
}

// 2. 非静态内部类持有外部类引用
class OuterClass {
    inner class InnerClass {
        fun doSomething() {
            // 内部类隐式持有外部类引用
        }
    }
    
    fun createInner(): InnerClass? {
        return InnerClass().also {
            // 如果返回引用,外部类无法回收
        }
    }
}

// 3. 单例持有 Context 引用
class SingletonLeak {
    companion object {
        // Activity 上下文会导致整个活动无法回收
        private var context: Context = null
        fun init(context: Context) {
            this.context = context // 泄漏
        }
    }
}

// 4. 未注销的监听器
class ListenerLeak : AppCompatActivity() {
    private val listener = object : View.OnClickListener {
        override fun onClick(v: View?) {
            // 监听器持有 Activity 引用
        }
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        someView.setOnClickListener(listener)
        // 忘记注销:someView.setOnClickListener(null)
    }
    
    override fun onDestroy() {
        // 应该在这里注销监听器
        super.onDestroy()
    }
}

// 5. 未关闭的资源
class ResourceLeak {
    fun openStream(): FileInputStream {
        return FileInputStream("file.txt")
            .also { /* 忘记关闭 */ }
    }
}

// 6. 线程导致泄漏
class ThreadLeak : AppCompatActivity() {
    private val thread = Thread {
        while (true) {
            // 线程持有 Activity 引用,无法回收
            doWork()
        }
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        thread.start()
    }
}

内存溢出(OutOfMemory)

定义:申请内存时无法分配,抛出 OutOfMemoryError

常见场景

kotlin
// 1. 加载过大图片
fun loadLargeImage(context: Context) {
    val bitmap = BitmapFactory.decodeFile("/path/to/large/image.jpg")
    // 图片过大直接 OOM
    imageView.setImageBitmap(bitmap)
}

// 2. 无限创建对象
fun createInfiniteObjects() {
    val objects = mutableListOf<Any>()
    while (true) {
        objects.add(ByteArray(1024 * 1024)) // 不断创建 1MB 数组
    }
}

// 3. 栈溢出(StackOverflow)
fun stackOverflow() {
    stackOverflow() // 无限递归
}

// 4. 堆外内存溢出
fun directByteBufferOverflow() {
    val buffer = DirectByteBuffer.allocate(1024 * 1024 * 100) // 100MB 堆外内存
}

内存泄漏与溢出的区别

特性内存泄漏内存溢出
定义不用的对象无法回收无法分配新内存
发生时机长期运行后逐渐累积短时间内大量分配
回收机制GC 无法回收GC 无法分配
解决方式找出并解除引用优化内存使用
严重性渐进式,最终导致 OOM立即崩溃

2. 内存泄漏检测与 LeakCanary

2.1 LeakCanary 简介

LeakCanary 是 Square 公司开源的自动化内存泄漏检测库,无需手动编写代码即可检测泄漏。

特性

  • 自动化检测:自动监控 Activity、Fragment、View 等常见泄漏点
  • 实时通知:检测到泄漏后即时弹出通知
  • 详细分析:提供完整的引用链分析
  • 易用性:简单集成,开箱即用
  • 性能影响小:仅在 Debug 版本启用

工作原理

LeakCanary 工作流程:
┌─────────────────┐
│  1. 对象监听    │ → 监听关键对象(Activity、Fragment 等)的生命周期
├─────────────────┤
│  2. 延迟检测    │ → 等待 GC 后再次检查对象是否存活
├─────────────────┤
│  3. 引用链分析  │ → 使用 H Heap Dump 分析引用链
├─────────────────┤
│  4. 结果展示    │ → 展示泄漏路径和建议
└─────────────────┘

2.2 集成与配置

Gradle 依赖

gradle
// 在模块级 build.gradle 中添加
dependencies {
    // LeakCanary Android
    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.14'
    
    // 可选:不显示通知,仅记录日志
    // debugImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:2.14'
    
    // 可选:仅分析不显示通知
    // debugImplementation 'com.squareup.leakcanary:leakcanary-android-no-hint:2.14'
}

初始化

kotlin
// LeakCanary 2.x 版本不需要手动初始化,会自动运行

// 如果需要自定义配置,创建 Application 子类
class LeakCanaryApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        
        if (BuildConfig.DEBUG) {
            // 创建自定义 Analyzer 配置
            LeakCanary.install(this).apply {
                // 自定义配置(LeakCanary 内部使用)
            }
        }
    }
}

// AndroidManifest.xml 配置
/*
<application
    android:name=".LeakCanaryApplication"
    android:debuggable="true">
    ...
</application>
*/

进阶配置

kotlin
// 自定义监控规则
class CustomLeakCanaryConfig {
    companion object {
        fun install(application: Application): LeakCanary {
            return LeakCanary.install(application).apply {
                // 排除某些类
                excludeRefs.add(javaClass.name)
            }
        }
    }
    
    // 自定义监控点
    fun watchObject(obj: Any, name: String) {
        // LeakCanary 内部 API(不推荐直接调用)
    }
}

2.3 使用 LeakCanary 检测泄漏

基本使用

kotlin
// 1. 自动检测 Activity 泄漏
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        // LeakCanary 会自动监控 Activity
        // 当 Activity 销毁后,如果有引用未被释放,会报告泄漏
    }
    
    override fun onDestroy() {
        super.onDestroy()
        // 等待几秒,LeakCanary 会在此后检测
    }
}

// 2. 手动检测任意对象
fun manuallyWatchObject(obj: Any) {
    if (BuildConfig.DEBUG) {
        // 使用 ReferenceWatcher 手动监控
        watch(obj).retainedName("Custom Object")
    }
}

// 3. 监控 Fragment
class SampleFragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_sample, container, false)
    }
    
    // LeakCanary 会自动监控 Fragment
}

查看分析结果

LeakCanary 分析结果示例:

🟢 NO LEAK
  对象已被成功回收

🔴 LEAK FOUND
  泄漏对象:com.example.MainActivity @ xxxxxx
  引用链:
    java.lang.ref.WeakReference<com.example.MainActivity>
      → com.example.Singleton.instance
         → com.example.MainActivity @ xxxxxx
  说明:Singleton 持有静态引用导致泄漏
  建议:使用 WeakReference 或 Application Context

2.4 常见泄漏场景与解决方案

场景 1:静态集合类

kotlin
// ❌ 错误示例
class BadCache {
    companion object {
        // 静态集合持有对象引用
        private val cache = mutableMapOf<String, Any>()
    }
    
    fun put(key: String, value: Any) {
        cache[key] = value // 对象永远无法被回收
    }
}

// ✅ 解决方案 1:使用 WeakReference
class GoodCacheWithWeakRef {
    companion object {
        private val cache = mutableMapOf<String, WeakReference<Any>>()
    }
    
    fun put(key: String, value: Any) {
        cache[key] = WeakReference(value)
    }
    
    fun get(key: String): Any? {
        return cache[key]?.get()
            ?.also { 
                // 定期清理弱引用
                cache.values.removeAll { it.get() == null }
            }
    }
    
    // 定期清理失效的弱引用
    fun cleanup() {
        cache.values.removeAll { it.get() == null }
    }
}

// ✅ 解决方案 2:使用 LruCache
class GoodCacheWithLru {
    private val cache = LruCache<String, Any>(16) // 最多 16 个条目
    
    fun put(key: String, value: Any) {
        cache.put(key, value)
    }
    
    fun get(key: String): Any? {
        return cache.get(key)
    }
}

// ✅ 解决方案 3:使用 WeakHashMap
class GoodCacheWithWeakHashMap {
    // 使用 WeakHashMap,key 为弱引用
    private val cache = WeakHashMap<String, Any>()
    
    fun put(key: String, value: Any) {
        cache[key] = value
    }
    
    fun get(key: String): Any? {
        return cache[key]
    }
}

场景 2:非静态内部类

kotlin
// ❌ 错误示例
class ActivityWithInnerClass : AppCompatActivity() {
    // 非静态内部类持有外部类引用
    inner class AsyncTaskInner {
        fun doWork() {
            // 如果任务执行时间长,Activity 无法回收
            Thread.sleep(5000)
            updateUi()
        }
        
        fun updateUi() {
            // 访问外部类成员
            textView.text = "Done"
        }
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val task = AsyncTaskInner()
        task.doWork()
    }
}

// ✅ 解决方案 1:使用静态内部类 + WeakReference
class ActivityWithStaticInner : AppCompatActivity() {
    // 静态内部类不持有外部类引用
    private inner class AsyncTaskInner private constructor(
        private val activityRef: WeakReference<ActivityWithStaticInner>
    ) {
        companion object {
            fun create(activity: ActivityWithStaticInner) = 
                AsyncTaskInner(WeakReference(activity))
        }
        
        fun doWork() {
            Thread.sleep(5000)
            updateUi()
        }
        
        fun updateUi() {
            val activity = activityRef.get() ?: return
            if (!activity.isFinishing) {
                activity.textView.text = "Done"
            }
        }
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val task = AsyncTaskInner.create(this)
        task.doWork()
    }
}

// ✅ 解决方案 2:使用 ViewModel
class MainViewModel : ViewModel() {
    val uiState = MutableLiveData<String>()
    
    fun doWork() {
        lifecycleScope.launch {
            delay(5000)
            uiState.value = "Done"
        }
    }
}

class ActivityWithViewModel : AppCompatActivity() {
    private val viewModel: MainViewModel by viewModels()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel.uiState.observe(this) { text ->
            textView.text = text
        }
        viewModel.doWork()
    }
}

场景 3:单例持有 Context

kotlin
// ❌ 错误示例
class SingletonWithActivityContext {
    companion object {
        private var context: Context? = null
        fun init(context: Context) {
            this.context = context // 如果传入 Activity,会导致泄漏
        }
    }
    
    fun getContext(): Context? = context
}

// ✅ 解决方案 1:使用 Application Context
class SingletonWithApplicationContext {
    companion object {
        private lateinit var context: Context
        @JvmStatic
        fun init(context: Context) {
            this.context = context.applicationContext
        }
    }
    
    fun getContext(): Context = context
}

// ✅ 解决方案 2:使用弱引用
class SingletonWithWeakContext {
    companion object {
        private var contextRef: WeakReference<Context>? = null
        fun init(context: Context) {
            this.contextRef = WeakReference(context.applicationContext)
        }
    }
    
    fun getContext(): Context? = contextRef?.get()
}

场景 4:未注销的监听器

kotlin
// ❌ 错误示例
class LeakyActivity : AppCompatActivity() {
    private val broadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            // 持有 Activity 引用
        }
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        registerReceiver(broadcastReceiver, IntentFilter("ACTION"))
        // 忘记注销
    }
    // 忘记在 onDestroy 中注销
}

// ✅ 解决方案 1:在生命周期中正确注销
class FixedActivity : AppCompatActivity() {
    private val broadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            // 处理广播
        }
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        registerReceiver(broadcastReceiver, IntentFilter("ACTION"))
    }
    
    override fun onDestroy() {
        unregisterReceiver(broadcastReceiver) // 正确注销
        super.onDestroy()
    }
}

// ✅ 解决方案 2:使用 LifecycleObserver
class ActivityWithLifecycleObserver : AppCompatActivity() {
    private val observer = object : LifecycleObserver {
        @OnLifecycleEvent(Lifecycle.Event.ON_START)
        fun onStart() {
            // 注册监听器
            someManager.addListener(listener)
        }
        
        @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
        fun onStop() {
            // 注销监听器
            someManager.removeListener(listener)
        }
    }
    
    private val listener = object : SomeListener {
        override fun onEvent(event: Event) {
            // 处理事件
        }
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        lifecycle.addObserver(observer)
    }
}

// ✅ 解决方案 3:使用 ViewBinding 自动处理
class ActivityWithViewBinding : AppCompatActivity() {
    private var _binding: ActivityMainBinding? = null
    private val binding get() = _binding!!
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        _binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        
        binding.button.setOnClickListener {
            // 使用 lambda,持有 Activity 引用
        }
    }
    
    override fun onDestroy() {
        _binding = null // 释放 View 和监听器
        super.onDestroy()
    }
}

场景 5:线程和异步任务

kotlin
// ❌ 错误示例
class ActivityWithThread : AppCompatActivity() {
    private val thread = Thread {
        while (true) {
            // 线程持有 Activity 引用
            doWork()
        }
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        thread.start()
    }
    // 忘记在 onDestroy 中停止线程
}

// ✅ 解决方案 1:使用 WeakReference
class ActivityWithWeakThread : AppCompatActivity() {
    private val thread = Thread {
        val activityRef = WeakReference<ActivityWithWeakThread>(this)
        while (true) {
            val activity = activityRef.get() ?: break
            activity.doWork()
        }
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        thread.start()
    }
    
    override fun onDestroy() {
        thread.interrupt() // 停止线程
        super.onDestroy()
    }
}

// ✅ 解决方案 2:使用协程 + CoroutineScope
class ActivityWithCoroutine : AppCompatActivity() {
    private val activityScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        activityScope.launch {
            // 协程与 Activity 生命周期关联
            launchDispatchersIo {
                doWork()
            }
        }
    }
    
    override fun onDestroy() {
        activityScope.cancel() // 自动取消所有协程
        super.onDestroy()
    }
}

// ✅ 解决方案 3:使用 ViewModelScope
class MainViewModel : ViewModel() {
    fun loadData() {
        viewModelScope.launch {
            // 协程与 ViewModel 生命周期关联
            // ViewModel 销毁时自动取消
            val data = repository.getData()
            uiState.value = data
        }
    }
}

// ✅ 解决方案 4:使用 LifecycleScope
class ActivityWithLifecycleScope : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        lifecycleScope.launch {
            // 协程与生命周期关联
            // onPause/onStop 时自动挂起
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                launch {
                    doWork()
                }
            }
        }
    }
}

场景 6:Timer 和 Handler

kotlin
// ❌ 错误示例
class ActivityWithTimer : AppCompatActivity() {
    private val timer = Timer()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        timer.schedule(object : TimerTask() {
            override fun run() {
                // 持有 Activity 引用
                runOnUiThread { updateUi() }
            }
        }, 1000, 1000)
    }
    // 忘记取消 Timer
}

// ✅ 解决方案 1:及时取消
class ActivityWithTimerFixed : AppCompatActivity() {
    private val timer = Timer()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        timer.schedule(object : TimerTask() {
            override fun run() {
                runOnUiThread { updateUi() }
            }
        }, 1000, 1000)
    }
    
    override fun onDestroy() {
        timer.cancel() // 取消 Timer
        super.onDestroy()
    }
}

// ✅ 解决方案 2:使用 Handler 并移除消息
class ActivityWithHandler : AppCompatActivity() {
    private val handler = Handler(Looper.getMainLooper())
    private val runnable = Runnable {
        updateUi()
        handler.postDelayed(this, 1000)
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        handler.postDelayed(runnable, 1000)
    }
    
    override fun onDestroy() {
        handler.removeCallbacks(runnable) // 移除消息
        super.onDestroy()
    }
}

// ✅ 解决方案 3:使用 HandlerThread
class ActivityWithHandlerThread : AppCompatActivity() {
    private lateinit var handlerThread: HandlerThread
    private lateinit var handler: Handler
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        handlerThread = HandlerThread("WorkThread").apply { start() }
        handler = Handler(handlerThread.looper)
    }
    
    override fun onDestroy() {
        handlerThread.quitSafely() // 安全退出
        super.onDestroy()
    }
}

场景 7:View 动画和 Transition

kotlin
// ❌ 错误示例
class ActivityWithAnimationLeak : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        val animation = AnimationUtils.loadAnimation(this, R.anim.fade_in)
        imageView.startAnimation(animation)
        // 动画持有 View 引用,如果 View 持有 Activity 引用会泄漏
    }
}

// ✅ 解决方案:使用 ObjectAnimator
class ActivityWithAnimator : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        // ObjectAnimator 不会持有 View 引用
        val animator = ObjectAnimator.ofFloat(imageView, View.ALPHA, 0f, 1f)
        animator.duration = 300
        animator.start()
        
        // 或使用 ViewPropertyAnimator
        imageView.animate()
            .alpha(1f)
            .setDuration(300)
            .start()
    }
}

2.5 LeakCanary 高级用法

自定义监控点

kotlin
// 监控自定义对象
class CustomWatcher {
    companion object {
        fun watch(obj: Any, name: String) {
            if (BuildConfig.DEBUG) {
                // 使用 ReferenceWatcher
                ReferenceWatcher.watch(obj, name)
            }
        }
    }
}

// 在代码中使用
class MyViewModel : ViewModel() {
    init {
        CustomWatcher.watch(this, "MyViewModel")
    }
}

排除假阳性

kotlin
// 某些情况可能误报,可以排除
class LeakCanaryConfig {
    companion object {
        fun init(application: Application) {
            LeakCanary.install(application)
            
            // 排除已知的假阳性
            // LeakCanary 内部配置
        }
    }
}

性能考虑

kotlin
// LeakCanary 在 Debug 版本引入的性能开销:
// 1. 内存占用增加:约 5-10MB
// 2. 启动时间增加:约 1-2 秒
// 3. CPU 占用:检测时会有短暂峰值

// 生产环境务必关闭
buildTypes {
    debug {
        // 启用 LeakCanary
        debuggable true
    }
    release {
        // 禁用 LeakCanary
        debuggable false
        minifyEnabled true
    }
}

3. Bitmap 内存优化

3.1 Bitmap 内存计算

Bitmap 内存占用

kotlin
// Bitmap 内存计算公式:
// 内存 (字节) = 宽度 × 高度 × 每个像素的字节数

// 不同配置每个像素的字节数:
// ARGB_8888: 4 字节(32 位,最常用)
// RGB_565:   2 字节(16 位)
// ARGB_X_8888: 4 字节
// ALPHA_8:   1 字节(8 位,仅透明度)

class BitmapMemoryCalculator {
    companion object {
        fun calculateMemory(width: Int, height: Int, config: Bitmap.Config): Long {
            val bytesPerPixel = when (config) {
                Bitmap.Config.ARGB_8888 -> 4
                Bitmap.Config.RGB_565 -> 2
                Bitmap.Config.ARGB_X_8888 -> 4
                Bitmap.Config.ALPHA_8 -> 1
                else -> 4
            }
            return width.toLong() * height * bytesPerPixel
        }
    }
}

// 示例:1920x1080 的 ARGB_8888 图片
// 内存 = 1920 × 1080 × 4 = 8,294,400 字节 ≈ 7.9MB
val bitmapMemory = BitmapMemoryCalculator.calculateMemory(
    1920, 1080, Bitmap.Config.ARGB_8888
)
println("Bitmap 内存占用:${bitmapMemory / 1024 / 1024}MB") // 7.9MB

Bitmap 在 Android 中的内存分配

kotlin
// Android 3.0 之前:Bitmap 数据存储在 native 堆
// Android 3.0-3.1:Bitmap 数据存储在 JNI 堆外内存
// Android 4.0 及以后:Bitmap 数据存储在 Java 堆

// 获取 Bitmap 内存占用
fun getBitmapMemoryUsage(bitmap: Bitmap): Long {
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        // API 19+: 使用 getByteCount()
        bitmap.byteCount.toLong()
    } else {
        // API 18 及以下:估算
        bitmap.rowBytes * bitmap.height.toLong()
    }
}

// 获取 Bitmap 占用的 PSS(实际物理内存)
fun getBitmapPss(bitmap: Bitmap): Long {
    // 需要使用 AllocationTracker 或第三方库
    return getBitmapMemoryUsage(bitmap)
}

3.2 Bitmap 加载优化

1. 缩小采样(inSampleSize)

kotlin
// ❌ 错误示例:直接加载大图
fun loadLargeImageWrong(context: Context, imagePath: String) {
    val options = BitmapFactory.Options()
    val bitmap = BitmapFactory.decodeFile(imagePath, options)
    imageView.setImageBitmap(bitmap)
    // 大图直接加载可能 OOM
}

// ✅ 正确示例:使用 inSampleSize 缩小
fun loadImageWithSampling(context: Context, imagePath: String, targetWidth: Int, targetHeight: Int) {
    // 1. 第一次解析,只获取尺寸
    val options = BitmapFactory.Options()
    options.inJustDecodeBounds = true
    BitmapFactory.decodeFile(imagePath, options)
    
    val outWidth = options.outWidth
    val outHeight = options.outHeight
    
    // 2. 计算合适的 inSampleSize
    val inSampleSize = calculateInSampleSize(outWidth, outHeight, targetWidth, targetHeight)
    
    // 3. 第二次解析,使用 inSampleSize 加载
    options.inJustDecodeBounds = false
    options.inSampleSize = inSampleSize
    
    // 4. 指定配置,减少内存
    options.inPreferredConfig = Bitmap.Config.RGB_565 // 如果不需要透明度
    
    val bitmap = BitmapFactory.decodeFile(imagePath, options)
    imageView.setImageBitmap(bitmap)
}

// 计算 inSampleSize
fun calculateInSampleSize(
    outWidth: Int,
    outHeight: Int,
    reqWidth: Int,
    reqHeight: Int
): Int {
    var inSampleSize = 1
    
    if (outHeight > reqHeight || outWidth > reqWidth) {
        val halfHeight = outHeight / 2
        val halfWidth = outWidth / 2
        
        // 计算缩放比例,必须是 2 的幂次方
        while (halfHeight / inSampleSize >= reqHeight && 
               halfWidth / inSampleSize >= reqWidth) {
            inSampleSize *= 2
        }
    }
    
    return inSampleSize
}

// 更精确的计算方法(支持任意值)
fun calculateInSampleSizePrecise(
    outWidth: Int,
    outHeight: Int,
    reqWidth: Int,
    reqHeight: Int
): Int {
    return maxOf(
        (outWidth.toFloat() / reqWidth).toInt(),
        (outHeight.toFloat() / reqHeight).toInt()
    ).takeIf { it > 0 } ?: 1
}

2. 分块加载(inBitmap)

kotlin
// ✅ 使用 inBitmap 复用内存
fun loadImageWithInBitmap(context: Context, imagePath: String, poolBitmap: Bitmap?) {
    val options = BitmapFactory.Options()
    
    // 1. 获取图片尺寸
    options.inJustDecodeBounds = true
    BitmapFactory.decodeFile(imagePath, options)
    
    // 2. 计算 inSampleSize
    val targetWidth = imageView.width
    val targetHeight = imageView.height
    options.inSampleSize = calculateInSampleSize(
        options.outWidth, options.outHeight, 
        targetWidth, targetHeight
    )
    
    // 3. 配置 inBitmap 复用
    options.inJustDecodeBounds = false
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        // Android 6.0+: 复用 Bitmap 要求字节大小一致
        if (poolBitmap != null && 
            poolBitmap.byteCount >= options.outWidth * options.outHeight * 4) {
            options.inBitmap = poolBitmap
        }
    } else {
        // Android 6.0 之前:要求宽度和高度一致
        if (poolBitmap != null &&
            poolBitmap.width == options.outWidth && 
            poolBitmap.height == options.outHeight) {
            options.inBitmap = poolBitmap
        }
    }
    
    val bitmap = BitmapFactory.decodeFile(imagePath, options)
    imageView.setImageBitmap(bitmap)
}

// Bitmap 内存池实现
class BitmapPool {
    private val pool = ArrayDeque<Bitmap>()
    private val lock = Any()
    
    fun acquire(targetWidth: Int, targetHeight: Int, config: Bitmap.Config = Bitmap.Config.ARGB_8888): Bitmap? {
        synchronized(lock) {
            val bitmap = pool.firstOrNull { 
                it.width == targetWidth && 
                it.height == targetHeight &&
                it.config == config
            }
            if (bitmap != null) {
                pool.removeFirst()
            }
            return bitmap
        }
    }
    
    fun release(bitmap: Bitmap) {
        if (bitmap.isRecycled) return
        synchronized(lock) {
            if (pool.size < 20) { // 限制池大小
                pool.addLast(bitmap)
            }
        }
    }
    
    fun clear() {
        synchronized(lock) {
            pool.forEach { it.recycle() }
            pool.clear()
        }
    }
}

3. 使用 Region 加载部分图片

kotlin
// ✅ 只加载图片的一部分
fun loadImageRegion(context: Context, imagePath: String, rect: Rect) {
    val options = BitmapFactory.Options()
    
    // 设置要加载的区域
    options.inJustDecodeBounds = false
    options.inMutable = true
    
    val bitmap = BitmapFactory.decodeRegion(imagePath, rect, options)
    imageView.setImageBitmap(bitmap)
}

// 九宫格加载大图
fun loadLargeImageAsNinePatch(context: Context, imagePath: String) {
    // 1. 获取图片尺寸
    val boundsOptions = BitmapFactory.Options()
    boundsOptions.inJustDecodeBounds = true
    BitmapFactory.decodeFile(imagePath, boundsOptions)
    
    val outWidth = boundsOptions.outWidth
    val outHeight = boundsOptions.outHeight
    
    // 2. 计算每个 tile 的尺寸
    val tileSize = 1024 // 每个 tile 1024x1024
    val tileCountX = (outWidth / tileSize).ceilToInt()
    val tileCountY = (outHeight / tileSize).ceilToInt()
    
    // 3. 分块加载
    val tiles = Array(tileCountX) { Array<Bitmap?>(tileCountY) { null } }
    
    for (x in 0 until tileCountX) {
        for (y in 0 until tileCountY) {
            val rect = Rect(
                x * tileSize,
                y * tileSize,
                minOf((x + 1) * tileSize, outWidth),
                minOf((y + 1) * tileSize, outHeight)
            )
            tiles[x][y] = BitmapFactory.decodeRegion(imagePath, rect, null)
        }
    }
    
    // 4. 拼接 tiles
    val finalBitmap = Bitmap.createBitmap(outWidth, outHeight, Bitmap.Config.ARGB_8888)
    val canvas = Canvas(finalBitmap)
    
    for (x in 0 until tileCountX) {
        for (y in 0 until tileCountY) {
            canvas.drawBitmap(tiles[x][y]!!, x * tileSize.toFloat(), y * tileSize.toFloat(), null)
        }
    }
    
    imageView.setImageBitmap(finalBitmap)
}

3.3 Bitmap 压缩

1. 压缩质量

kotlin
// ✅ 压缩 Bitmap 质量
fun compressBitmapWithQuality(bitmap: Bitmap, quality: Int = 80): Bitmap {
    val stream = ByteArrayOutputStream()
    
    // 压缩到 ByteArrayOutputStream
    bitmap.compress(Bitmap.CompressFormat.JPEG, quality, stream)
    
    // 解码压缩后的数据
    val compressedBitmap = BitmapFactory.decodeStream(
        stream.getInputStream(), 
        null, 
        BitmapFactory.Options().apply {
            inPreferredConfig = Bitmap.Config.RGB_565 // 进一步减少内存
        }
    )
    
    // 原 Bitmap 可以回收
    if (compressedBitmap != bitmap) {
        bitmap.recycle()
    }
    
    return compressedBitmap
}

2. 压缩尺寸

kotlin
// ✅ 压缩 Bitmap 尺寸
fun compressBitmapWithSize(bitmap: Bitmap, targetWidth: Int, targetHeight: Int): Bitmap {
    val newWidth = minOf(bitmap.width, targetWidth)
    val newHeight = minOf(bitmap.height, targetHeight)
    
    return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true)
}

// ✅ 保持宽高比压缩
fun compressBitmapKeepAspect(bitmap: Bitmap, maxSize: Int): Bitmap {
    val width = bitmap.width
    val height = bitmap.height
    
    var newWidth = width
    var newHeight = height
    
    if (width > height) {
        if (width > maxSize) {
            newHeight = (height * maxSize / width).toInt()
            newWidth = maxSize
        }
    } else {
        if (height > maxSize) {
            newWidth = (width * maxSize / height).toInt()
            newHeight = maxSize
        }
    }
    
    return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true)
}

3. 压缩格式选择

kotlin
// ✅ 选择合适的压缩格式
fun compressBitmapSmartly(bitmap: Bitmap, quality: Int = 80): Bitmap {
    val stream = ByteArrayOutputStream()
    
    // 判断是否需要透明度
    val hasAlpha = bitmap.hasAlpha()
    
    val format = if (hasAlpha) {
        Bitmap.CompressFormat.PNG // 需要透明度时用 PNG
    } else {
        Bitmap.CompressFormat.JPEG // 不需要透明度时用 JPEG,体积更小
    }
    
    bitmap.compress(format, quality, stream)
    
    return BitmapFactory.decodeStream(stream.getInputStream())!!
        .also { 
            if (it !== bitmap) bitmap.recycle()
        }
}

3.4 使用图片加载库

1. Glide 优化

kotlin
// ✅ 使用 Glide 加载图片
class GlideImageLoader {
    // 基本加载
    fun loadImageBasic(context: Context, url: String, imageView: ImageView) {
        Glide.with(context)
            .load(url)
            .into(imageView)
    }
    
    // 指定尺寸(避免 Overscaling)
    fun loadImageWithSize(context: Context, url: String, imageView: ImageView) {
        Glide.with(context)
            .load(url)
            .override(100, 100) // 指定目标尺寸
            .into(imageView)
    }
    
    // 指定缩放类型
    fun loadImageWithScaleType(context: Context, url: String, imageView: ImageView) {
        Glide.with(context)
            .load(url)
            .centerCrop() // 或 centerInside, fit 等
            .into(imageView)
    }
    
    // 内存池和磁盘缓存配置
    fun loadImageWithCache(context: Context, url: String, imageView: ImageView) {
        Glide.with(context)
            .load(url)
            .diskCacheStrategy(DiskCacheStrategy.ALL) // 缓存内存和磁盘
            .memoryCache(MemoryCache()) // 自定义内存缓存
            .into(imageView)
    }
    
    // 占位图和错误图
    fun loadImageWithPlaceholder(context: Context, url: String, imageView: ImageView) {
        Glide.with(context)
            .load(url)
            .placeholder(R.drawable.placeholder)
            .error(R.drawable.error)
            .into(imageView)
    }
    
    // 圆形图片
    fun loadCircleImage(context: Context, url: String, imageView: ImageView) {
        Glide.with(context)
            .load(url)
            .circleCrop()
            .into(imageView)
    }
    
    // 多图加载(Grid)
    fun loadGif(context: Context, url: String, imageView: ImageView) {
        Glide.with(context)
            .asGif()
            .load(url)
            .into(imageView)
    }
    
    // 预加载
    fun preload(context: Context, url: String) {
        Glide.with(context)
            .load(url)
            .preload()
    }
}

// Glide 内存管理配置
class GlideModule : AppGlideModule() {
    override fun applyOptions(context: Context, builder: GlideBuilder) {
        // 设置内存池大小
        builder.setMemoryCache(LruResourceCache(25 * 1024 * 1024)) // 25MB
        
        // 设置磁盘缓存大小
        builder.setDiskCache(DiskLruCacheWrapper(
            context.getCacheDir().resolve("glide-cache"),
            100 * 1024 * 1024 // 100MB
        ))
    }
    
    override fun isManifestParsingEnabled(): Boolean = false
}

2. Picasso 优化

kotlin
// ✅ 使用 Picasso 加载图片
class PicassoImageLoader {
    // 基本加载
    fun loadImageBasic(context: Context, url: String, imageView: ImageView) {
        Picasso.get()
            .load(url)
            .into(imageView)
    }
    
    // 指定尺寸
    fun loadImageWithSize(context: Context, url: String, imageView: ImageView) {
        Picasso.get()
            .load(url)
            .resize(100, 100)
            .into(imageView)
    }
    
    // 居中裁剪
    fun loadImageCenterCrop(context: Context, url: String, imageView: ImageView) {
        Picasso.get()
            .load(url)
            .centerCrop()
            .into(imageView)
    }
    
    // 圆形图片
    fun loadCircleImage(context: Context, url: String, imageView: ImageView) {
        Picasso.get()
            .load(url)
            .circle()
            .into(imageView)
    }
    
    // 占位图和错误图
    fun loadImageWithPlaceholder(context: Context, url: String, imageView: ImageView) {
        Picasso.get()
            .load(url)
            .placeholder(R.drawable.placeholder)
            .error(R.drawable.error)
            .into(imageView)
    }
    
    // 配置内存和磁盘缓存
    fun configurePicasso(context: Context) {
        val picasso = Picasso.Builder(context)
            .maxBitmapMemory(50 * 1024 * 1024) // 50MB
            .lruDiskCache(context.cacheDir.resolve("picasso-cache"), 50 * 1024 * 1024) // 50MB
            .build()
        
        Picasso.setSingletonInstance(picasso)
    }
}

3. Coil 优化(Kotlin 首选)

kotlin
// ✅ 使用 Coil 加载图片(Kotlin 协程支持)
class CoilImageLoader {
    // 基本加载
    fun loadImageBasic(context: Context, url: String, imageView: ImageView) {
        imageView.load(url)
    }
    
    // 指定尺寸
    fun loadImageWithSize(context: Context, url: String, imageView: ImageView) {
        imageView.load(url) {
            size(Size(100, 100))
        }
    }
    
    // 内存和磁盘缓存
    fun loadImageWithCache(context: Context, url: String, imageView: ImageView) {
        imageView.load(url) {
            memoryCachePolicy(MemoryCachePolicy.ENABLED)
            diskCachePolicy(DiskCachePolicy.ENABLED)
        }
    }
    
    // 圆形图片
    fun loadCircleImage(context: Context, url: String, imageView: ImageView) {
        imageView.load(url) {
            crossfade(true)
            transformations(CircleCropTransformation())
        }
    }
    
    // 配置 Coil
    fun configureCoil(context: Context) {
        ImageLoader.Builder(context)
            .memoryCache { 
                MemoryCache(maxSizePercent = 0.25) // 25% 内存
            }
            .diskCache { 
                DiskCache.Builder()
                    .directory(context.cacheDir.resolve("coil-cache"))
                    .maxSizePercent(0.05) // 5% 磁盘空间
                    .build()
            }
            .build()
    }
}

3.5 Bitmap 最佳实践

kotlin
class BitmapBestPractices {
    // 1. 使用合适的 Bitmap.Config
    fun useProperConfig(): Bitmap {
        // 不需要透明度:RGB_565(节省 50% 内存)
        val bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565)
        return bitmap
    }
    
    // 2. 及时回收 Bitmap
    fun recycleBitmap(bitmap: Bitmap) {
        if (!bitmap.isRecycled) {
            bitmap.recycle()
        }
        // 设置为 null,避免误用
        // bitmap = null // Kotlin 中无法设置外部变量为 null
    }
    
    // 3. 使用 WeakReference 持有 Bitmap
    fun useWeakReference(bitmap: Bitmap): WeakReference<Bitmap> {
        return WeakReference(bitmap)
    }
    
    // 4. 避免在静态变量中持有 Bitmap
    // ❌ 错误
    /*
    class BadBitmapHolder {
        companion object {
            var bitmap: Bitmap? = null // 永远无法回收
        }
    }
    */
    
    // ✅ 正确:使用 LruCache
    class GoodBitmapHolder {
        private val cache = object : LruCache<String, Bitmap>(16) {
            override fun sizeOf(key: String, bitmap: Bitmap): Int {
                return bitmap.byteCount / 1024 // KB 为单位
            }
        }
        
        fun getBitmap(key: String): Bitmap? {
            return cache.get(key)
        }
        
        fun putBitmap(key: String, bitmap: Bitmap) {
            cache.put(key, bitmap)
        }
    }
    
    // 5. 使用 RecyclerView 时复用 ViewHolder
    class ImageViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val imageView: ImageView = itemView.findViewById(R.id.image)
        private var currentBitmap: Bitmap? = null
        
        fun bind(url: String) {
            // 旧图片可以回收
            currentBitmap?.recycle()
            currentBitmap = loadImage(url)
            imageView.setImageBitmap(currentBitmap)
        }
        
        fun onRecycled() {
            currentBitmap?.recycle()
            currentBitmap = null
        }
    }
    
    // 6. 大图使用缩略图
    fun loadThumbnail(context: Context, imagePath: String): Bitmap {
        val options = BitmapFactory.Options().apply {
            inJustDecodeBounds = true
        }
        BitmapFactory.decodeFile(imagePath, options)
        
        val inSampleSize = calculateInSampleSize(
            options.outWidth, options.outHeight, 
            200, 200 // 目标尺寸 200x200
        )
        
        options.inJustDecodeBounds = false
        options.inSampleSize = inSampleSize
        
        return BitmapFactory.decodeFile(imagePath, options)
    }
    
    // 7. 使用图片加载库
    fun loadImageWithLibrary(context: Context, url: String, imageView: ImageView) {
        // 推荐:Glide、Coil、Picasso
        Glide.with(context).load(url).into(imageView)
    }
}

4. View 内存优化

4.1 View 内存占用

kotlin
// View 内存组成:
// 1. View 对象本身
// 2. 背景图片、纹理
// 3. 监听器
// 4. 子 View 树
// 5. 布局参数

class ViewMemoryAnalyzer {
    fun analyzeViewMemory(view: View): Map<String, Long> {
        return mapOf(
            "View Object" to view.memoryFootprint,
            "Background" to getBackgroundMemory(view),
            "Children" to calculateChildrenMemory(view)
        )
    }
    
    private fun getBackgroundMemory(view: View): Long {
        return when (val bg = view.background) {
            is BitmapDrawable -> bg.intrinsicWidth * bg.intrinsicHeight * 4L
            else -> 0
        }
    }
    
    private fun calculateChildrenMemory(parent: ViewGroup): Long {
        return if (parent is ViewGroup) {
            parent.childrenCount
                .let { parent.childrenAsList.sumOf { child ->
                    analyzeViewMemory(child)["View Object"]!!
                }}
        } else {
            0
        }
    }
}

4.2 减少 View 层级

xml
<!-- ❌ 错误示例:过多嵌套 -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Hello" />

        </LinearLayout>

    </LinearLayout>

</LinearLayout>

<!-- ✅ 正确示例:使用 ConstraintLayout -->
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

4.3 使用 merge 标签

xml
<!-- ✅ 使用 merge 减少层级 -->
<!-- res/layout/item_merge.xml -->
<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Item" />

    <ImageView
        android:layout_width="50dp"
        android:layout_height="50dp" />

</merge>

<!-- 在布局中 include -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <include layout="@layout/item_merge" />

</LinearLayout>

4.4 使用 ViewStub

xml
<!-- ✅ 使用 ViewStub 延迟加载 -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ViewStub
        android:id="@+id/viewStub"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout="@layout/complex_layout" />

</LinearLayout>
kotlin
// ViewStub 使用示例
class ViewStubUsage : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_with_stub)
        
        val viewStub = findViewById<ViewStub>(R.id.viewStub)
        // 延迟 inflate
        viewStub.inflate()
    }
}

5. 集合类内存优化

5.1 集合容量优化

kotlin
// ❌ 错误示例:默认容量导致多次扩容
fun badListCreation() {
    val list = mutableListOf<String>()
    for (i in 0 until 1000) {
        list.add("item_$i")
        // 多次扩容,浪费内存
    }
}

// ✅ 正确示例:指定初始容量
fun goodListCreation() {
    val list = mutableListOf<String>(1000) // 指定初始容量
    for (i in 0 until 1000) {
        list.add("item_$i")
    }
}

5.2 使用合适的数据结构

kotlin
// 根据场景选择合适的数据结构
class DataStructureOptimization {
    // 1. 频繁查找:HashMap
    val map = mutableMapOf<String, Int>() // O(1) 查找
    
    // 2. 有序数据:Array
    val array = arrayOf(1, 2, 3) // 内存更紧凑
    
    // 3. 少量数据:数组优于列表
    val smallData = arrayOfNulls<String>(10)
    
    // 4. 只读数据:使用 immutable 集合
    val immutableList = listOf(1, 2, 3)
}

5.3 及时清空集合

kotlin
class CollectionCleanup {
    private val dataList = mutableListOf<Data>()
    
    // 及时释放
    fun clearData() {
        dataList.clear() // 释放元素引用
    }
    
    // 或者设置为 null
    fun releaseData() {
        dataList.clear()
        // dataList = null // 如果是局部变量
    }
}

6. 内存优化最佳实践

6.1 代码规范

kotlin
// 1. 及时释放资源
class ResourceReleasing {
    private var inputStream: FileInputStream? = null
    
    fun closeResource() {
        inputStream?.use { it.close() }
        inputStream = null
    }
}

// 2. 使用 try-finally 或 use 块
fun readFileWithUse(path: String): String {
    return File(path).use { file ->
        file.readText()
    }
}

// 3. 避免在长期运行的对象中持有短期对象
class LongLivedManager {
    // ❌ 错误
    private var activity: Activity? = null
    
    // ✅ 正确:使用弱引用
    private var activityRef: WeakReference<Activity>? = null
}

// 4. 使用 Pool 模式复用对象
class ObjectPool<T : Any> {
    private val pool = ArrayDeque<WeakReference<T>>()
    
    fun acquire(): T? {
        val ref = pool.firstOrNull { it.get() != null }?.get()
        if (ref != null) pool.removeFirst()
        return ref
    }
    
    fun release(obj: T) {
        pool.addFirst(WeakReference(obj))
    }
}

6.2 监控与调优

kotlin
class MemoryMonitoring {
    // 1. 获取当前内存使用
    fun getCurrentMemoryUsage(): MemoryInfo {
        val activityManager = 
            getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
        val memoryInfo = ActivityManager.MemoryInfo()
        activityManager.getMemoryInfo(memoryInfo)
        return memoryInfo
    }
    
    // 2. 获取进程内存
    fun getProcessMemory(): Long {
        val debugMemoryInfo = android.os.Debug.MemoryInfo()
        android.os.Debug.getProcessMemoryInfo(
            android.os.Process.myPid(), 
            arrayOf(debugMemoryInfo)
        )
        return debugMemoryInfo.totalPss
    }
    
    // 3. 设置内存阈值告警
    fun setupMemoryWarning(threshold: Long) {
        val currentMemory = getProcessMemory()
        if (currentMemory > threshold) {
            // 触发告警或清理
            triggerMemoryCleanup()
        }
    }
    
    // 4. 清理缓存
    fun triggerMemoryCleanup() {
        // 清理图片缓存
        Glide.get(applicationContext).memoryCache?.clear()
        
        // 清理单例缓存
        singleton.clearCache()
        
        // 释放不用的资源
        releaseUnusedResources()
    }
}

7. 面试考点总结

7.1 基础概念

Q1: Android 内存区域划分?

  • 堆内存(Heap):对象实例
  • 栈内存(Stack):方法调用、局部变量
  • 方法区:类信息、常量、静态变量
  • 直接内存:Native 堆外内存

Q2: GC 算法有哪些?

  • 标记 - 清除:产生碎片
  • 标记 - 复制:新生代使用,无碎片
  • 标记 - 整理:老年代使用,无碎片
  • 分代回收:新生代 + 老年代

Q3: GC Roots 包括哪些?

  • 虚拟机栈引用的对象
  • 方法区静态属性引用的对象
  • 方法区常量引用的对象
  • 本地方法栈 JNI 引用的对象
  • 线程本地变量(ThreadLocal)

7.2 内存泄漏

Q4: 常见的内存泄漏场景?

  1. 静态集合类持有对象引用
  2. 非静态内部类持有外部类引用
  3. 单例持有 Context 引用
  4. 未注销的监听器和广播
  5. 未关闭的资源(流、Cursor)
  6. 未停止的线程和异步任务
  7. 未移除的 Timer/Handler

Q5: 如何使用 LeakCanary 检测泄漏?

kotlin
// Gradle 依赖
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.14'

// 自动监控 Activity、Fragment、View
// 检测后弹出通知,点击查看引用链

Q6: 如何避免单例内存泄漏?

kotlin
// 使用 Application Context
private var context: Context = application.applicationContext

// 或使用弱引用
private var contextRef: WeakReference<Context>? = null

7.3 Bitmap 优化

Q7: Bitmap 内存如何计算?

内存 (字节) = 宽度 × 高度 × 每个像素字节数
ARGB_8888: 4 字节
RGB_565: 2 字节

Q8: 如何避免 Bitmap OOM?

  1. 使用 inSampleSize 缩小采样
  2. 使用 inBitmap 复用内存
  3. 使用合适的 Bitmap.Config
  4. 及时回收 Bitmap
  5. 使用图片加载库(Glide/Coil/Picasso)

Q9: inSampleSize 的工作原理?

kotlin
// 计算缩放比例(2 的幂次方)
val inSampleSize = calculateInSampleSize(outWidth, outHeight, reqWidth, reqHeight)
options.inSampleSize = inSampleSize

7.4 工具使用

Q10: 如何使用 Android Profiler 分析内存?

  1. Android Studio → View → Tool Windows → Profiler
  2. 连接设备,选择目标进程
  3. 查看 Heap、CPU、Network 等
  4. 进行内存快照分析

Q11: LeakCanary 检测泄漏的流程?

  1. 监控对象生命周期
  2. 延迟检测(等待 GC)
  3. 堆转储分析
  4. 生成引用链报告

7.5 实战问题

Q12: 列表滚动后 OOM 怎么办?

  1. 检查 ViewHolder 是否复用
  2. 检查图片是否及时释放
  3. 使用 Glide 的自动内存管理
  4. 增加 inSampleSize 缩小图片

Q13: 如何实现图片懒加载?

kotlin
// 使用 RecyclerView + Glide
class ImageAdapter : RecyclerView.Adapter<ImageViewHolder>() {
    override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
        val url = items[position].url
        Glide.with(holder.itemView.context)
            .load(url)
            .into(holder.imageView)
    }
}

总结

内存优化是 Android 性能优化的重要组成部分,需要掌握:

  1. 理论基础:理解 ART 内存模型、GC 算法
  2. 工具使用:熟练使用 LeakCanary、Android Profiler
  3. 实战技巧:Bitmap 优化、View 优化、集合优化
  4. 最佳实践:代码规范、资源管理、监控调优

通过持续学习和实践,可以有效避免内存问题,提升应用性能和用户体验。