一、卡顿的本质:为什么UI会卡?
做UI开发这么多年,我见过太多人一遇到卡顿就慌了神。其实说白了,卡顿没那么神秘。今天我就带你彻底搞懂它。
1.1 主线程阻塞的真相
先问个问题:你的App界面为什么能流畅滑动?
答案很简单——主线程在不停地干活。它负责处理触摸事件、执行布局、绘制界面。一旦它忙不过来,卡顿就来了。
我举个例子。假设主线程是个餐厅服务员,他需要同时做三件事:
- 接单(处理触摸事件)
- 上菜(执行布局)
- 摆盘(绘制界面)
如果这时候后厨(子线程)突然喊他去做报表……嗯,客人就得等着了。这就是主线程阻塞。
核心结论:主线程只做UI相关的事。任何耗时操作——网络请求、大文件读写、复杂计算——都不该出现在主线程上。
我在项目中遇到过最典型的案例:一个列表页,每次滑动都卡成PPT。查了半天,发现是列表项的onBindViewHolder里做了图片压缩。压缩操作放在主线程,不卡才怪。
1.2 帧率与刷新率的关系
你肯定听过60fps、120fps这些词。它们到底什么意思?
帧率(FPS),就是每秒能绘制多少帧画面。刷新率(Hz),是屏幕每秒刷新多少次。
这两者必须配合好。你想想看:
- 如果帧率是60,刷新率是60:完美,一帧对一帧
- 如果帧率是30,刷新率是60:屏幕刷新两次,画面才更新一次——你就能感觉到卡顿
- 如果帧率是120,刷新率是60:多出来的帧就浪费了,这叫「帧撕裂」
| 场景 | 帧率 | 刷新率 | 体验 |
|---|---|---|---|
| 理想状态 | 60 FPS | 60 Hz | 流畅 |
| 轻度卡顿 | 30-45 FPS | 60 Hz | 能感觉到掉帧 |
| 严重卡顿 | < 30 FPS | 60 Hz | 明显卡顿,操作延迟 |
| ANR | 0 FPS | 60 Hz | 应用无响应 |
个人习惯:我一般用Choreographer来监控帧率。它能精确告诉你每一帧花了多少时间。超过16ms(60fps的阈值)就要警惕了。
1.3 卡顿的两种类型:掉帧与ANR
卡顿其实分两种,很多人混为一谈。我帮你理清楚。
掉帧(Jank)
掉帧就是画面不连贯。比如你滑动列表,突然跳了一下。原因很简单:某一帧没在规定时间内完成。
为什么会这样?
- 布局嵌套太深,measure/layout耗时过长
- 绘制时做了大量计算,比如自定义View里的
onDraw太复杂 - 频繁触发GC,导致主线程被暂停
我记得有一次,一个同事写的自定义View,在onDraw里用了Paint.measureText。每次绘制都重新测量,掉帧掉到怀疑人生。改成缓存测量结果后,瞬间流畅了。
ANR(Application Not Responding)
ANR比掉帧严重得多。掉帧只是画面不流畅,ANR是应用直接「死」了。
Android系统有个机制:如果主线程超过5秒没处理完输入事件,或者广播接收器超过10秒没返回,系统就会弹ANR对话框。
避坑指南:我曾经在BroadcastReceiver里直接做了网络请求。结果用户手机网络慢,10秒没返回,ANR弹出来了。后来我改成用IntentService处理,再也没出过问题。
掉帧和ANR的区别,我用一张图来说明:
记住这个原则:掉帧可以忍,ANR不能忍。掉帧影响体验,ANR直接导致用户流失。我见过太多App因为ANR频繁被卸载。
嗯,到这里你应该对卡顿的本质有了清晰认识。主线程阻塞是万恶之源,帧率与刷新率的不匹配是表象,掉帧和ANR是两种不同严重程度的后果。下一章我会讲如何用工具精准定位卡顿——到时候你就能亲手抓出那些「凶手」了。
一个小建议:从现在开始,养成习惯——每次写代码前先问自己:「这段代码会不会阻塞主线程?」如果答案是「会」,那就换个方式做。
可可精品资料库