RecycleView_ ListView原理

ListView 原理

ListView 实现成百上千条数据都不会 OOM 的原因
因为 getview 的第二个参数 convertView 就是之前用过的 VIew 对象,当 view 对象完全移除屏幕后,会存入 RecycleBin 对象里,当 listview 的屏幕外下面的 view 滑出来的时候,会从 RecycleBin 里取出复用。ListView 中的子 View 其实来来回回就那么几个,移出屏幕的子 View 会很快被移入屏幕的数据重新利用起来,所以不会 OOM
unknown_filename.3

coverview 原理
这个参数用于将之前加载好的布局进行缓存,以便之后可以进行重用,在 getView ()方法中进行了判断,如果 convertView 为空,则使用 LayoutInflater 去加载布局,如果不为空则直接对 convertView 进行重用

RecycleView 和 ListView 对比

RecycleView 是 ListView 的升级版,和 ListView 一样,RecyclerView 是用来显示大量数据的容器,并通过复用有限数量的 View,来提高滚动时的性能。与 ListView 不同的是 RecyclerView不负责布局,只专注于复用机制,布局交由 LayoutManager 来管理。

ListView 的局限

  • 只有纵向列表一种布局
  • 没有支持动画的 API
  • 没有强制实现 ViewHolder

RecyclerView 的优势

  • 默认支持 Linear, Grid, Staggered Grid 三种布局
  • 友好的 ItemAnimator 动画 API
  • 强制实现 ViewHolder
  • 解耦的架构设计(adapter、layoutManager、item animator)
  • 局部刷新
  • 缓存有区别

RecycleView 原理

用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private List<String> dataList;

public MyAdapter(List<String> dataList) {
this.dataList = dataList;
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout, parent, false);
return new ViewHolder(view);
}

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
String item = dataList.get(position);
holder.textView.setText(item);
}

@Override
public int getItemCount() {
return dataList.size();
}

public static class ViewHolder extends RecyclerView.ViewHolder {
TextView textView;

public ViewHolder(View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.textView);
}
}
}

缓存

这10张图拿去,别再说学不会RecyclerView的缓存复用机制了!

unknown_filename.1
unknown_filename.2

RecyclerView 缓存分层:

第一层缓存 Scrap

(/skræp/ 碎片、小块)(mAttachedScrap)屏幕内缓存,可直接复用,无需重新绑定,且用 position 寻找缓存,这个缓存结构实际上更多是为了避免出现像局部刷新这一类的操作,导致所有的列表项都需要重绘的情形

1
inal ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();

unknown_filename.7|700

第二层缓存 mCachedViews

mCachedViews 主要用于存放已被移出屏幕、但有可能很快重新进入屏幕的列表项,它的默认大小是2。可直接复用,无需重新绑定,且用 position 寻找缓存,

1
2
3
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
int mViewCacheMax = DEFAULT_CACHE_SIZE;
static final int DEFAULT_CACHE_SIZE = 2;

unknown_filename.8|700

第三层 ViewCacheExtension

开发者自定义控制的 View 缓存帮助类。一般而言,我们不会自定义缓存实现,使用 Recycler 提供的3级缓存足够。

第四层 RecycledViewPoll

所有被废弃的 ItemView 的池(没有废弃使用)

mRecyclerPool 主要用于按不同的 itemType 分别存放超出 mCachedViews 限制的、被移出屏幕的列表项,其会先以 SparseArray 区分不同的 itemType,然后每种 itemType 对应的值又以 ArrayList 的形式持有着每个列表项的 ViewHolder 对象,每种 itemType 的 ArrayList 大小限制默认为5。

由于 mCachedViews 默认的大小限制仅为2,因此,当滑出屏幕的列表项超过2个后,就会按照先进先出的顺序,依次将 ViewHolder 对象从 mCachedViews 移出,并按 itemType 放入 RecycledViewPool 中的不同 ArrayList。

注意

  • Scrap 与 Cache 是通过 position 来找到缓存的,并且他的数据是肯定的,所以不需要重新绑定数据
  • RecycledViewPool 是通过 viewType 来找到缓存的,并且他的数据是脏数据,找到缓存的话,需要重新绑定数据
  • RecycleView.setItemViewCacheSize (size) 加大 RecyclerView 缓存空间, 利用空间换时间策阅来提高流畅性。
  • 多个 RecyclerView 共用 RecycledViewPool,比如 RecyclerView 里又有多类似的 RecyclerView,竖向里有横向
1
2
3
4
5
RecyclerView. RecycledViewPool recycledViewPool =
new RecyclerView. RecycledView Pool ( ) ;
recyclerView1. setRecycledViewPool ( recycledViewPool) ;
recyclerView2. setRecycledViewPool ( recycledViewPool) ;
recyclerView3. setRecycledViewPool ( recycledViewPool) ;

ListView、RecycleView 的优化

  • Item 的布局层次结构尽量简单,避免布局太深或者不必要的重绘
  • Item 高度固定, 设置 RecyclerView.setHasFixedSize (true), 避免不停的测量。requeLayout 资源浪费,
  • recycleview 加载1000条数据也没问题,因为会只加载一屏的数据,但是添加了 scroview 就会出现卡顿。如果是 listview,还需要指定他的高度, nested scroll view 会导致 recycleview 把数据全部加载出来
  • 如果不要求动画, 可用通过 ((SimpleItemAnimator) rv.getItemAnimator ()). setSupportsChangeAnimations (false); 把默认动画关闭来提高性能
  • 避免在 getView 方法中做耗时的操作,例如加载大量图片,当用户不停的滑动时,由于 ui 在主线程操作的,会出现卡顿,可以在滑动的时候停止加载 (setOnscrollerListener),在 getView 方法里只有静止才加载图片
1
2
3
4
5
6
7
8
9
10
11
if (view.getContext () != null) {
switch (scrollState) {
case SCROLL_STATE_IDLE:
Glide.with (view.getContext ()). resumeRequests ();
break;
case SCROLL_STATE_TOUCH_SCROLL:
case SCROLL_STATE_FLING:
Glide.with (view.getContext ()). pauseRequests ();
break;
}
}
  • 在一些场景中,ScollView 内会包含多个 ListView,可以把 listview 的高度写死固定下来。ScollView 在快速滑动过程中需要大量计算每一个 listview 的高度,阻塞了 UI 线程导致卡顿现象出现,如果我们每一个 item 的高度都是均匀的,可以通过计算把 listview 的高度确定下来,避免卡顿现象出现
  • 使用 RecycleView 代替 listview:每个 item 内容的变动,listview 都需要去调用 notifyDataSetChanged 来更新全部的 item,太浪费性能了。RecycleView 可以实现当个 item 的局部刷新,并且引入了增加和删除的动态效果,在性能上和定制上都有很大的改善
  • ListView 中元素避免半透明:半透明绘制需要大量乘法计算,在滑动时不停重绘会造成大量的计算,在比较差的机子上会比较卡。在设计上能不半透明就不不半透明。实在要弄就把在滑动的时候把半透明设置成不透明,滑动完再重新设置成半透明。
  • 复用历史的 view 对象 convertview
  • 减少 item 查询的次数 viewholder
  • 异步加载数据(把图片缓存)

其他注意

列表中 item/广告的 impression 统计
ListView getView
RecyclerView 通过 onBindViewHolder () 统计? 可能错误!
通过 onViewAttachedToWindow ()统计

点击监听器的设置用户不停的滑动就会不停的创建监听器,每次绑定一个新的。这样写会造成内存抖动。点击监听器只是一个观察者模式
unknown_filename

  1. 用户滑动到横向滑动的 item RecyclerView 的时候, 由于需要创建更复杂的
    RecyclerView 以及多个子 view, 可能会导致页面卡顿
  2. 由于 RenderThread 的存在, RecyclerView 会进行 prefetch
    • 只有 LinearLayoutManager API
    • 只有嵌套在内部的 RecyclerView 才会生效

unknown_filename.4

DiffUtil 提高列表性能的方法

它会自动计算新老数据集的差异,并根据差异情况,自动调用以下四个方法

1
2
3
4
adapter.notifyItemRangeInserted (position, count);
adapter.notifyItemRangeRemoved (position, count);
adapter.notifyItemMoved (fromPosition, toPosition);
adapter.notifyItemRangeChanged (position, count, payload);
  1. 局部更新方法 notifyltemXXX () 不适用于所有情况
  2. notifyDataSetChange () 会导致整个布局重绘, 重新绑定所有 ViewHolder, 而
    且会失去可能的动画效果
  3. DiffUtil 适用于整个页面需要刷新, 但是有部分数据可能相同的情况

DiffUtil 在使用起来,主要需要关注几个类:

DiffUtil. Callback:具体用于限定数据集比对规则。
DiffUtil. DiffResult:比对数据集之后,返回的差异结果。
1、DiffUtil. Callback
DiffUtil. Callback 主要就是为了限定两个数据集中子项的比对规则。毕竟开发者面对的数据结构多种多样,既然没法做一套通用的内容比对方式,那么就将比对的规则,交还给开发者来实现即可。

它拥有 4 个抽象方法和 1 个非抽象方法的抽象类。我们需要继承并实现它的所有方法:在自定义的 Callback 中,其实需要实现 4 个方法:

getOldListSize ():旧数据集的长度。
getNewListSize ():新数据集的长度
areItemsTheSame ():判断是否是同一个 Item。
areContentsTheSame ():如果是通一个 Item(即 areItemsTheSame 返回 true),此方法用于判断是否同一个 Item 的内容也相同。
如我们有两个 Object,它们可能拥有很多属性,但是其中只有两个属性需要被显示出来,那只要这两个属性一致我们这个方法就要返回 true。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public boolean areContentsTheSame (int oldItemPosition, int newItemPosition) {
TestBean beanOld = mOldDatas.get (oldItemPosition);
TestBean beanNew = mNewDatas.get (newItemPosition);
if (! beanOld.getDesc (). equals (beanNew.getDesc ())) {
return false;//如果有内容不同,就返回 false
}
if (beanOld.getPic () != beanNew.getPic ()) {
return false;//如果有内容不同,就返回 false
}
return true; //默认两个 data 内容是相同的
}


DiffUtil. DiffResult diffResult = DiffUtil.calculateDiff (new DiffCallBack (mOldList, mList), true);
diffResult.dispatchUpdatesTo (myAdapter);
mOldList = new ArrayList<>(mList);
myAdapter.setDatas (mList);

更高效地刷新 RecyclerView | DiffUtil二次封装 - 简书

    val oldList = ... // 老列表
    val newList = ... // 新列表
    val adapter:RecyclerView. Adapter = ...
    
    // 1. 定义比对方法
    val callback = object : DiffUtil.Callback () {
        override fun getOldListSize (): Int = oldList. size
        override fun getNewListSize (): Int = newList. size
        override fun areItemsTheSame (oldItemPosition: Int, newItemPosition: Int): Boolean {
            // 分别获取新老列表中对应位置的元素
            val oldItem = oldList[oldItemPosition]
            val newItem = newList[newItemPosition]
            return ... // 定义什么情况下新老元素是同一个对象(通常是业务 id)
        }
        override fun areContentsTheSame (oldItemPosition: Int, newItemPosition: Int): Boolean {
            val oldItem = oldList[oldItemPosition]
            val newItem = newList[newItemPosition]
            return ... // 定义什么情况下同一对象内容是否相同 (由业务逻辑决定)
        }
        override fun getChangePayload (oldItemPosition: Int, newItemPosition: Int): Any? {
            val oldItem = oldList[oldItemPosition]
            val newItem = newList[newItemPosition]
            return ... // 具体定义同一对象内容是如何地不同 (返回值会作为 payloads 传入 onBindViewHoder())
        }
    }
    // 2. 进行比对并输出结果
    val diffResult = DiffUtil.calculateDiff (callback)
    // 3\. 将比对结果应用到 adapter
    diffResult.dispatchUpdatesTo (adapter)

RecycleView_ ListView原理
http://peiniwan.github.io/2024/04/46c1c618dbc1.html
作者
六月的雨
发布于
2024年4月6日
许可协议