前两周又有一个同事拿着低端机过来找我,说信息流列表滑动超过一分钟就开始掉帧,再过一阵直接闪退。不用看日志我也能猜到七八分——RecyclerView 配 Glide 的场景,卡顿和内存占用几乎永远纠缠在一起。很多人把锅甩给"图片太多",但实际上,内存优化不是少放几张图,而是把每一张图在屏幕上占住的字节数控制住。这篇文章我把 RecyclerView 和 Glide 两边能做的内存优化动作拆开讲,穿插一个我自己排查线上事故的完整过程,最后给几个可以直接抄走的经验阈值。适合列表已经能跑起来、但内存曲线越来越难看的开发者参考。
1. 内存不是"图片太多"撑大的:先拆清楚两边各占多少
先把内存账算清楚。一张 1080x1920 的 ARGB_8888 图片,像素数是 1080 × 1920 = 2,073,600,每个像素占 4 字节,刚解码完就是 8.3MB。这个数字不会因为你手机屏幕只有 390×844 就自动变小。显示区域小,只是看起来小,内存未必小——这句话是列表 OOM 的第一个真相。
RecyclerView 本身其实挺省内存。它只创建可见区域加上缓存池里少量 ViewHolder,几十个 View 撑死也就几 MB。真正让内存失控的通常有两条线:
- 列表 item 里加载的图片,解码后按像素占用内存,像素尺寸决定内存,文件大小反而无所谓;
- Glide 有自己的一套内存缓存和 BitmapPool,图片滑动过去之后不会立刻消失,而是被放在缓存里等待复用,这是流畅滑动的前提,也是内存上涨的主要原因。
所以项目里经常出现"列表也就 20 条数据,内存却飙到三四百兆"的现象。原因不是 item 数量多,而是每张原图可能 2000×1500,一条 item 就吃掉 12MB,加上 Glide 内存缓存和 BitmapPool 里的残留,滑两屏就可能堆出上百 MB。
这里有一个关键认知:RecyclerView 只负责页面索引和 UI 复用,它不直接拥有 Bitmap;Glide 才是那张图真正的"内存持有者"。所以我们在聊优化的时候,必须分两层去看:一层是列表层,别让 View 和数据对象把内存拖死;另一层是图片层,别让每一张图的字节数超出实际显示需求。
这里顺带纠正一个特别常见的误区:很多人一 OOM 就怀疑 Glide 泄漏,其实大多数时候是"原图尺寸没有被裁剪,加载到了巨大的内存尺寸"。先记住这个结论,后面第四章会给你看具体案例。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. RecyclerView 侧的止血动作:这几个配置不改,优化就是空谈
2.1 先把"数据对象里存 Bitmap"的老毛病揪出来
我接手过的项目里,不止一次看到类似 NewsBean.bitmap 这种字段。老代码可能是以前用 AsyncTask 加载图片留下的,后来接入 Glide,但 Bean 里的 Bitmap 没清理,列表数据一存,所有图片全都压在内存里。这种情况无论你在 Glide 上怎么调格式、怎么调缓存策略,都救不回来。
正确做法是数据对象里只存 URL、图片 ID、资源路径等轻量字段,真正显示交给 Glide。这是 RecyclerView 内存优化的第一道止血,也是很多人忽略的根因。
2.2 合理控制 ViewHolder 池和 itemViewType 数量
RecyclerView 的复用机制本身不消耗多少内存,但有两个动作会打破它的复用:
- 随意调用
holder.setIsRecyclable(false)。这个方法一旦被滥用,ViewHolder 不会回到池子里,滑出去之后就被丢弃,需要重新 inflate,等于抛弃了整个缓存体系。itemView 里包含 ImageView 时,反复 inflate 还会带来 GC 压力。 - 大量 itemViewType。每个
itemViewType在 RecycledViewPool 里是独立队列,类型越多,池子越碎片化。如果你的服务端返回的type字段几百种变化,那基本上每个 item 都需要重新创建 ViewHolder。尽量把类型收敛到几种,比如
