RecyclerView性能优化实战

作者: 奔跑吧李博 | 来源:发表于2023-01-07 19:02 被阅读0次

在Android中RecyclerView的使用随处可见,它的性能优化程度跟用户体验息息相关。

性能优化实战的例子如下,是获取手机所有已安装app列表:


RecyclerView的一些优化方案和使用技巧:

  • recyclerView.setHasFixedSize(true)

当Item的高度如是固定的,设置这个属性为true可以提高性能,尤其是当RecyclerView有条目插入、删除时性能提升更明显。RecyclerView在条目数量改变,会重新测量、布局各个item,如果设置了setHasFixedSize(true),由于item的宽高都是固定的,adapter的内容改变时,RecyclerView不会整个布局都重绘。

 void onItemsInsertedOrRemoved() {
   if (hasFixedSize) layoutChildren();
   else requestLayout();
}
  • 使用getExtraLayoutSpace为LayoutManager设置更多的预留空间

在RecyclerView的元素比较高,一屏只能显示一个元素的时候,第一次滑动到第二个元素会卡顿。

RecyclerView (以及其他基于adapter的view,比如ListView、GridView等)使用了缓存机制重用子 view(即系统只将屏幕可见范围之内的元素保存在内存中,在滚动的时候不断的重用这些内存中已经存在的view,而不是新建view)。

这个机制会导致一个问题,启动应用之后,在屏幕可见范围内,如果只有一张卡片可见,当滚动的时 候,RecyclerView找不到可以重用的view了,它将创建一个新的,因此在滑动到第二个feed的时候就会有一定的延时,但是第二个feed之 后的滚动是流畅的,因为这个时候RecyclerView已经有能重用的view了。

        val linearLayoutManager: LinearLayoutManager = object : LinearLayoutManager(applicationContext, LinearLayoutManager.VERTICAL, false) {
            override fun getExtraLayoutSpace(state: RecyclerView.State): Int {
                return 300
            }
        }
        recyclerView.layoutManager = linearLayoutManager
  • 避免创建过多监听器

onCreateViewHolder 和 onBindViewHolder 对时间都比较敏感,尽量避免繁琐的操作和循环创建对象。例如创建 OnClickListener,可以全局创建一个。同时onBindViewHolder调用次数会多于onCreateViewHolder的次数,如从RecyclerViewPool缓存池中取到的View都需要重新bindView,所以我们可以把监听放到CreateView中进行。

优化前:
注意,反复滑动列表,会一直调用onBindViewHolder方法,所以这里会一直创建OnClickListener对象。

    override fun onBindViewHolder(holder: AppViewHolder, position: Int) {
        holder.ivIcon?.background = context.packageManager.getActivityIcon(datas[position].intent)
        holder.tvName?.text = datas[position].name
        holder.tvPkg?.text = "包名:" + datas[position].pkg

        holder.itemView.setOnClickListener(object: OnClickListener {
            override fun onClick(v: View?) {
                context.startActivity(datas[position].intent)
            }
        })
    }

优化后:

    class AppViewHolder(itemView: View): ViewHolder(itemView) {
        var ivIcon: ImageView?= null
        var tvName: TextView?= null
        var tvPkg: TextView?= null

        init {
            ivIcon = itemView.findViewById(R.id.iv_icon)
            tvName = itemView.findViewById(R.id.tv_name)
            tvPkg = itemView.findViewById(R.id.tv_pkg)
            itemView.setOnClickListener(onClickListener)
        }

        var onClickListener: OnClickListener = object: OnClickListener {
            override fun onClick(v: View?) {
                
            }
        }
    }
数据处理与视图绑定分离

RecyclerView的 bindViewHolder方法是在UI线程进行的,如果在该方法进行耗时操作,将会影响滑动的流畅性。

局部刷新

可以用一下一些方法,替代notifyDataSetChanged,达到局部刷新的目的。notifyDataSetChanged会触发所有item的detached回调再触发onAttached回调。

notifyItemChanged(int position)
notifyItemInserted(int position)
notifyItemRemoved(int position)
notifyItemMoved(int fromPosition, int toPosition) 
notifyItemRangeChanged(int positionStart, int itemCount)
notifyItemRangeInserted(int positionStart, int itemCount) 
notifyItemRangeRemoved(int positionStart, int itemCount) 
复用RecycledViewPool

在TabLayout+ViewPager+RecyclerView的场景中,当多个RecyclerView有相同的item布局结构时,多个RecyclerView共用一个RecycledViewPool可以避免创建ViewHolder的开销,避免GC。RecycledViewPool对象可通过RecyclerView对象获取,也可以自己实现。
如果LayoutManager是LinearLayoutManager或其子类,需要手动开启这个特性: layout.setRecycleChildrenOnDetach(true)

        val recycledViewPool = recyclerView.recycledViewPool
        recyclerView1.setRecycledViewPool(recycledViewPool)
        recyclerView2.setRecycledViewPool(recycledViewPool)
用SortedList实现添加删除ItemView自动更新

我们在给RecyclerView的ArrayList<Item> data添加一个Data数据时,一般需要自己通知RecyclerView更新。Android Support Lirbrary中提供了一个SortedList工具类,它是一个有序列表,数据变动时会回调SortedList.Callback中方法。

class AppAdapter(val context: Context): RecyclerView.Adapter<AppAdapter.AppViewHolder>() {
    private var datas: SortedList<AppInfo>?= null

    init {
        datas = SortedList(AppInfo::class.java, object: SortedListAdapterCallback<AppInfo>(this) {
            override fun compare(o1: AppInfo, o2: AppInfo): Int {
                // 实现这个方法来定义Item的显示顺序
                if (o1.name.length>o2.name.length) {
                    return 1
                } else if (o1.name.length<o2.name.length) {
                    return 1
                } else {
                    return 0
                }
            }

            override fun areContentsTheSame(oldItem: AppInfo, newItem: AppInfo): Boolean {
                // 比较两个Item的内容是否一致,如不一致则会调用adapter的notifyItemChanged()
                return oldItem.pkg.equals(newItem.pkg)
            }

            override fun areItemsTheSame(item1: AppInfo, item2: AppInfo): Boolean {
                return item1.intent == item2.intent
            }
        })
    }

    fun setDatas(datas: ArrayList<AppInfo>) {
        this.datas?.addAll(datas)
        // 会通过SortedListAdapterCallback自动通知更新
    }
}

当数据发生改变时,例如删除,增加等,只需直接对mDataList进行相应操作,无需关心mAdapter内数据显示更新问题,不用再调用notifyDataChanged等函数,因为SortedListAdapterCallback内的回调函数自动完成了。

使用DiffUtil局部刷新

DiffUtil是androidx.recyclerview.widget包下的一个工具类,当你的RecyclerView需要更新数据时,将新旧数据集传给它,它就能快速告知adapter有哪些数据需要更新。就相当于如果改变了就对某个item刷新,没改变就没刷新,可以简称为局部刷新。

mAdapter.notifyDataSetChanged()有两个缺点:
1.不会触发RecyclerView的动画(删除、新增、位移、change动画)
2.性能较低,毕竟是无脑的刷新了一遍整个RecyclerView , 极端情况下:新老数据集一模一样,效率是最低的。

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

adapter.notifyItemRangeInserted(position, count);
adapter.notifyItemRangeRemoved(position, count);
adapter.notifyItemMoved(fromPosition, toPosition);
adapter.notifyItemRangeChanged(position, count, payload);

简单使用DiffUtil,我们需要且仅需要额外编写一个类。

/**
 * 核心类 用来判断 新旧Item是否相等
 */
public class DiffCallBack extends DiffUtil.Callback {
    private List<AppInfo> mOldDatas, mNewDatas;

    @Override
    public int getOldListSize() {
        return mOldDatas.size();
    }

    @Override
    public int getNewListSize() {
        return mNewDatas.size();
    }

    /**
     * 被DiffUtil调用,用来判断 两个对象是否是相同的Item。
     * @param oldItemPosition
     * @param newItemPosition
     * @return
     */
    @Override
    public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
        return mOldDatas.get(oldItemPosition).getName().equals(mNewDatas.get(newItemPosition).getName());
    }

    /**
     * 被DiffUtil调用,用来检查 两个item是否含有相同的数据
     * @param oldItemPosition
     * @param newItemPosition
     * @return
     */
    @Override
    public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
        return mOldDatas.get(oldItemPosition).getPkg().equals(mNewDatas.get(newItemPosition).getPkg());
    }
}

使用方式:

        val diffResult = DiffUtil.calculateDiff(DiffCallBack(mProducts, newProducts))
        diffResult.dispatchUpdatesTo(adapter)
优化滑动操作

设置 RecyclerView.addOnScrollListener();来在滑动过程中停止加载的操作。

参考:
https://blog.csdn.net/GracefulGuigui/article/details/103646864
https://blog.csdn.net/yaojie5519/article/details/117174114

相关文章

  • RecyclerView性能优化实战

    在Android中RecyclerView的使用随处可见,它的性能优化程度跟用户体验息息相关。 性能优化实战的例子...

  • 浅谈RecyclerView的性能优化

    RecyclerView的性能优化 在我们谈RecyclerView的性能优化之前,先让我们回顾一下Recycle...

  • linux实用命令

    摘自:性能优化实战 专栏

  • Android UI性能优化

    Ui性能优化 参考博客:Android UI性能优化实战 识别绘制中的性能问题Android UI性能优化详解 1...

  • 性能优化

    Android UI性能优化实战 识别绘制中的性能问题性能优化(二) UI 绘制优化 通过Hierarchy Vi...

  • DiffUtil和RecyclerView的联合使用

    最近公司在做性能优化的相关工作,在RecyclerView的优化方面,采用了DiffUtil来提升性能。 废话不多...

  • 复习

    RecyclerView的缓存机制bitmap 优化性能优化view 的绘制流程浅析LRUCache原理(Andr...

  • RecyclerView性能优化

    概述 RecyclerView有着极高的灵活性,能实现ListView、GridView的所有功能。在日常开发中,...

  • RecyclerView性能优化

    概述 RecyclerView有着极高的灵活性,能实现ListView、GridView的所有功能。在日常开发中,...

  • RecyclerView性能优化

    RecyclerView入门 RecyclerView缓存机制

网友评论

    本文标题:RecyclerView性能优化实战

    本文链接:https://www.haomeiwen.com/subject/wisscdtx.html