文章目录
- 一、基础原理与复用缓存
-
- [1. RecyclerView 与 ListView 的核心区别是什么?](#1. RecyclerView 与 ListView 的核心区别是什么?)
- [2. 四级缓存(mAttachedScrap、mCachedViews、mViewCacheExtension、mRecyclerPool)各自作用、优先级与降级策略,不同滑动 / 刷新场景的命中逻辑?](#2. 四级缓存(mAttachedScrap、mCachedViews、mViewCacheExtension、mRecyclerPool)各自作用、优先级与降级策略,不同滑动 / 刷新场景的命中逻辑?)
- [3. mAttachedScrap 与 mCachedViews 的本质区别,为何 detach 而非 remove?](#3. mAttachedScrap 与 mCachedViews 的本质区别,为何 detach 而非 remove?)
- [4. ViewHolder 定义、复用触发条件、创建时机,放入 RecycledViewPool 的条件?如何自定义复用池大小与策略?](#4. ViewHolder 定义、复用触发条件、创建时机,放入 RecycledViewPool 的条件?如何自定义复用池大小与策略?)
- [5.onBindViewHolder 重复调用的原因及优化方案?](#5.onBindViewHolder 重复调用的原因及优化方案?)
- [6. 预取(Prefetch)机制原理、LinearLayoutManager 预取距离 / 数量计算逻辑?](#6. 预取(Prefetch)机制原理、LinearLayoutManager 预取距离 / 数量计算逻辑?)
- [二、LayoutManager 布局](#二、LayoutManager 布局)
-
- [1. LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager 的布局逻辑差异?](#1. LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager 的布局逻辑差异?)
- [2. onLayoutChildren 完整执行流程(锚点定位、填充、回收),自定义 LayoutManager 如何处理异常布局?](#2. onLayoutChildren 完整执行流程(锚点定位、填充、回收),自定义 LayoutManager 如何处理异常布局?)
- [5. 自定义 LayoutManager 核心方法(measureChild、layoutDecorated、scrollVerticallyBy/scrollHorizontallyBy)作用,dx/dy 如何正确消费?](#5. 自定义 LayoutManager 核心方法(measureChild、layoutDecorated、scrollVerticallyBy/scrollHorizontallyBy)作用,dx/dy 如何正确消费?)
- [6. GridLayoutManager Span 分配机制,动态 SpanSize 布局冲突解决方案?](#6. GridLayoutManager Span 分配机制,动态 SpanSize 布局冲突解决方案?)
- [7. 实现多布局的思路,无限循环 LayoutManager 需规避的边界问题?](#7. 实现多布局的思路,无限循环 LayoutManager 需规避的边界问题?)
- [三、Adapter 与数据刷新](#三、Adapter 与数据刷新)
-
- [8. notifyDataSetChanged、notifyItemChanged、notifyItemRangeChanged 的区别与性能影响?](#8. notifyDataSetChanged、notifyItemChanged、notifyItemRangeChanged 的区别与性能影响?)
- [9. DiffUtil 核心算法(Myers)原理、时间 / 空间复杂度,自定义 DiffUtil.Callback 方式?](#9. DiffUtil 核心算法(Myers)原理、时间 / 空间复杂度,自定义 DiffUtil.Callback 方式?)
- [10.AsyncListDiffer 异步计算逻辑、线程池策略,如何避免主线程阻塞?](#10.AsyncListDiffer 异步计算逻辑、线程池策略,如何避免主线程阻塞?)
- [11. payload 精准局部刷新原理](#11. payload 精准局部刷新原理)
- [12. notifyItemMoved 底层实现,为何移动操作会导致其他 Item 重复 bind?](#12. notifyItemMoved 底层实现,为何移动操作会导致其他 Item 重复 bind?)
- 四、分割线、动画与交互
-
- [13. 自定义 ItemDecoration 核心方法(getItemOffsets、onDraw、onDrawOver)作用?](#13. 自定义 ItemDecoration 核心方法(getItemOffsets、onDraw、onDrawOver)作用?)
- [14. DefaultItemAnimator 原理,自定义增删改动画的思路?](#14. DefaultItemAnimator 原理,自定义增删改动画的思路?)
- [15. ItemTouchHelper 工作原理,侧滑删除、拖拽排序的实现逻辑?](#15. ItemTouchHelper 工作原理,侧滑删除、拖拽排序的实现逻辑?)
- [16. RecyclerView 点击 / 长按事件最佳实现方式?](#16. RecyclerView 点击 / 长按事件最佳实现方式?)
- [17. ItemAnimator 各动画方法(animateChange/animateRemove)执行时机与视图层级操作?](#17. ItemAnimator 各动画方法(animateChange/animateRemove)执行时机与视图层级操作?)
- 五、滑动机制
-
- [18. NestedScrollingChild2/3 嵌套滑动完整流程?](#18. NestedScrollingChild2/3 嵌套滑动完整流程?)
- [19. 内外层 RecyclerView 嵌套滑动事件分发拦截逻辑,如何实现协同滑动?](#19. 内外层 RecyclerView 嵌套滑动事件分发拦截逻辑,如何实现协同滑动?)
- [20. RecyclerView 与 CoordinatorLayout/AppBarLayout 联动的 Behavior 原理?](#20. RecyclerView 与 CoordinatorLayout/AppBarLayout 联动的 Behavior 原理?)
- [21. 自定义惯性滑动(fling),修改摩擦系数与速度的方式?](#21. 自定义惯性滑动(fling),修改摩擦系数与速度的方式?)
- [22. onScrollStateChanged 三种状态切换逻辑?](#22. onScrollStateChanged 三种状态切换逻辑?)
- [23. 请完整描述 RecyclerView 从手指按下到滑动停止的全流程机制?包括事件分发、scroll 处理、View 填充与回收、惯性滑动、嵌套滑动的整体逻辑。***](#23. 请完整描述 RecyclerView 从手指按下到滑动停止的全流程机制?包括事件分发、scroll 处理、View 填充与回收、惯性滑动、嵌套滑动的整体逻辑。***)
- [24. RecyclerView 为什么滑动时不会一次性把所有 Item 布局出来?它是如何做到 "边滑边布局、边滑边回收" 的?***](#24. RecyclerView 为什么滑动时不会一次性把所有 Item 布局出来?它是如何做到 “边滑边布局、边滑边回收” 的?***)
- 六、性能优化与内存
-
- [1. 原题:RecyclerView 卡顿、OOM 常见原因及优化方案?](#1. 原题:RecyclerView 卡顿、OOM 常见原因及优化方案?)
- [2. 大数据量(万级 / 10w+)分页加载、缓存池优化方案?](#2. 大数据量(万级 / 10w+)分页加载、缓存池优化方案?)
- [3. 滑动时频繁 GC 规避方案,过度绘制排查?](#3. 滑动时频繁 GC 规避方案,过度绘制排查?)
- [4. ViewHolder/Adapter 内存泄漏场景及根治方案,离屏渲染使用场景与副作用?](#4. ViewHolder/Adapter 内存泄漏场景及根治方案,离屏渲染使用场景与副作用?)
- 七、源码级细节
-
- [1. RecyclerView 绘制流程(dispatchDraw)与 ItemView 绘制顺序?](#1. RecyclerView 绘制流程(dispatchDraw)与 ItemView 绘制顺序?)
- [2. GapWorker 机制在预取与异步布局中的作用?](#2. GapWorker 机制在预取与异步布局中的作用?)
- [3. tryGetViewHolderForPosition 方法完整执行逻辑?](#3. tryGetViewHolderForPosition 方法完整执行逻辑?)
- [4. "scrap" 与 "recycle" 核心概念理解?](#4. “scrap” 与 “recycle” 核心概念理解?)
- [5. 粘性头部(Sticky Header)实现思路?](#5. 粘性头部(Sticky Header)实现思路?)
一、基础原理与复用缓存
1. RecyclerView 与 ListView 的核心区别是什么?
- 架构解耦:RecyclerView将布局(LayoutManager)、复用(Recycler)、动画(ItemAnimator)、分割线(ItemDecoration)模块化,ListView高度耦合。
- 缓存机制:RecyclerView采用四级缓存(mAttachedScrap、mCachedViews、mViewCacheExtension、mRecyclerPool),ListView仅两级缓存。
- 布局能力:RecyclerView 支持线性、网格、瀑布流及自定义布局,ListView 仅支持线性布局。
- 刷新机制 :RecyclerView 支持 DiffUtil 局部精准刷新,ListView 仅支持全局
notifyDataSetChanged。 - 嵌套滑动 :RecyclerView 实现 NestedScrolling 接口 ,支持嵌套滑动,ListView 嵌套易卡顿。
快速记忆:解耦强、缓存多、布局全、刷新准、嵌套顺。
2. 四级缓存(mAttachedScrap、mCachedViews、mViewCacheExtension、mRecyclerPool)各自作用、优先级与降级策略,不同滑动 / 刷新场景的命中逻辑?
- 优先级:mAttachedScrap > mCachedViews > mViewCacheExtension > mRecyclerPool
- mAttachedScrap(屏幕内缓存):存储屏幕内 detach 的 View,保留完整状态,布局重构时直接复用,无需 rebind。
- mCachedViews(屏外缓存):缓存刚滑出屏幕的 ViewHolder(默认 2 个),保留状态,回滑时优先命中,无需rebind。
- mViewCacheExtension(自定义缓存):开发者扩展的中间层缓存,优先级高于复用池。
- mRecyclerPool(复用池) :按 ViewType 分组缓存,清空 View 状态,复用需重新 bind,支持跨 Adapter 复用。
- 场景:快速滑动优先命中 Pool;回滑优先命中 Cache;布局刷新优先命中 Scrap。
快速记忆:屏内 Scrap → 近屏 Cache → 自定义 → 复用池;越靠前,复用成本越低。
3. mAttachedScrap 与 mCachedViews 的本质区别,为何 detach 而非 remove?
- mAttachedScrap:存储屏幕内可见 View,通过 detach 临时分离,用于布局重构(如旋转、局部刷新),视图层级保留。
- mCachedViews:存储刚滑出屏幕的 View,完整保留 View 状态,用于快速回滑复用。
- detach vs remove:detach 仅临时脱离父容器,保留视图属性与状态;remove 完全移除视图,需重新 addView 重建
快速记忆:Scrap 管屏内、Cache 管屏外;detach 暂离、remove 销毁。
4. ViewHolder 定义、复用触发条件、创建时机,放入 RecycledViewPool 的条件?如何自定义复用池大小与策略?
- 定义 :ViewHolder 是一种用于优化列表项(如 ListView 或RecyclerView)性能的设计模式 。它的核心思想是将视图的引用缓存起来,避免每次 getView() 调用时都去查找视图控件(即减少 findViewById() 的调用次数)
- 复用条件 :ViewType 一致、position 匹配、缓存命中。
- 创建时机:各级缓存均未命中时,通过 Adapter.onCreateViewHolder 创建。
- 入池条件 :滑出屏幕、CachedViews 已满、非固定位置 View。
- 自定义复用池大小与策略:通过 setRecycledViewPool () 设置自定义池,setMaxRecycledViews (viewType, max) 配置单类型容量。
快速记忆:同类型复用;无缓存创建;Cache 满入池;可自定义容量。
5.onBindViewHolder 重复调用的原因及优化方案?
- 原因:数据刷新、布局重测、复用池重建、焦点变更、嵌套滑动冲突。
- 优化:使用 payload 实现局部刷新;开启 setHasFixedSize (true) 减少重测;避免 Item 布局动态变更;降低notify 调用频率。
快速记忆:刷新 / 布局 / 复用导致;payload + 固定尺寸 + 少通知。
6. 预取(Prefetch)机制原理、LinearLayoutManager 预取距离 / 数量计算逻辑?
- 原理 :通过 GapWorker 异步线程,在滑动时提前加载下一屏 Item,降低滑动卡顿。
- 计算 :LinearLayoutManager 根据滑动速度、屏幕剩余空间计算预取距离(默认一屏),预取数量 = 当前可见 Item 数 +预取 Item 数
快速记忆:GapWorker 异步预取;按速度 + 空间取一屏。
二、LayoutManager 布局
1. LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager 的布局逻辑差异?
- LinearLayoutManager:线性排列,单行或单列,布局方向可控。
- GridLayoutManager:网格布局,均分 Span 宽度 / 高度,支持跨行跨列。
- StaggeredGridLayoutManager:瀑布流布局,Item 高度 / 宽度不定,自动填充空白,无均分规则。
快速记忆:Linear 线、Grid 格、Staggered 流。
2. onLayoutChildren 完整执行流程(锚点定位、填充、回收),自定义 LayoutManager 如何处理异常布局?
流程:
- 确定布局锚点(锚 View 或锚位置)
- 清空 Scrap 与缓存
- 向上下 / 左右填充可见区域
- 回收超出屏幕的 View
- 调整边界偏移。
异常处理:处理边界越界、数据为空、锚点失效等场景,避免布局崩溃
快速记忆:定锚→清缓存→填充→回收→调偏移。
5. 自定义 LayoutManager 核心方法(measureChild、layoutDecorated、scrollVerticallyBy/scrollHorizontallyBy)作用,dx/dy 如何正确消费?
- measureChild:测量子 View 尺寸。
- layoutDecorated:布局子 View(包含分割线偏移)。
- scrollBy:处理滑动偏移,返回实际消费的 dx/dy。
- dx/dy 消费:表示横向/纵向滑动的距离(水平/垂直方向 )。根据滑动方向与边界限制,计算可滑动距离,返回实际滚动值。
快速记忆:测尺寸、布视图、滑偏移;返回实际滚动量。
6. GridLayoutManager Span 分配机制,动态 SpanSize 布局冲突解决方案?
- 通过 setSpanSizeLookup () 自定义每个 Item 所占 Span 数量;
- 动态变更 Span 时需调用 notify 触发重布局,避免布局错位;处理边界 Span 分配防止越界。
快速记忆:SpanLookup 定占比;动态改需重布局。
7. 实现多布局的思路,无限循环 LayoutManager 需规避的边界问题?
- 多布局 :通过 getItemViewType 区分类型,对应多 ViewHolder。
- 无限循环 :getItemCount 返回极大值,position 取模映射;规避滑动卡顿、边界闪烁、焦点错乱问题。
快速记忆:ViewType 分类型;取模做循环。
三、Adapter 与数据刷新
8. notifyDataSetChanged、notifyItemChanged、notifyItemRangeChanged 的区别与性能影响?
- notifyDataSetChanged:全局刷新,所有 Item 重建 rebind,性能最差。
- notifyItemChanged:单 Item 刷新,支持 payload 局部刷新,性能中等。
- notifyItemRangeChanged:批量局部刷新,性能最优。
快速记忆:全局最差、单条中等、批量最优。
9. DiffUtil 核心算法(Myers)原理、时间 / 空间复杂度,自定义 DiffUtil.Callback 方式?
- 原理:基于 Eugene W. Myers 差分算法,计算新旧数据集最小差异集合(增删改移)。复杂度:O (N+D),N 为数据量,D为差异数。
- 自定义:继承 DiffUtil.Callback,实现getOldListSize、getNewListSize、areItemsTheSame、areContentsTheSame、getChangePayload。
快速记忆:差分找最小差异;O (N+D);五步回调自定义。
10.AsyncListDiffer 异步计算逻辑、线程池策略,如何避免主线程阻塞?
在后台线程执行 Diff 计算 ,计算完成后主线程应用更新结果;使用独立线程池避免阻塞;通过锁保证数据一致性。
快速记忆:后台算、主线更;线程池防阻塞。
11. payload 精准局部刷新原理
- 在 Android 中,RecyclerView 的 局部刷新是通过 notifyItemRangeChanged(int
positionStart, int itemCount, Object payload) 实现的。其核心原理是 利用 payload
参数传递差异信息,只刷新 item 中需要更新的部分 UI,而不是整个 item。
12. notifyItemMoved 底层实现,为何移动操作会导致其他 Item 重复 bind?
- 移动操作改变相邻 Item 位置,触发位置校验与 rebind;
- 优化:减少移动范围,使用 DiffUtil 批量处理移动。
快速记忆:移动改位置→相邻重绑;Diff 批量优化。
四、分割线、动画与交互
13. 自定义 ItemDecoration 核心方法(getItemOffsets、onDraw、onDrawOver)作用?
-
getItemOffsets:设置 Item 四周间距,占用布局空间。
-
onDraw:在 ItemView 下层绘制(如分割线)。
-
onDrawOver:在 ItemView 上层绘制(如角标、遮罩)。
快速记忆:Offsets 占空、Draw 下层、Over 上层。
14. DefaultItemAnimator 原理,自定义增删改动画的思路?
- 原理:默认处理 Item 增删改移的过渡动画;
- 自定义:继承 SimpleItemAnimator,重写animateAdd、animateRemove、animateChange、animateMove。
快速记忆:四动画(增删改移);重写方法自定义。
15. ItemTouchHelper 工作原理,侧滑删除、拖拽排序的实现逻辑?
- 监听滑动 / 拖拽手势,回调 onSwiped(侧滑)、onMove(拖拽)
- 实时更新 ViewHolder 偏移,同步修改数据源。
快速记忆:侧滑删、拖拽移;回调改数据。
16. RecyclerView 点击 / 长按事件最佳实现方式?
在 ViewHolder 中绑定点击事件,避免重复设置;通过接口回调解耦 Adapter 与业务层。
17. ItemAnimator 各动画方法(animateChange/animateRemove)执行时机与视图层级操作?
- animateChange:Item 内容变更时执行,处理新旧视图过渡。
- animateRemove:Item 移除时执行,处理视图退场动画。
- 层级:在 dispatchDraw 阶段执行动画,不阻塞布局流程。
快速记忆:改时 Change、删时 Remove;绘制阶段执行。
五、滑动机制
18. NestedScrollingChild2/3 嵌套滑动完整流程?
- 子 View 发起滑动 → 父 View 预拦截 → 父子协同消费滑动距离 → 惯性滑动联动;Child3 支持链式传递。
快速记忆:子发→父拦→协同消费。
19. 内外层 RecyclerView 嵌套滑动事件分发拦截逻辑,如何实现协同滑动?
- 按方向分流:父层拦截一个方向,子层处理另一方向;
- 通过 requestDisallowInterceptTouchEvent 控制父层拦截。
快速记忆:分方向拦截;禁止父拦截。
20. RecyclerView 与 CoordinatorLayout/AppBarLayout 联动的 Behavior 原理?
- Behavior 监听嵌套滑动,根据偏移量动态调整关联 View(如折叠 AppBar)
- 重写 onNestedScroll 处理联动逻辑。
快速记忆:监听滑动;联动偏移。
21. 自定义惯性滑动(fling),修改摩擦系数与速度的方式?
- 重写 fling () 方法;修改 VelocityTracker 速度、ViewConfiguration 摩擦系数,控制惯性滑动距离。
快速记忆:改速度 + 摩擦;控惯性。
22. onScrollStateChanged 三种状态切换逻辑?
- IDLE:静止状态。
- DRAGGING:手指触摸拖动。
- SETTLING:手指离开后的惯性滑动。
- 切换:触摸→DRAGGING;松开→SETTLING;停止→IDLE。
快速记忆:静、拖、滑。
23. 请完整描述 RecyclerView 从手指按下到滑动停止的全流程机制?包括事件分发、scroll 处理、View 填充与回收、惯性滑动、嵌套滑动的整体逻辑。***
- 事件分发:触摸事件经由 onInterceptTouchEvent 判断是否拦截,按下时记录起点坐标,移动时判定为滑动意图后开始消费事件。
- 手指拖动:计算偏移 dy/dx → 通过 scrollByInternal 消费距离 → 触发LayoutManager 的 scrollVerticallyBy/scrollHorizontallyBy → 调用 fill()方法填充新可见区域、回收滑出屏幕的 Item。
- 填充与回收逻辑:滑动时先从缓存获取 ViewHolder,填充到新位置;超出屏幕的 View 按优先级存入 CachedViews 或RecycledViewPool。
- 惯性滑动:手指抬起时通过 VelocityTracker 获取滑动速度 → 交给 OverScroller 计算减速轨迹 → 通过 computeScroll() 持续回调更新偏移 → 同步执行填充与回收。
- 嵌套滑动:支持遵循 NestedScrollingChild2/3 机制,滑动前先询问父 View 是否消费部分距离,实现协同滑动(如与AppBarLayout 联动)。
- 滑动停止: 惯性速度衰减为 0 或手动打断滑动时,状态切换为 IDLE,完成一次完整滑动流程。
快速记忆:事件拦截 → 计算偏移 → 填充回收 → 惯性滚动 → 嵌套协同 → 停止 idle。
24. RecyclerView 为什么滑动时不会一次性把所有 Item 布局出来?它是如何做到 "边滑边布局、边滑边回收" 的?***
- RecyclerView 不会全量布局,而是采用可见区域驱动的增量布局:
- 只在 onLayoutChildren 和滑动时布局当前屏幕可见范围的 Item。
- 滑动过程中通过 fill() 方法动态向滑动方向填充 即将显示的 Item,同时回收反方向超出屏幕的 Item。
- 配合四级缓存实现 ViewHolder 复用,避免频繁创建销毁 View。
- 结合预取机制提前加载下一屏 Item,保证滑动流畅。
快速记忆:只布局可见区域;滑动填充 + 回收;缓存复用;预取兜底。
六、性能优化与内存
1. 原题:RecyclerView 卡顿、OOM 常见原因及优化方案?
- 卡顿:onBind 耗时、布局复杂、过度绘制、频繁 GC。
- OOM:图片过大、复用池溢出、内存泄漏。
- 优化:异步加载、简化布局、减少对象创建、复用池调优、图片压缩。
快速记忆:耗时 / 复杂 / 泄漏→卡顿 OOM;异步 + 简化 + 复用。
2. 大数据量(万级 / 10w+)分页加载、缓存池优化方案?
- 分页加载 + DiffUtil 局部刷新;扩容复用池;图片三级缓存;避免动态布局。
快速记忆:分页 + 差分 + 大复用池 + 图缓存。
3. 滑动时频繁 GC 规避方案,过度绘制排查?
- GC:避免 onBind 创建对象,使用对象池。
- 过度绘制:减少重复背景、使用 clipRect、精简 onDrawForeground 逻辑。
快速记忆:少创建防 GC;少背景防过度绘。
4. ViewHolder/Adapter 内存泄漏场景及根治方案,离屏渲染使用场景与副作用?
-
泄漏:ViewHolder 持有 Context、Adapter 匿名内部类 → 弱引用、静态内部类。
-
离屏渲染:setLayerType 开启,用于复杂动画;副作用:占用显存,低机型卡顿。
快速记忆:弱引用防泄漏;离屏渲染慎开。
七、源码级细节
1. RecyclerView 绘制流程(dispatchDraw)与 ItemView 绘制顺序?
- drawBackground → dispatchDraw(绘制子 View)→ drawForeground → 绘制
ItemDecoration。
快速记忆:背景→子项→前景→分割线。
2. GapWorker 机制在预取与异步布局中的作用?
- 异步线程执行预取、布局计算,分担主线程压力,提升滑动流畅度。
快速记忆:异步干活;主线减负。
3. tryGetViewHolderForPosition 方法完整执行逻辑?
- 查找 Scrap → 查找 Cache → 查找 Extension → 查找 Pool → 未命中则创建 → 绑定数据。
快速记忆 :逐级查缓存;无则新建。
4. "scrap" 与 "recycle" 核心概念理解?
- scrap:临时 detach,保留状态,快速复用
- recycle:完全回收,清空状态,进入复用池
快速记忆:scrap 暂存;recycle 清空。
5. 粘性头部(Sticky Header)实现思路?
- 通过 ItemDecoration.onDrawOver 在顶层绘制固定头部;
- 监听滑动,动态更新头部内容与位置。
快速记忆:Decoration 上层画;滑动更内容。