RecyclerView
RecyclerView是列表控件,用于展示大量的数据。不过会随着数据的增加,RecyclerView性能会受到影响,导致卡顿、内存泄漏等
简单使用
mBinding.recyclerView.layoutManager = LinearLayoutManager(context)
val adapter = ItemAdapter(context, data) { title ->
}
mBinding.recyclerView.adapter = adapter
// Adapter
class ItemAdapter(private val context: Context, private var data: List<String>,
private val click: (title: String) -> Unit): RecyclerView.Adapter<ItemAdapter.Holder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder =
Holder(ItemBinding.inflate(LayoutInflater.from(context), parent, false))
override fun onBindViewHolder(holder: OcrHolder, position: Int) {
val title = data[position]
holder.binding.ocrPropTitle.text = title
holder.binding.root.setOnClickListener {
click(title)
}
}
override fun getItemCount(): Int = data.size
@SuppressLint("NotifyDataSetChanged")
fun submit(list: List<String>) {
data = list
notifyDataSetChanged()
}
inner class Holder(val binding: ItemBinding): RecyclerView.ViewHolder(binding.root)
}
优化
- 布局优化:减少嵌套
ConstraintLayout、merge标签 - 减少绘制:差异化刷新
notifyItemChanged(int position)、DiffUtil - 滑动优化:滑动时暂停耗时操作
例glide取消加载图片 - 预加载:预加载即将显示item,提高展示性能
- 内存优化:及时释放内存,避免内存泄漏
布局优化
- 减少嵌套层级
复杂布局合理使用ConstraintLayout、merge标签合并布局 - RecyclerView.setHasFixedSize(true)
固定item高度
item高度不变的列表,设置后不会因item改变触发重新计算,避免requestLayout导致的资源浪费
绘制优化
- 分页
减少每页加载数据量 - 局部刷新
只更新变化列表项、notifyItemChanged(int position) - DiffUtil差异性处理
DiffUtil高效计算数据差异,并将结果用到RecyclerView中
val Diff(private val old: ArrayList<String>, private val new: ArrayList<String>): DiffUtil.Callback() {
override fun getOldListSize() = old.size
override fun getNewListSize() = new.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
old[oldItemPosition] == new[newItemPosition]
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
old[oldItemPosition] == new[newItemPosition]
}
val diff = DiffUtil.calculateDiff(Diff(old, new))
diff.dispatchUpdatesTo(adapter)
滑动优化
- onCreateViewHolder()初始化
设置点击事件 - onBindViewHolder()绑定数据
避免耗时操作 - 添加滑动监听
RecyclerView.addOnScrollListener(listener)
recyclerView.addOnScrollListener(object: RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
// 空闲
loaidng()
} else {
// 非空闲状态,例Glide.with(fragment).clear(holder.iv)
pause()
}
}
})
预加载
- calculateExtraLayoutSpace
预留额外空间,提前加载屏幕外Item,避免滑动中卡顿 - collectAdjacentPrefetchPositions
滑动中预取相邻item数据,提高滑动流畅度
class LayoutManager(context: Context, orientation: Int = 1, reverseLayout: Boolean = false):
LinearLayoutManager(context, orientation, reverseLayout) {
constructor(context: Context) : super(context, 1)
override fun calculateExtraLayoutSpace(state: RecyclerView.State, extraLayoutSpace: IntArray) {
super.calculateExtraLayoutSpace(state, extraLayoutSpace)
// 设置额外的布局空间,可以根据需要动态计算
extraLayoutSpace[0] = 300
extraLayoutSpace[1] = 300
}
override fun collectAdjacentPrefetchPositions(dx: Int, dy: Int, state: RecyclerView.State?,
layoutPrefetchRegistry: LayoutPrefetchRegistry) {
super.collectAdjacentPrefetchPositions(dx, dy, state, layoutPrefetchRegistry)
// 根据滑动方向(dx, dy)收集相邻的预取位置
val anchorPos = findFirstVisibleItemPosition()
if (dy > 0) {
// 下滑,预取下面的Item数据
for (i in anchorPos + 1 until state?.itemCount ?: 0) {
layoutPrefetchRegistry.addPosition(i, 0)
}
} else {
// 上滑,预取上面的Item数据
for (i in anchorPos - 1 downTo 0) {
layoutPrefetchRegistry.addPosition(i, 0)
}
}
}
}
内存优化
- 共用RecyclerViewPool
适用于多个RecyclerView之间的数据或布局结构相似的场景
val pool = RecyclerView.RecycledViewPool()
recycler1.setRecycledViewPool(pool)
recycler2.setRecycledViewPool(pool)
- adapter.setHasStableIds(true)
提高复用ViewHolder稳定性,减少内存消耗 - recyclerView.setItemViewCacheSize(30)
设置RV中ViewHolder缓存数量 - 重写Adapter#onViewRecycled(holder)
资源回收,避免内存泄漏和资源浪费
override fun onViewRecycled(holder: ViewHolder) {
super.onViewRecycled(holder)
// 释放图片资源
holder.binding.iv.setImageDrawable(null)
// 移除监听器
holder.binding.root.setOnClickListener(null)
}
缓存机制
为提高列表滚动时的性能,而采用多级缓存策略来存储和复用View,减少View的创建和销毁,进而减少内存分配和GC触发频率
Recycler
- 负责回收和复用ViewHolder的类
class Recycler {
// 存放可视范围内的ViewHolder(但在onLayoutChildren的时,会将所有View都缓存到这),复用时若position或id对应上,则无需重新绑定数据
val mAttachedScrap: ArrayList<ViewHolder> = ArrayList()
// 存放可视范围内数据发生变化的ViewHolder,复用时需重新绑定数据
var mChangedScrap: ArrayList<ViewHolder>? = null
// 存放remove的ViewHolder,复用时若position或id对应上,则无需重新绑定数据
val mCachedViews: ArrayList<ViewHolder> = ArrayList()
// 默认值为2
private var mRequestedCacheMax: Int = DEFAULT_CACHE_SIZE
// 默认值为2
var mViewCacheMax: Int = DEFAULT_CACHE_SIZE
// 存放remove && 重置数据的ViewHolder,复用时需要重新绑定数据。 默认大小是 5
var mRecyclerPool: RecycledViewPool? = null
// 自定义的缓存
private var mViewCacheExtension: ViewCacheExtension? = null
}
一级缓存(Scrap缓存)
- 屏内缓存,包括
mAttachedScrap和mChangedScrap,用于保存屏内可视或即将可视的ViewHolder - mAttachedScrap:存放已加入RecyclerView但与RecyclerView临时分离的ViewHolder(例滚动中场景)
- mChangedScrap:存放数据已改变但尚未重新绑定数据的ViewHolder(例动画播放场景)
二级缓存(Cache缓存)
- 离屏缓存,mCachedViews,用于保存尚未被回收的ViewHolder
- 大小有限制,默认大小为2
- 当需展示新视图时,会先检查Cache缓存中是否有可用的ViewHolder
三级缓存(ViewCacheExtension)
- 自定义缓存,由开发者自定义策略,按需缓存更多的ViewHolder
- 通过实现ViewCacheExtension接口扩展
终极缓存(RecycledViewPool)
- 回收缓存池,mRecyclerPool,用于存放被标记为废弃的ViewHolder(其他缓存不需要的ViewHolder)
- 池中ViewHolder已被抹除数据,使用时需重新绑定数据
- RecycledViewPool会根据itemType创建不同的集合存储ViewHolder
策略
- RecyclerView滚动时,先移除滑出屏幕的item,并将
ViewHolder缓存至mCachedViews中,若mCachedViews(有大小限制)已满,会将最早加入的ViewHolder移除并存入RecycledViewPool中。复用时逐级从缓存中获取ViewHolder,不为null复用,为null创建新的ViewHolder - 当某个item数据发生变化时,若ViewHolder是可视的,则会被缓存至
mChangedScrap中,需重新绑定数据时,直接从缓存中获取该ViewHolder - 删除item时,其对应的ViewHolder会先后进入Scrap缓存、Cached缓存、RecycledViewPool