使用优化 | RecyclerView中可优化的点

简介: 使用优化 | RecyclerView中可优化的点

RecyclerView中你可能不知道的一些东西


1,RecyclerView是什么


为有限的屏幕显示大量的数据且灵活的View,如下图


0a2653c851af460fa595bd959398a8f1.png


相比较 ListView


ListView:


只有纵向列表一种布局

没有支持动画的 API

接口设计和系统不一致,如 setOnItemClickListener

没有强制实现 ViewHolder

性能不如 RecyclerView

RecyclerView:


默认支持 Linear,Grid ,Staggered Grid 布局

友好的 ItemAnimator 动画 Api。在刷新的时候调用对应的刷新 api 即可看到动画

强制实现 ViewHolder

RecyclerView 的源码是非常解耦的,且性能非常好


2,RecyclerView 中重要的组件


RecyclerView:一个特殊的 ViewGroup,他本身不会做太多的工作。重要的工作都会交给下面的三个组件来完成

LayoutManager:负责布局和摆放 item

ItemAnimator:负责动画

Adapter:适配器模式,对数据进行适配,把数据列表转化成 RecyclerView 需要的 ItemViewAdapter


3,简单的使用


Demo


4,ViewHoder 究竟是什么


ViewHolder 和 item 是一一对应的关系,在创建一个item的时候就会创建一个 ViewHolder,这样当 Item 进行复用的时候就可以直接拿到 ViewHolder,从而防止重复进行 findViewById 。


所以说就算你没有使用 ViewHolder,你的 item 还是会被复用,不同的是他会重新进行 findViewById 的操作。


ViewHolder 的实践:一般情况下我们是在 onBindViewHolder 方法中绑定数据,但是如果是多个条目,那么这种写法就会非常臃肿,这种情况下就可以吧绑定数据的代码写在 ViewHolder 中。


5,RecyclerView 的缓存机制


RecyclerView 中缓存的其实是 ViewHolder。ViewHolder和 item 实际上是绑定的,所以缓存了 ViewHolder 也就相当于缓存了 item。


1,Scrap


屏幕内部的 itemView,可直接进行使用


2,Cache


被滑出的 View 会放在 Cache 中,当用户倒着滑的时候就会直接从 Cache 中获取 viewHolder,避免重复创建 ViewHolder。从Cache 中拿到的缓存可直接进行使用,无需重新创建可绑定数据。


3,ViewCacheExtension


用户自定义的Cache策略,用户如果定义了,就会去里面找缓存,如果没有则直接去下面的缓存池。


4,RecyclerViewPool


缓存池,里面保存的都是被废弃的 ItemView。如果从上面的缓存都没有找到,则就会从 RecyclerViewPoll 中查找


在 RecyclerViewPoll 中保存的数据都是脏数据,即使在 RecyclerViewPoll 中找到了,虽然不会重新创建 ViewHolder,但是会重新执行onBindView 的操作。这也是 Poll 和前面1和2中不一样的地方。


如果在上面的 4 级缓存中都没有,则会重新创建 ViewHolder。最终调用的是 onCreateViewHolder,由用户自行创建。


6,RecyclerView 中 item 广告的统计


在 ListView 中通过 getView() 方法进行统计是没有问题的。每次滑动的时候都会调用 getView() 方法。


在 RecyclerView 中 通过 onBindViewHolder() 统计?可能错误!


onBindViewHolde 这个方法不是每次都调用的,有可能你看到了item 10 多次,但是只统计了 5,6次。这种情况下数据就是错误的。


如何解决呢?


通过 onViewAttachedToWindow() 统计即可。每看到一次,这个方法就会执行一次


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


不要在 onBindViewHolder 方法中创建点击事件


在创建 ViewHolder 的时候创建 点击事件,如在 new ViewHolder() 或者在 ViewHolder 的初始化方法中创建点击事件即可。


LinearLayoutManager.setInitialPrefetchltemCount() 方法


如果是 RecyclerView 嵌套横向的 ReyclerView,当用户滑动的时候,由于需要创建更复杂的 RecyclerView 以及多个子View,可能会导致页面卡顿


由于 RenderThread 的存在,RecyclerView 会进行 prefetch(RenderThread 是一个专门用于 UI 渲染的线程,把原来很多放在主线程的操作放在了这个线程) 。这样在渲染的时候主线程就会有更多的空闲时间,那么在这个空闲的状态,recyclerView 就可以用来做 prefetch


setInitialPrefetchltemCount(横向列表初次显示时可见的 item 个数),调用这个方法后,由于 prefetch,用户在滑动的时候就不会那么卡顿了。


需要注意的:


只有 LinearLayoutManager 有这个 API

只有嵌套在内部的 RecyclerView 才会生效

RecyclerView.setHasFixedSize()


//伪代码
void onContentsChanged(){
  if(mHasFixedSize){
  layoutChildren();
  }else{
  requestLayout();
  }
}


通过上面的伪代码可以看到,如果有固定大小,则直接 layoutChildren,否则 requestLayout()。


requestLayout() 会让 RecyclerView 重新走一遍绘制流程。


所以如果 recycleView 的数据是固定的,则可以将此方法设置为 true。


多个 RecyclerView 共用 RecycledViewPoll


注意这个 RecycledViewPool 不是 四级缓存中的 RecyclerViewPool


RecyclerView 会默认给自己创建一个 RecycledViewPool


使用场景:如果是一个 tab 页面,并且有很多个子页面,他们的 item 大致都相同,那么就可以设置一个共享的 RecycledViewPoll,这样就可以提升一定的性能。通过下面代码即可设置


RecyclerView.RecycledViewPool pool = new RecyclerView.RecycledViewPool();
recycler1.setRecyclerViewPool(pool);
recycler2.setRecyclerViewPool(pool);
recycler3.setRecyclerViewPool(pool);


DiffUtil


计算两个不同列表的差异,根据计算出的差异输出一段操作,把第一个 list 变成第二个list


局部更新方法:notifyItemXXX() 不适用于所有情况


有可能你不确定你要更新的 item 是哪个了,那么只能通过 notifyDataSetChange() 进行刷新,这样会导致整个布局重绘,重新绑定所有的 ViewHolder,而且会失去可能的动画效果


DiffUtil 适用于整个页面需要刷新,但是有部分数据可能相同的情况。


DiffUtili.Callback,他是用于给系统计算 diff 的callback


/**
 *一个由DiffUtil在计算两个列表之间的差异时使用的回调类
 */
public abstract static class Callback {
    /**
     * 旧数据的大小
     */
    public abstract int getOldListSize();
    /**
     * 新数据的大小
     */
    public abstract int getNewListSize();
    /**
     * 由DiffUtil调用,以确定两个对象是否表示同一项
     * <p>
     * 例如,如果条目具有惟一的id,该方法应该检查它们的id是否相等
     *
     * @param oldItemPosition 旧数据在列表中的位置
     * @param newItemPosition 新数据在列表中的位置
     * @return 如果两项表示同一对象,则为真;如果两项不同,则为假
     */
    public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition);
    /**
     * 当需要检查两个项是否具有相同的数据时,由DiffUtil调用。DiffUtil使用此信息检测项的内容是否已更改
     * <p>
     * areItemsTheSame 返回true时才会调用此方法,例如,两个 User 的id是一样的,但是他的数据可能发生了变化,所以此方法会被调用。
     * <p>
     * @param oldItemPosition  旧数据在列表中的位置
     * @param newItemPosition 新数据在列表中的位置                   
     * return true 表示这两个列表的数据相同,false 表示数据发生了更改
     */
    public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition);
    /**
     * 当上面 areItemsTheSame 返回true,areContentsTheSame 返回false时调用
     * <p>
     *  更新数据,如果不实现此方法,则永远也不会有 item 内部的增量更新了
     * <p>
     * Default implementation returns {@code null}.
     *
     * @param oldItemPosition 旧数据在列表中的位置
     * @param newItemPosition 新数据在列表中的位置           
     *
     * @return 一个有效的对象,表示两项之间的更改。
     */
    @Nullable
    public Object getChangePayload(int oldItemPosition, int newItemPosition) {
        Bundle b = new Bundle()
        if(.....){
            b.put("key",value)
        }
        return b;
    }
}


那么如何使用呢?


经过测试,发现适用的场景如下:


在刷新列表的时候,一般情况下的操作是,清空原有的数据,然后填入新的数据,最后not…


但是使用了 Diff 之后,在刷新列表的时候,只需要填入新的数据,然后调用 Diff 的方法,即可。在内部会通过算法进行计算出差异,然后保留新的数据。这里的保留指的是 ,在原来数据的基础上进行增删改查,使其最终的结果和刷新的数据一样。看一下案例即可清楚,如下:


默认的刷新


2019082413041482.gif


使用 Diff 之后


2019082413041482.gif


通过上面的图可以看到,使用 Diff 之后可以看到明显的动画痕迹。使用 Diff 后,会将新数据中和原有数据相同的 item 进行保留,不相同的全部 remove (这里指的是旧数据列表的数据),最后再将新数据中的数据添加进来。这样就可以避免直接调用 notifyDataSetChanged() 而产生的性能消耗和列表的卡顿。


甚至,可以看到那些数据是重复的:


2019082413041482.gif


下面就看一下具体的实现过程


使用 diff ,计算差异


class RvDiffItemCallback(val old: List<String>, val new: List<String>) : DiffUtil.Callback() {
    override fun getOldListSize(): Int {
        return old.size
    }
    override fun getNewListSize(): Int {
        return new.size
    }
  //判断id是否相同,由于是 string,没有id,所以就直接比较了
    override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return old[oldItemPosition] == new[newItemPosition]
    }
  //判断内容是否相同
    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return old[oldItemPosition] == new[newItemPosition]
    }
}


class Adapter() : RecyclerView.Adapter<Adapter.Holder>() {
        var data = mutableListOf<String>()
        fun addNewData(list: MutableList<String>) {
            val diffResult = DiffUtil.calculateDiff(RvDiffItemCallback(data, list), false)
            data = list
                //如果不需要看到动画,则直接传入this,否则自己实现即可
//            diffResult.dispatchUpdatesTo(this)
            diffResult.dispatchUpdatesTo(object : ListUpdateCallback {
                override fun onChanged(position: Int, count: Int, payload: Any?) {
                    notifyItemRangeChanged(position, count, payload)
                }
                override fun onMoved(fromPosition: Int, toPosition: Int) {
                    notifyItemMoved(fromPosition, toPosition)
                }
                override fun onInserted(position: Int, count: Int) {
                    notifyItemRangeInserted(position, count)
                }
                override fun onRemoved(position: Int, count: Int) {
                    notifyItemRangeRemoved(position, count)
                }
            })
        }
    //................
}


使用 diff 进行增量更新


在 areContentsTheSame 方法中判断内容是否相同,如果相同,就不会去加载这个 item。如果内容不相同,则会返回 false,则可以对数据进行更新。实现 getChangePayload 方法即可


override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
      return old[oldItemPosition] != new[newItemPosition]
    }
  override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
        val payload = Bundle()
        payload.putString("key", "${old[oldItemPosition]} = ${new[newItemPosition]} 重复的值")
        return payload
  }


areContentsTheSame 返回 false 之后,下面的方法才会调用,在这个方法中创建一个有效的对象,然后返回即可。


由于在 areItemsTheSame 中比较 id 的时候直接比较的是内容。所以在比较内容的时候进行取反,对相同的内容进行增量更新(一般情况下增量更新的都是 id 相同 且 内容不同的 item 进行更新)


然后在 adapter 中修改如下:


override fun onBindViewHolder(holder: Holder, position: Int, payloads: MutableList<Any>) {
      if (payloads.isEmpty()) {
          onBindViewHolder(holder, position)
      } else {
          //增量更新
          val pay = payloads[0] as Bundle
          val value = pay.get("key") as String
          holder.tvText.text = value
      }
  }


注意 onBindViewHolder 是三个参数的方法,如没有增量,则调用原有的 onBindViewHolder。否则使用增量的数据。


最终的效果就是,上面的最后一张图;


这里只是演示一下增量的用法,具体的判断应该自行实现,上述代码只是写起来简单,就是为了一下效果。


8, ItemDecoration 绘制风分割线


简单的看一下源码


public abstract static class ItemDecoration {
    /**
     * 在 itemView 之前绘制,会出现在 item 的下面
     */
    public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull State state) {
        onDraw(c, parent);
    }
    /**
     * 在 itemView 的上面绘制,覆盖在上面
     * @param c Canvas to draw into
     * @param parent RecyclerView this ItemDecoration is drawing into
     * @param state The current state of RecyclerView.
     */
    public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent,
            @NonNull State state) {
        onDrawOver(c, parent);
    }
    /**
     * Item 的偏移
     */
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
            @NonNull RecyclerView parent, @NonNull State state) {
        getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),
                parent);
    }
}


结合 DividerItemDecoration 的源码即可清楚。

相关文章
|
23天前
|
缓存 监控 Android开发
Android中的RecyclerView优化策略与实践
【4月更文挑战第5天】本文深入探讨了在安卓开发中,如何针对RecyclerView进行性能优化。通过分析常见的滚动卡顿、内存泄漏等问题,提出了相应的解决方案,并结合实际案例展示了优化过程。文章不仅涵盖了使用RecyclerView时应当遵循的最佳实践,还提供了高级技巧以供进阶开发者参考,旨在帮助读者构建更加流畅和高效的列表显示。
|
7月前
|
缓存 Java 测试技术
RecyclerView 优化—滑动性能提升
RecyclerView 优化—滑动性能提升
122 0
|
10月前
|
缓存 Android开发
ListView的优化
ListView的优化
|
11月前
|
XML 存储 缓存
RecyclerView 性能优化 | 把加载表项耗时减半 (二)
RecyclerView 性能优化 | 把加载表项耗时减半 (二)
170 0
|
11月前
|
XML 存储 缓存
RecyclerView 性能优化 | 把加载表项耗时减半 (一)
RecyclerView 性能优化 | 把加载表项耗时减半 (一)
10919 0
|
11月前
|
XML 前端开发 数据格式
RecyclerView 性能优化 | 把加载表项耗时减半 (三)
RecyclerView 性能优化 | 把加载表项耗时减半 (三)
97 0
|
11月前
|
XML 存储 缓存
RecyclerView 性能优化 | 把加载表项耗时减半 (三)(下)
RecyclerView 性能优化 | 把加载表项耗时减半 (三)
109 0
|
11月前
|
存储 缓存 Java
RecyclerView 性能优化 | 是什么在破坏缓存机制?
RecyclerView 性能优化 | 是什么在破坏缓存机制?
141 0
|
缓存 算法
深入讲解RecyclerView布局动画原理(一)
深入讲解RecyclerView布局动画原理(一)
深入讲解RecyclerView布局动画原理(一)
深入讲解RecyclerView布局动画原理(二)
深入讲解RecyclerView布局动画原理(二)
深入讲解RecyclerView布局动画原理(二)