Appearance
Android 内存优化
目录
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.0M1.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 Context2.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.9MBBitmap 在 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: 常见的内存泄漏场景?
- 静态集合类持有对象引用
- 非静态内部类持有外部类引用
- 单例持有 Context 引用
- 未注销的监听器和广播
- 未关闭的资源(流、Cursor)
- 未停止的线程和异步任务
- 未移除的 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>? = null7.3 Bitmap 优化
Q7: Bitmap 内存如何计算?
内存 (字节) = 宽度 × 高度 × 每个像素字节数
ARGB_8888: 4 字节
RGB_565: 2 字节Q8: 如何避免 Bitmap OOM?
- 使用 inSampleSize 缩小采样
- 使用 inBitmap 复用内存
- 使用合适的 Bitmap.Config
- 及时回收 Bitmap
- 使用图片加载库(Glide/Coil/Picasso)
Q9: inSampleSize 的工作原理?
kotlin
// 计算缩放比例(2 的幂次方)
val inSampleSize = calculateInSampleSize(outWidth, outHeight, reqWidth, reqHeight)
options.inSampleSize = inSampleSize7.4 工具使用
Q10: 如何使用 Android Profiler 分析内存?
- Android Studio → View → Tool Windows → Profiler
- 连接设备,选择目标进程
- 查看 Heap、CPU、Network 等
- 进行内存快照分析
Q11: LeakCanary 检测泄漏的流程?
- 监控对象生命周期
- 延迟检测(等待 GC)
- 堆转储分析
- 生成引用链报告
7.5 实战问题
Q12: 列表滚动后 OOM 怎么办?
- 检查 ViewHolder 是否复用
- 检查图片是否及时释放
- 使用 Glide 的自动内存管理
- 增加 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 性能优化的重要组成部分,需要掌握:
- 理论基础:理解 ART 内存模型、GC 算法
- 工具使用:熟练使用 LeakCanary、Android Profiler
- 实战技巧:Bitmap 优化、View 优化、集合优化
- 最佳实践:代码规范、资源管理、监控调优
通过持续学习和实践,可以有效避免内存问题,提升应用性能和用户体验。