Android性能优化
今日目标
- 吃透Android常见内存泄漏的场景、核心成因,掌握通用修复套路,快速识别项目中潜在的泄漏风险。
- 熟练集成LeakCanary,能看懂泄漏堆栈、定位泄漏点、完成修复。
- 掌握主线程卡顿的核心成因、ANR触发条件,牢记日常开发避坑规范,能剥离主线程耗时代码。
- 主攻「LeakCanary内存泄漏排查 + 主线程卡顿优化 + 工程性能自查」
高频内存泄漏场景
- 核心:内存泄漏 = 长生命周期对象持有短生命周期对象
- 例:Activity、Fragment的引用,导致短生命周期对象无法被GC回收,长期积累引发OOM
场景1:静态变量持有Activity/Context
- 原因:静态变量生命周期与应用一致,持有页面引用后,页面销毁仍无法释放。
- 修复:使用弱引用(WeakReference),页面销毁时置空静态变量。
kotlin
class UserActivity: BaseActivity() {
private val binding: ActivityUserBinding by lazy { ActivityUserBinding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
...
sActivity = WeakReference(this)
}
...
override fun onDestroy() {
super.onDestroy()
// 页面销毁时清空弱引用
sActivity?.clear()
sActivity = null
}
companion object {
// 弱引用
private var sActivity: WeakReference<UserActivity>? = null
}
}
场景2:匿名内部类/lambda隐式持有外部类
- 原因:匿名内部类默认持有外部页面引用,若内部类做耗时操作(例:线程、延时任务),页面销毁后仍在执行,导致泄漏。
- 修复:使用静态内部类+弱引用,或页面销毁时取消耗时操作。
场景3:Handler延时消息未移除
- 原因:Handler发送延时消息后,消息队列持有Handler引用,Handler又持有Activity引用,页面销毁后消息未移除,导致泄漏。
- 修复:页面onDestroy时,调用handler.removeCallbacksAndMessages(null)。
kotlin
class UserActivity: BaseActivity() {
private val binding: ActivityUserBinding by lazy { ActivityUserBinding.inflate(layoutInflater) }
...
private val mHandler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
// 模拟耗时操作
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
...
// 发送10s延时消息,页面销毁后消息仍在队列
mHandler.sendEmptyMessageDelayed(1, 10000)
}
override fun onDestroy() {
super.onDestroy()
...
// 移除所有消息和回调,避免泄露
mHandler.removeCallbacksAndMessages(null)
}
}
场景4:单例持有页面引用
- 原因:单例是全局唯一、生命周期长,若构造方法或方法参数传入Activity,会长期持有引用。
- 修复:单例中使用弱引用持有页面,或避免直接传入Activity,改用Application Context。
场景5:定时器、协程生命周期未取消
- 原因:页面销毁后,定时器(Timer)、协程仍在运行,持有页面引用。
- 修复:页面onDestroy时,取消定时器、取消协程(job.cancel())。
kotlin
// 使用lifecycleScope,自动跟随页面生命周期取消
class UserActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
// lifecycleScope 会在页面销毁时自动取消协程,无需手动取消
lifecycleScope.launch {
// 耗时操作逻辑
delay(10000)
}
}
}
场景6:自定义View、监听未解绑
- 原因:页面销毁时,未解除自定义View的监听、广播接收器、EventBus订阅等,导致引用持有。
- 修复:页面onDestroy时,解绑监听、取消订阅。
场景7:资源未释放
- 原因:Bitmap、文件流、MediaPlayer等资源使用后未关闭/释放,占用内存且无法回收。
- 修复:使用后及时close()、recycle(),结合try-catch-finally确保释放。
LeakCanary集成与使用
- 1.依赖版本配置
ini
// gradle/libs.versions.toml
[versions] # 版本号
...
leakcanary = "2.12"
[libraries] # 依赖库
...
leakcanary = { group = "com.squareup.leakcanary", name = "leakcanary-android", version.ref = "leakcanary" }
- 2.两种引入方式(2选1即可):全项目监控
scss
// 方式1:base/build.gradle.kts
dependencies {
debugApi(libs.leakcanary)
}
// 方式2:app/build.gradle.kts
dependencies {
debugImplementation(libs.leakcanary)
}
- 3.LeakCanary版本2.x之后只需引入依赖即可自动监控
卡顿 & ANR
卡顿是主线程阻塞导致,长期严重卡顿会触发ANR,影响用户体验,是线上App质量考核的核心指标。
- ANR触发条件:主线程阻塞超过5秒、广播接收者执行超过10秒、服务执行超过20秒(前台)/200秒(后台)。
- 主线程禁止操作:网络请求、文件IO(读写)、大数据解析(JSON、Excel解析)、多层循环耗时操作、复杂计算。
- 卡顿优化核心:将主线程耗时代码剥离到子线程(使用协程、ThreadPoolExecutor),避免同步调用。
- 辅助优化:布局优化(减少嵌套、避免过度绘制、使用ViewStub懒加载)、启动优化(冷启动异步初始化、非核心任务懒加载)。
项目性能自查
- 内存自查:使用Android Studio Profiler(Memory面板),查看APP运行时内存占用,排查是否有内存抖动、内存泄漏。
- 卡顿自查:使用Profiler(CPU面板),查看主线程CPU占用,定位耗时操作,确保无长期阻塞。
- 启动速度自查:记录APP冷启动、热启动时间,排查启动时主线程耗时任务,优化异步初始化。