Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2019-08-07:说说你对RecyclerView的了解,是否了解其缓存的内部机制? #7

Open
zhjlong opened this issue Aug 7, 2019 · 11 comments

Comments

@zhjlong
Copy link
Collaborator

zhjlong commented Aug 7, 2019

No description provided.

@zhjlong zhjlong changed the title 2019-08-07:说说你对Recycler的了解,是否了解其缓存的内部机制? 2019-08-07:说说你对RecyclerView的了解,是否了解其缓存的内部机制? Aug 7, 2019
@MicroKibaco
Copy link
Owner

MicroKibaco commented Aug 7, 2019

RecyclerView 是什么?

  RecyclerView是Android5.0推出,Google工程师在support-v7包中引入的一个全新列表控件,用于显示庞大数据集容器,可通过保持有限数量的视图进行非常有效的滚动操作,它不关心item是否显示在正确的位置以及如何显示,通过LayoutManager控制布局是横向还是纵向,它不关心 item 如何 分隔,通过 ItemDecoration 来绘制分割线,它不关心 item 增加或删除的动画效果,你可以通过 ItemAnimation 绘制你想要的动画效果,RecyclerView仅仅关注如何回收和复用view,如果有数据集合,其中元素将因用户操作或网络事件而发生改变,建议使用RecyclerView

RecyclerView使用

一.添加依赖

  使用RecyclerView需要在 app/build.gradle添加 相关依赖,然后同步一下就可以使用依赖了:

 implementation 'com.android.support:recyclerview-v7:25.3.1'

二.编写代码

  创建布局文件

<androidx.constraintlayout.widget.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

   <androidx.recyclerview.widget.RecyclerView
           android:id="@+id/recyclerview"
           android:layout_width="match_parent"
           android:layout_height="match_parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

  创建完成后在Activity获取RecycleView对象,并声明 LayoutManager 与 Adapter,代码如下:

public class MainActivity extends AppCompatActivity implements SimpleAdapter.OnItemClickListener {


    private RecyclerView mRecyclerView;
    private List<String> mDatas;
    private SimpleAdapter mAmAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initDatas();
        initView();
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        final int itemId = item.getItemId();


        switch (itemId) {
            case R.id.action_add:
                mAmAdapter.addData(1);
                mRecyclerView.setAdapter(mAmAdapter);
                break;


            case R.id.action_delete:
                mAmAdapter.deleteData(1);
                mRecyclerView.setAdapter(mAmAdapter);
                break;


            case R.id.action_grid_view:
                mRecyclerView.setAdapter(new SimpleAdapter(this, mDatas, false));

                mRecyclerView.setLayoutManager(new GridLayoutManager(this, 5));
                break;
            case R.id.action_list_view:

                mRecyclerView.setAdapter(new SimpleAdapter(this, mDatas, true));
                mRecyclerView.setLayoutManager(new LinearLayoutManager(this));

                break;
            case R.id.action_hor_grid_view:

                mRecyclerView.setAdapter(new SimpleAdapter(this, mDatas, false));
                mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(5, StaggeredGridLayoutManager.HORIZONTAL));

                break;
            case R.id.action_stagger_view:
                startActivity(new Intent(this, StaggeredGridViewActivity.class));
                break;
            default:
                break;
        }
        return super.onOptionsItemSelected(item);
    }

    private void initView() {
        mRecyclerView = (RecyclerView) findViewById(R.id.recyclerview);
        mAmAdapter = new SimpleAdapter(this, mDatas, true);
        // 设置RecyclerView的布局管理
        mRecyclerView.setAdapter(mAmAdapter);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this, RecyclerView.HORIZONTAL, false));
        mAmAdapter.setOnItemClickListener(this);

    }

    private void initDatas() {
        mDatas = new ArrayList<>();

        for (int i = 'A'; i < 'z'; i++) {
            mDatas.add("" + (char) i);
        }
    }

    @Override
    public void onItemClick(View view, int position) {
        Toast.makeText(MainActivity.this, "Click:" + position
                , Toast.LENGTH_LONG).show();
    }

    @Override
    public void onItemLongClick(View view, int position) {
        mAmAdapter.deleteData(position);
    }
}

  RecycleView 和 ListView 一样需要适配器的,但是 RecycleView 需要设置 布局管理器(setLayoutManager),这是为了方便扩展,这里使用了 LinearLayoutManager.其中 Adapter 的创建比较关键,来看一下 SimpleAdapter 的代码:

public class SimpleAdapter extends RecyclerView.Adapter<MyViewHolder> {

    public LayoutInflater mInflater;
    private Context mContext;
    private List<String> mDatas;
    private boolean mIsListView;
    public OnItemClickListener mListener;

    public interface OnItemClickListener {

        void onItemClick(View view, int position);

        void onItemLongClick(View view, int position);
    }


    public void setOnItemClickListener(OnItemClickListener listener) {
        this.mListener = listener;

    }


    public SimpleAdapter(Context context, List<String> datas, boolean isListView) {

        this.mContext = context;
        this.mDatas = datas;
        this.mInflater = LayoutInflater.from(context);
        this.mIsListView = isListView;

    }


    public void addData(int position) {

        if (position >= 1) {
            mDatas.add(mDatas.get(position));
            notifyItemInserted(position);
        }

    }

    public void deleteData(int postion) {
        mDatas.remove(postion);
        notifyItemRemoved(postion);
    }

    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

        final View view = mInflater.inflate(mIsListView ? R.layout.item_simple_list_view : R.layout.item_simple_text_view, parent, false);
        MyViewHolder viewHolder = new MyViewHolder(view);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(@NonNull final MyViewHolder holder, final int position) {
        holder.tv.setText(mDatas.get(position));
        setUpItemEvent(holder);
    }

    public void setUpItemEvent(@NonNull final MyViewHolder holder) {
        if (mListener != null) {
            final int layoutPosition = holder.getLayoutPosition();
            holder.tv.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {

                    mListener.onItemClick(holder.tv, layoutPosition);
                }
            });

            holder.tv.setOnLongClickListener(new View.OnLongClickListener() {
                @Override
                public boolean onLongClick(View view) {
                    mListener.onItemLongClick(holder.tv, layoutPosition);
                    return false;
                }
            });
        }
    }

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

  上面用到 item_simple_text_view.xml 布局文件,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
             android:orientation="vertical"
             android:layout_width="wrap_content"
             android:background="@color/colorAccent"
             android:layout_margin="3dp"
             android:id="@+id/layout_frame"
             android:layout_height="wrap_content">

    <TextView
            android:id="@+id/id_tv"
            android:gravity="center"
            android:layout_width="72dp"
            android:layout_height="72dp"/>

</FrameLayout>

  SimpleAdapter 继承 RecyclerView.Adapter ,需要一个ViewHolder 泛型,创建 ViewHolder 需要继承 RecycleView.ViewHolder , ViewHolder 的构造方法需要传递 View 对象, View 对象 需要继承 RecycleView.ViewHolder,ViewHolder 构造方法需要传递 View对象,View对象会和 ViewHolder 进行绑定.

public class MyViewHolder extends RecyclerView.ViewHolder {
    TextView tv;

    public MyViewHolder(@NonNull View itemView) {
        super(itemView);
        tv = (TextView) itemView.findViewById(R.id.id_tv);
    }
}

  可以发现 RecyclerView.Adapter 设计针对性很强,强制程序员通过ViewHolder 和 复用 View 进行优化,不会出现之前 ListView 不采取 优化情况.这种机制避免创建过多View和频繁调用findViewById的方法

三.运行

写完这些代码后,这个simple就可以运行起来了,从例子也可以看出,RecycleView的用法并不比 ListView 复杂,反而更加灵活好用,它将数据,排列方式,数据展示方式都分割开来,因此自定义的形式也非常多,非常灵活

RecyclerView不同布局的排列方式

上面的效果是水平列表,还可以选择其他排列方式,非常灵活,这就是比单一的listView/GridView强大的地方

  • ListView

以垂直列表方式展示显示项目

  mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
  • GrideView

以垂直列表方式展示显示表格布局

  mRecyclerView.setLayoutManager(new GridLayoutManager(this, 5));
  • 横向 ListView

如果想设置一个横向的列表,只要设置 LinearLayoutManager 就行,注意, 要声明 LayoutManager 的类型是 LinearLayoutManager ,而不是父类的 LayoutManager

 mRecyclerView.setLayoutManager(new LinearLayoutManager(this, RecyclerView.HORIZONTAL, false));
  • 横向 GrideView

以水平列表方式展示显示表格布局

mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(5, StaggeredGridLayoutManager.HORIZONTAL))```

  • 瀑布流

一.高度绘制

 for (int i = 0; i < mDatas.size(); i++) {
            mHeight.add((int) (100 + Math.random() * 300));
        }

二.布局绘制

   @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {

        final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
        layoutParams.height = mHeight.get(position);
        holder.itemView.setLayoutParams(layoutParams);
        holder.tv.setText(mDatas.get(position));
     }

三.设置RecyclerView的布局管理

  StaggeredGridLayoutManager manager = new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);
  mRecyclerView.setLayoutManager(manager);

四.RecyclerView添加点击事件

  当使用一段时间的RecycleView,发现为其实现每一项点击事件并没有ListView那么轻松,ListView直接加一个 OnItemClickListerner 即可,实际上我们不要把
RecyclerView 当作 ListView使用,希望大家把他看成一个容器,里面包含不同的item,它们可以设置不同方式的排列组合,非常灵活,点击事件按照自己的意愿进行实现.那么如何为RecycleView添加点击事件呢?
  其实我发现,Adapter是添加点击事件一个很好的地方,里面构造布局等View的主要场所,也是布局和数据进行绑定的地方,首先在 Adapter 中创建一个实现点击接口,其中View是点击 item,data是数据,postion是条目位置,因为我们想知道点击的区域部分的数据和位置是什么,以便下一步进行操作:

    public interface OnItemClickListener {

       void onItemClick(View view, int position);

       void onItemLongClick(View view, int position);
   }

  定义完接口,在 Adapter 中添加 要实现的接口和添加设置的方法

 public OnItemClickListener mListener;
 public void setOnItemClickListener(OnItemClickListener listener) {
       this.mListener = listener;

   }

  那么这个接口用在什么地方呢?如下代码,为Adapter 实现 onClickListerner 方法:

    @Override
  public void onBindViewHolder(@NonNull final MyViewHolder holder, final int position) {
      holder.tv.setText(mDatas.get(position));
      setUpItemEvent(holder);
  }

  public void setUpItemEvent(@NonNull final MyViewHolder holder) {
      if (mListener != null) {
          final int layoutPosition = holder.getLayoutPosition();
          holder.tv.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View view) {

                  mListener.onItemClick(holder.tv, layoutPosition);
              }
          });

          holder.tv.setOnLongClickListener(new View.OnLongClickListener() {
              @Override
              public boolean onLongClick(View view) {
                  mListener.onItemLongClick(holder.tv, layoutPosition);
                  return false;
              }
          });
      }
  }

做完这些事情就可以在 Activity 或者其他地方 为 RecycleView 添加项目点击事件了,如:

 private void initView() {
      mRecyclerView = (RecyclerView) findViewById(R.id.recyclerview);
      mAmAdapter = new SimpleAdapter(this, mDatas, true);
      // 设置RecyclerView的布局管理
      mRecyclerView.setAdapter(mAmAdapter);
      mRecyclerView.setLayoutManager(new LinearLayoutManager(this, RecyclerView.HORIZONTAL, false));
      mAmAdapter.setOnItemClickListener(this);

  }

  @Override
  public void onItemClick(View view, int position) {
      Toast.makeText(MainActivity.this, "Click:" + position
              , Toast.LENGTH_LONG).show();
  }

  @Override
  public void onItemLongClick(View view, int position) {
      mAmAdapter.deleteData(position);
  }

五.RecycleView添加删除数据
以前在ListView中,只要 修改数据后用 Adapter 的 notifyDatasetChange 一下就可以更新界面.然而,在RecycleView 中还有高级的用法,可以让添加或者删除条目自带动画,可以在SimpleAdapter 传创建 addItem 和 removeItem 方法.

添加数据:

    public void addData(int position) {

       if (position >= 1) {
           mDatas.add(mDatas.get(position));
           notifyItemInserted(position);
       }

   }

删除数据:

       public void deleteData(int postion) {
       mDatas.remove(postion);
       notifyItemRemoved(postion);
   }

注意,这里更新数据集不是 adapter.notifyDataSetChange(),而是 notifyItemInserted(postion)与 notifyItemRemoved(postion),否则没有动画效果

RecyclerView核心类
  • Adapter
      使用RecyleView之前需要继承 自 RecyleView.Adapter 的适配器,作用是将适配器与每一个 item 界面进行绑定

  • ViewHolder

    • viewHolder 和 item view 是什么关系?一对一?一对多?还是多对一?
  • view Holder 解决的是什么问题?

  • view Holder 的 Listview item view 复用有什么关系?

  没有实现 view Holder 的 getView() 会重复执行 findViewById,findViewById 底层实现是一个 dns ,深度优先复杂度,所以viewHolder模式实现了getView方法,他的来历是用来保存View的convertView

不实现 view Holder 还会复用 item view 吗?这个问题我还有待研究.

  • LayoutManager
      LayoutManager 用来确定 每一个 item 如何排列摆放,何时展示和隐藏.回收或重用一个View时,LayoutManager会向适配器请求的数据替换旧的数据,这种机制避免创建过多的View和频繁调用findViewById的方法

  • ItemDecoration

  • ItemAnimation

RecyclerView与ListView相比的优势

  因为ListView只有纵向列表一种布局,不像RecyleView一样支持 Linear, Grild ,Stagged,Stagged ,Grid 各种可扩展布局,没有支持动画的API,但是RecyleView可以通过ItemAnimation自定义你想要的动画,相关监听接口如:setOnClickListerner(),setOnLongItenListerner(),setSelection()的设计和系统也不一致,并且没有强制实现ViewHolder,RecyleView做到了这一点,降低了耦合,另外ListView性能也不如RecyclerView,所以强烈推荐大家使用RecyclerView

@liu1813565583
Copy link
Contributor

RecyclerView 滑动场景下的回收复用涉及到的结构体两个:
mCachedViews 和 RecyclerViewPool

mCachedViews 优先级高于 RecyclerViewPool,回收时,最新的 ViewHolder 都是往 mCachedViews 里放,如果它满了,那就移出一个扔到 ViewPool 里好空出位置来缓存最新的 ViewHolder。

复用时,也是先到 mCachedViews 里找 ViewHolder,但需要各种匹配条件,概括一下就是只有原来位置的卡位可以复用存在 mCachedViews 里的 ViewHolder,如果 mCachedViews 里没有,那么才去 ViewPool 里找。

在 ViewPool 里的 ViewHolder 都是跟全新的 ViewHolder 一样,只要 type 一样,有找到,就可以拿出来复用,重新绑定下数据即可。

@MicroKibaco
Copy link
Owner

MicroKibaco commented Aug 7, 2019

RecyclerView性能优化策阅

本内容参考 今日头条 Blankj 阿里巴巴面经

  1. 数据优化

  • 服务端获取数据分页数据的时候进行缓存,提升二次加载的速度

  • 对于新增或删除的数据 使用 DiffUtis 来进行局部刷新,而不是全局刷新

  • 尽量多使用notifyInsertChangeSet() 而不是 notifyChangeSet()

  1. 布局优化

  • 减少过渡绘制

    • 避免使用ConstrainLayout ,可以使用ReativeLayout 减少嵌套层级,用Layout Inspector 查看 View 层级

    • 减少布局层级

  • 减少 xml 文件 inflate 时间

    • xml Inflate 出 itemView是通过耗时 I/O 操作的,尤其是 type 复用率 低的情况下,这种inflate 方式的损耗极大,所以我们不要考虑用inflate了,可以考虑用代码绘制,类似于 new inflate()
  1. 其他

  • item 高度固定,设置RecyclerView.setHasFixedSize(true),避免requeLayout 资源浪费

  • 设置 RecyclerView.addOnScrollListener(listener); 来对滑动过程中停止加载操作

  • 如果不要求动画,可用通过 ((SimpleItemAnimator) rv.getItemAnimator()).setSupportsChangeAnimations(false); 把默认动画关闭来提高性能

  • TextView 使用 String.toUpperCase 替换 android:textAllCaps="true"

  • TextView 使用 StaticLayout 或者 DynamicLayout 来自定义view替换它

  • 重新 RecyclerView.onViewRecycled(holder) 回收资源

  • RecycleView.setItemViewCacheSize(size) 加大 RecyclerView 缓存空间,利用空间换时间策阅来提高流程性

  • 如果多个 RecyclerView 的 adapter 是一样的,比如嵌套的 RecyclerView 存在一样的adapter,可以通过 RecyclerView.setRecycledViewPool(pool); 共有一个 RecycledViewPool

  • 对于 ItemView 设置监听器,不要对每一个 item 都调用 addXxListener,应该共有一个 XxListener 根据 id 来进行不同操作,优化了对象的频繁创建带来的资源消耗。

  • 通过 getExtraLayoutSpace 来增加 RecyclerView 预留的额外空间(显示范围内,应该额外缓存的空间) ,如下所示:

   new LinearLayoutManager(this) {
    @Override
    protected int getExtraLayoutSpace(RecyclerView.State state) {
        return size;
    }
};

@MicroKibaco
Copy link
Owner

RecyclerView缓存机制

暂时没搞明白,欢迎一块儿探讨~

@liyilin-jack
Copy link

recyclerView算是Android中比较常用的控件了,平常的使用也很简单,没什么说的。
另外,recyclerView也经常会有嵌套使用和多条目布局的使用方法,嵌套使用时要注意处理是否有滑动冲突,多条目布局就是定义多个ViewHolder来区分不同的条目。

另外使用itemDecoration可以灵活控制布局之间的间距和装饰等效果。

缓存机制的话确实还没读过源码,找了篇技术文章,可以参考下:https://blog.csdn.net/wzy_1988/article/details/81569156

@chengying1216
Copy link

RecyclerView vs ListView
ListView相比RecyclerView,有一些优点:

1.addHeaderView(), addFooterView()添加头视图和尾视图。
2.通过”android:divider”设置自定义分割线。
3.setOnItemClickListener()和setOnItemLongClickListener()设置点击事件和长按事件。

这些功能在RecyclerView中都没有直接的接口,要自己实现(虽然实现起来很简单),因此如果只是实现简单的显示功能,ListView无疑更简单。

RecyclerView相比ListView,有一些明显的优点:

1.默认已经实现了View的复用,不需要类似if(convertView == null)的实现,而且回收机制更加完善。
2.默认支持局部刷新。
3.容易实现添加item、删除item的动画效果。
4.容易实现拖拽、侧滑删除等功能。

RecyclerView是一个插件式的实现,对各个功能进行解耦,从而扩展性比较好。

找到一篇基本可以全面了解recyclerview的文章,可以参考下
https://www.jianshu.com/p/4f9591291365

@MicroKibaco
Copy link
Owner

RecyclerView vs ListView
ListView相比RecyclerView,有一些优点:

1.addHeaderView(), addFooterView()添加头视图和尾视图。
2.通过”android:divider”设置自定义分割线。
3.setOnItemClickListener()和setOnItemLongClickListener()设置点击事件和长按事件。

这些功能在RecyclerView中都没有直接的接口,要自己实现(虽然实现起来很简单),因此如果只是实现简单的显示功能,ListView无疑更简单。

RecyclerView相比ListView,有一些明显的优点:

1.默认已经实现了View的复用,不需要类似if(convertView == null)的实现,而且回收机制更加完善。
2.默认支持局部刷新。
3.容易实现添加item、删除item的动画效果。
4.容易实现拖拽、侧滑删除等功能。

RecyclerView是一个插件式的实现,对各个功能进行解耦,从而扩展性比较好。

找到一篇基本可以全面了解recyclerview的文章,可以参考下
https://www.jianshu.com/p/4f9591291365

那你是如何解决下划线渲染两次的呢?

@skylarliuu
Copy link

RecyclerView是Android 5.0推出的一个可以展示大量数据的、灵活的控件。

优势

  • 支持线性,网格和瀑布流三种布局
  • 强制实现ViewHolder
  • 提供友好的ItemAnimator动画
  • 解耦的架构设计
  • 比ListView有更好的性能

重要组件

  • LayoutManager:定位和布局Item
  • ItemAnimator:Item动画
  • Adapter:创建View并绑定数据

性能优化

  • 使用LinearLayoutManager.setInitialPrefetchItemCount(count)设置嵌套在内部的横向RecyclerView的初次显示Item个数
  • Item高度固定时(数据变化也不会导致Item高度变化),使用RecyclerView.setHasFixedSize(true)
  • 多个RecyclerView共用一个RecyclerViewPool
  • DiffUtil局部刷新

RecyclerView缓存

  • 第一层Scrap & 第二层Cache:缓存当前屏幕的ItemView。都是根据position去缓存和获取复用ItemView,复用时不需要再调用onBindViewHolder()重新进行数据绑定了。
  • 第三层ViewCacheExtension:开发者自定义的缓存策略
  • 第四层缓存池:存放已经滑出屏幕的View,需要复用时根据item type去查找是否有可复用的ItemView,如果有可复用的就拿出来再重新进行数据绑定,没有就调用onCreateViewHolder()再新建一个ItemView。

@ArtarisCN
Copy link

RecyclerView 核心要点

RecycleView 是什么

A flexible view for providing a limited window into a large data set
有限的屏幕显示大量的数据的灵活的视图

ListView 的局限

  • 只支持纵向列表的滑动
  • 没有支持动画的 API
  • 接口设计和系统设计不一致
    • setOnItemClickListener()
    • setOnItemLongClickListener()
    • setSelection()
  • 没有强制实现 ViewHolder
    • 没有实现ViewHolder 的 getView();
      每次都实现findViewById() DFS 浪费时间,消耗性能a/需要实现 ViewHolder 并且 ItemView.setTag(ViewHolder)
  • 性能不如 RecycleView

RecycleView 的优势

  • 默认支持 Linear,Grid,Staggered Grid 三种布局
  • 友好的 ItemAnimator 动画 API
  • 强制实现 ViewHolder
  • 解耦的架构设计
    • RecycleView
      • LayoutManager
      • ItemAnimator
      • Adapter
  • 相比 ListView 更好的性能

RecycleView Demo

ViewHolder 究竟是什么

  • ViewHolder 和 item view 是什么关系?一对一?一对多
    • 一一对应
  • 不使用 ViewHolder 还会实现复用吗
    • 是复用的,这是无关的,会复用 convertView,只是 findViewById 消耗性能

RecyclerView 缓存机制

ListView 缓存机制

两层缓存机制
Active View:指屏幕能看到的 ItemView
Android 刷新屏幕的时候会将屏幕中的 View 清空掉,再加进来,这时回收的使用的就是Active View,这时不需要重新绑定
Scrap View:屏幕中看不到的 ItemView

RecycleView 缓存原理

RecycleView 缓存的是 ViewHolder ,使用了四层缓存机制

  • Scrap:屏幕内部的 ItemView,通过数据集的 Position 找到的,可以直接复用不需要绑定
  • Cache:移出屏幕的 ItemView 放入一个CacheView(默认个数为2),就是比屏幕多两个 ItemView, 方便来回翻少量的View,可以直接复用不需要绑定
  • ViewCacheExtension:用户自定义的缓存机制

**ViewCacheExtension Example **
广告夹杂在 RecycleView 内,并且不会发生变化,广告和内容分开请求

  • 广告卡片
    • 每页一共有四个广告
    • 短期内不会发生变化
  • Cache 只关心 position,不关心 view type
  • RecycleViewPool 只关心 view type,每次都要重新绑定
  • 解决方法:在 ViewCacheExtension 保存四个广告的 Card
  • RecycleViewPool:被废弃的 ItemView ,内部的 data 都是 dirty 的。通过ViewType来重新Bind数据的

注意广告的 impression

  • ListView 通过 getView() 统计?准确!用户看到item的时候一定会通过 getView() 「即使发生了复用」
  • RecycleView 通过 onBindView() 统计?错误!发生复用的时候不通过 onBindView() Scrap,Cache 不回发生重新绑定!
  • RecycleView 通过 onViewAttachedToWindow() 统计!这时每当 ItemView 出现在用户视野里的时候都会回调。

可能不知道的 RecyclerView 性能优化策略

  • 在 onBindViewHolder() 设置点击监听?
    在 onBindViewHolder() 里设置点击监听器会导致重复创建点击监听器,会造成内存抖动
    改善:
    在 onCreateViewHolder()/ViewHolder Constructor 里设置点击监听,ItemView - ViewHolder - View.OnClickListener 三者一一对应
    也可以只设置一个 ViewOnClickListener ,坏处是处理逻辑都在一起,还会造成数据耦合严重(要取出点击View的对象)
  • LinearLayoutManager.setInitialPrefetchItemCount()
    • 内部嵌套中的 RecycleView ,用户滑动到横向滑动的item RecycleView 的时候,由于需要创建更复杂的 RecycleView 及多个子 View,可能会导致页面卡顿
    • 由于 RenderThread(分担主线程的渲染压力,放到这里来执行) 的存在,RecycleView 会进行 prefetch (API 21⬆️)
    • LinearLayoutManager.setInitialPrefetchItemCount(横向列表初始显示时可见的 item 个数)
      • 只有 LinearLayoutManager 有这个 API
      • 只有在嵌套在内部的RecycleView 才会生效

  • RecycleView.setHasFixedSize(true)

    伪代码:

    void onContentsChanged(){
        if(mHasFixedSize){
            layoutChildren();
        } else {
            requestLayout();
        }
    ]
    

    如果更改数据不会造成每个 ItemView 的不会影响RecyclerView的宽高的时候可以设置这个代码,避免Item的重新绘制

  • 多个 RecyclerView 共用一个 RecycleViewPool
    默认是每一个 RecyclerView 用自己的 RecycleViewPool,可以通过 API 设置共享缓存池

    RecyclerView.RecycledViewPool recycledViewPool = new RecyclerView.RecycledViewPool();
            mRecyclerView.setRecycledViewPool(recycledViewPool);
            mRecyclerView1.setRecycledViewPool(recycledViewPool);
            mRecyclerView2.setRecycledViewPool(recycledViewPool);
    
  • getViewType

  • DiffUtil

    • 局部更新方法 notifyItemXXX() 不适用于所有情况
    • notifyDataSetChange() 会导致整个布局重回,重新绑定所有 ViewHolder,而且会可能失去可能的动画效果
    • DiffUtil 适用于整个页面需要刷新,但是有部分数据可能相同的情况
    • 内部算法使用的动画规划算法
     private DiffUtil.Callback mDiffUtilCallBack = new DiffUtil.Callback() {
        //旧列表的长度
        @Override public int getOldListSize() {return 0;}
        //新列表的长度
        @Override public int getNewListSize() {return 0;}
        //列表项的ID是否变化了
        @Override public boolean areItemsTheSame(int i, int i1) {return false;}
        //列表项的内容是否变化了
        @Override public boolean areContentsTheSame(int i, int i1) {return false;}
        //列表项的哪些内容变化了
        @Nullable @Override public Object getChangePayload(int oldItemPosition, int newItemPosition) {
            return super.getChangePayload(oldItemPosition, newItemPosition);
        }
    };
    

    • 在数据量很大的时候异步计算diff
      • 使用 Thread/Handler 将 DiffResult 发送到主线程
      • 使用RxJava 将 calculateDiff 操作放到后台线程执行
      • 使用 Google 提供 AsyncListDiff(Executor)/ListAdapter

为什么 ItemDecoration 可以绘制分割线

还能做什么?

  • 划分割线
  • 高亮
  • 从视觉上对视图分组 Section

@zhengjunke
Copy link

RecyclerView内部缓存机制是四级缓存:Scrap、Cache、ViewCacheExtension、RecycledViewPool,RecyclerView是通过LayoutManager里面的Recycler来管理缓存;
1、Scrap:屏幕内部的ItemView,通过数据集的position来找到对应的Item,可以直接取过来用;
2、Cache:刚移出屏幕的ItemView,放到Cache里,当Cache里的ItemView重新进入屏幕时,也是通
过position来找到对应的Item,直接可以使用,不需要走bindViewHolder()。Cache和 Scrap 一样,都是可以直接通过position来找到对应的Item,不需要重新绑定;
3、ViewCacheExtension:自定义缓存,如果有自定义,需要在这里面找,没有的话直接跳过;
4、RecycledViewPool:所有被废弃的ItemView的Pool,该pool里面的Item都是dirty的,需要通过
ViewType来找到数据,找到数据的话,需要重新绑定,不走createViewHodler(),走bindViewHolder()。

@FrozenFreeFall
Copy link

FrozenFreeFall commented Mar 4, 2020

据我观察,公用一个 RecycledViewPool,不能达到完美的优化效果,只是节省了内存.
`
RecyclerView.RecycledViewPool recycledViewPool = new RecyclerView.RecycledViewPool();

mRecycledViewPool.setMaxRecycledViews(1, 10);
mRecycledViewPool.setMaxRecycledViews(2, 10);
mRecycledViewPool.setMaxRecycledViews(3, 10);
mRecycledViewPool.setMaxRecycledViews(4, 10);
mRecycledViewPool.setMaxRecycledViews(5, 10);

mRecyclerView1.setRecycledViewPool(recycledViewPool);
mRecyclerView2.setRecycledViewPool(recycledViewPool);
mRecyclerView3.setRecycledViewPool(recycledViewPool);
`

比如 mRecyclerView1 type=1,保存了 5个.
在 mRecyclerView2 传入 recycledViewPool,并没有 mRecyclerView1 type=1的5个缓存.
所以,当 mRecyclerView2 第一次 type=1 出来的时候,还是会执行 onCreateViewHolder,onBindViewHolder... ... ????

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants