CrazyDailyQuestion icon indicating copy to clipboard operation
CrazyDailyQuestion copied to clipboard

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

Open zhjlong opened this issue 6 years ago • 11 comments

zhjlong avatar Aug 07 '19 00:08 zhjlong

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

MicroKibaco avatar Aug 07 '19 07:08 MicroKibaco

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

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

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

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

liu1813565583 avatar Aug 07 '19 08:08 liu1813565583

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 avatar Aug 07 '19 11:08 MicroKibaco

RecyclerView缓存机制

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

MicroKibaco avatar Aug 07 '19 11:08 MicroKibaco

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

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

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

liyilin-jack avatar Aug 07 '19 12:08 liyilin-jack

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

chengying1216 avatar Aug 07 '19 13:08 chengying1216

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 avatar Aug 07 '19 13:08 MicroKibaco

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。

skylarliuu avatar Aug 07 '19 14:08 skylarliuu

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

ArtarisCN avatar Aug 07 '19 15:08 ArtarisCN

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()。

zhengjunke avatar Aug 07 '19 15:08 zhengjunke

据我观察,公用一个 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... ... ????

FrozenFreeFall avatar Mar 04 '20 16:03 FrozenFreeFall