美文网首页Android进阶之路源码解析相关Android开发经验谈
【进阶】RecyclerView源码解析(四)——Recycle

【进阶】RecyclerView源码解析(四)——Recycle

作者: 被代码淹没的小伙子 | 来源:发表于2018-04-24 21:33 被阅读215次

    1.【进阶】RecyclerView源码解析(一)——绘制流程
    2.【进阶】RecyclerView源码解析(二)——缓存机制
    3.【进阶】RecyclerView源码解析(三)——深度解析缓存机制
    4.【进阶】RecyclerView源码解析(四)——RecyclerView进阶优化使用

    上一篇博客比较深度的对RecyclerView的缓存机制进行了分析,分别对SrapViews、CacheViews、RecyclerPool这三级缓存进行多角度分析和实际对Demo验证。前三篇博客可以说都是从源码对角度对RecyclerView进行分析,分别对RecyclerView的绘制机制,缓存源码,缓存机制三个角度进行深度分析,虽然都是从源码角度进行分析,比较抽象,但是对于我们理解RecyclerView的使用有很大的帮助。

    前言

    本篇博客将打算从实际开发过程中,结合前面三篇博客的分析,总结一下RecyclerView的使用过程中的进阶使用(仅仅是我统计总结的,大家如果有其他的见解,欢迎大家在在评论区分享~)。

    一.不要在onBind的时候设置onClickListener

    当为RecyclerView中的ItemView中的设置点击事件或者其他事件的时候,往往我们的写法总是在onBindViewHolder中给ItemView去设置点击事件。

    @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            holder.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    //do something
                }
            });
        }
    

    问题:这时候我们可以考虑一下我们这种写法是否合理,从前面的源码分析甚至对RecyclerView有一定基础了解的都知道onBindViewHolder的调用时机是View滑到页面可显示位置时,就会出发这个方法回调。那当我们这样设置的时候就意味着,这个View只要滑到屏幕内,这个我们就会给这个itemView设置一次onClickListener,并且这个onClickListener每次滑动的时候都是重新new出来的。显而易见这样是不合理的。好吧,那我们优化一下~

    1.1 第一次优化

    @Override
        public void onBindViewHolder(ViewHolder holder, int position) {
            holder.setOnClickListener(mOnClickListener);
        }
        
        private View.OnClickListener mOnClickListener = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //do something
            }
        }
    

    优化: 嗯,这样看起来舒服多了,这样我们每次onBindViewHolder的时候设置的onClickListener都是同一个mOnClickListener,这样我们就不用每次在onBindViewHolder都new一个onClickListener了。
    问题:
    但是这样真 就够了吗?再回想一下,RecyclerView的优势就是对于ViewHolder的复用。这样考虑一下,当position =1的第一次显示在界面显示,我们已经对view设置过onClickListener,我们这是滑出position=1,再滑回position=1。当我们向下滑这时position=1被放入缓存,如果仅仅是在CacheViews缓存中还好,因为不会调onBindViewHolder方法(具体原因见上篇博客),如果是在CacheViews或者RecyclerPool的时候,每次滑入还会调onBindViewHolder方法,也就是说,明明我们已经给这个View设置过onClickListener了,每次显示的时候,我们还要再给这个view设置一次onClickListener,这样肯定是不合理的。那就再优化一下~
    1.2 第二次优化

    private class XXXHolder extends RecyclerView.ViewHolder {
            private EditText mEt;
            EditHolder(View itemView) {
                super(itemView);
                mEt = (EditText) itemView;
                mEt.setOnClickListener(mOnClickListener);
            }
        }
        private View.OnClickListener mOnClickListener = new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //do something
            }
        }
    

    没错,就是上面这样,既然每次Bind的时候没必要重复设置onClickListener,那么我们就在onCreateViewHolder中设置,在这个ViewHolder在new的时候,设置一个全局的OnClickListener。这样刚才考虑的问题就迎刃而解了。

    二.不要在onBindViewHolder做逻辑判断和计算。

    这也是我们经常容易犯的问题,原因其实和第一条也是相似的,每次滑入后我们都必须做完这些逻辑判断和计算,页面才能绘制出来,这样明显是很消耗性能的。常见的一些逻辑判断:

    1.TextView.setText(Html.fromHtml(str);
    2.计算UI的宽高比,margin,padding,每次都用DensityUtils.dp2px()转换。
    3.每次都new一些可以复用都对象:adapter,viewparam
    等。。。

    优化建议:

    1.可以考虑尽可能都逻辑前移
    2.onBindViewHolderz中都对象考虑懒加载或者变成私有变量。

    三.RecyclerView嵌套RecyclerView考虑设置RecyclerPool缓存。

    这个是我们经常考虑不到都一点,我们经常有这样都需求,一个竖向都RecyclerView需要展示多个横向滑动都RecyclerView都楼层。这时候我们就可以考虑使用RecyclerPool给子RecyclerView设置一个缓存池,这样当存在多个横向滑动的RecyclerView时,就可以减少子RecyclerView的子ViewHolder的创建,实现多个RecyclerView之间的复用。
    代码实现:

    private RecyclerView.RecycledViewPool childPool;
    public XXAdapter(){
        childPool = new RecyclerView.RecycledViewPool();
    }
    private class RcyViewHolder extends RecyclerView.ViewHolder {
            private SRecyclerView sRcy;
    
            public RcyViewHolder(View itemView) {
                super(itemView);
                sRcy = itemView.findViewById(R.id.rcy_child);
                LinearLayoutManager manager = new LinearLayoutManager(mContext);
                //1.设置回收
                manager.setRecycleChildrenOnDetach(true);
                manager.setOrientation(LinearLayoutManager.HORIZONTAL);
                sRcy.setLayoutManager(manager);
                //2.设置缓存Pool
                sRcy.setRecycledViewPool(childPool);
            }
        }
    

    Demo比较
    Demo是上一篇博客的拓展,一个父RecyclerView中包含两种类型的楼层,第一种是一个TextView,第二种是一个横向的RecyclerView。而子RecyclerView里面就是横向的多个ImageView的列表。

    Demo地址:RecyclerViewStudy,感兴趣的可以star~

    首先我们来看一下没有设置RecyclerPool之前
    3.1 没有设置RecyclerPool

    第一个ChildRecyclerView
    可以看到,刚进入的时候,这时只有一个横向的ChildRecyclerView,从面板可以看到这时第一个ChildRecyclerView:new了三个ImageViewHolder。
    这时我们向下滑动展示出第二个横向的ChildRecyclerView。
    第二个ChildRecyclerView
    可以看到,这时第二个横向的ChildRecyclerView滑入的时候,从面板可以看到,从刚才的new了三个的ImageViewHolder又new了三个ImageViewHolder。
    3.2 设置RecyclerPool
    第一个ChildRecyclerView
    可以看到,这时候没有什么特殊的变化,由于只有一个横向的ChildRecyclerView,所以仍然只是new了三个ImageViewHolder。
    第二个ChildRecyclerView
    这时候就可以清除的看到啊设置完RecyclerViewPool的变化了,可以发现第二个ChildRecyclerView滑入后,没有new任何新的ImageViewHolder,也就是说第二个ChildRecyclerView复用了第一个ChildRecyclerView的new出来的三个ImageViewHolder。也就是说这时内存里只存在三个ImageViewHolder。这样就节省了创建3个ImageViewHolder的时间。

    四.对于大量图片的RecyclerView考虑重写onScroll事件,滑动暂停后再加载

    这个我们平时就经常实现了,当长图片列表的时候,我们经常做这样的优化,防止图片的大量加载,毕竟图片一直是内存占用大户。

    五.对于复杂布局的RecyclerView考虑重写onScroll事件,滑动暂停后再加载复杂布局

    这个其实我们平时没有考虑,考虑一种情况:RecyclerView中存在几种绘制复杂,占用内存高的楼层类型,但是用户只是快速滑动到底部,并没有必要绘制计算这几种复杂类型,所以也可以考虑对滑动速度,滑动状态进行判断,满足条件后再加载这几种复杂的。

    六.不要什么都用notifydatasetchange!!!!

    这个其实每个人都熟知,但是往往都不遵循,RecyclerView和ListView的一个显著区别就是RecyclerView提供了多种刷新类型,不像ListView每次刷新都需要重新Bind界面内都所有都View。RecyclerView通过给每个ViewHolder设置标志位来判断需要刷新的ViewHolder。具体原理如下图:(图片来源:多次提到的Bugly博客~~)

    RecyclerView刷新机制
    6.1 Demo验证
        case R.id.delete:
                    mData.remove(0);
                    //局部刷新
                    //mAdapter.notifyItemRemoved(0);
                    //全局刷新
                    mAdapter.notifyDataSetChanged();
    

    Demo很简单,就是点击删除后,移除第一个Item。

    刚进入
    notifyDataSetChanged
    可以看到,当我们仅仅是删除了第一项或者某一项,调用了notifyDataSetChanged方法,会导致整个页面范围内的ViewHolder重新调用onBindViewHolder方法,这样就重复做了一次Bind操作。这时我们换用notifyItemRemoved方法。
    notifyItemRemoved
    可以看到,这时只会由于第一个移除,导致新的一个position=8进入并展示,所以只有position=8调用了onBindViewHodler方法,而其他的已经绑定的ViewHolder不需要重新绑定。

    七.减少每个ItemView的层级嵌套

    这就是老生常谈的优化了。

    八.升级Recycle版本到25以上的版本,使用recyclerview prefetch功能

    关于Prefethc功能本篇博客就不讲解了,这里提供两篇博客供大家理解吧:
    RecyclerView Prefetch功能探究
    RecyclerView的新机制:预取(Prefetch)

    九.设置setItemViewCacheSize缓存大小

     recyclerView.setItemViewCacheSize(20);
     recyclerView.setDrawingCacheEnabled(true);
     recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);
    

    其实setItemViewCacheSize设置的是CacheViews的大小,通过前一篇博客,我们应该知道CacheViews的特点:

    1.CacheViews中的缓存只能position相同才能复用,并且不会重新Bind.
    2.CacheViews满了后会移除到RecyclerPool中,并重置ViewHolder.
    3.RecyclerPool中的缓存复用需要重新Bind.

    所以我们可以适当的通过调用setItemViewCacheSize方法,来增加CacheViews的大小(默认是2),来防止小范围的滑动导致的重复Bind而导致的卡顿。典型的拿空间还时间,所以要考虑内存问题,根据自己的应用实际情况设置大小

    十.如果RecyclerView固定宽高,只是用于展示固定大小的组件,然后设置recyclerView.setHasFixedSize(true)这样可以避免每次绘制Item时,不再重新计算Item高度。

    总结

    刚好凑够10条也算满足了强迫症的毛病~~~,以上仅仅是我个人的总结,如果大家还有什么不错的建议欢迎大家在下方评论分享

    RecyclerView的源码系列到此算是结束了,四篇博客算是我收集的RecyclerView相关学习博客的总结和自己的分析。随着RecyclerView的使用场景越来越多,只有真正从源码角度理解了RecyclerView的绘制,缓存原理,才能进一步理解和优化我们通过RecycelrView实现的页面,真是应了那句话:Read The Fucking Source Code

    相关文章

      网友评论

      • 小肥羊9527:支持!!
      • N城渔夫:非常棒、我支持你
      • kirk王:棒棒哒,大神求带😀
      • 阳台边上吃火锅:如果OnClickListener这样子set的话 那每个item的点击事件该如何区分?
        被代码淹没的小伙子:区分可以通过getId,或者getTag加标志位区分
      • starsight:listener优化了以后就不能获取位置了……所以感觉追求优化还是重写recyclerview的touchlistener吧
        被代码淹没的小伙子:@starsight 为什么不直接给root.setTag,通过index来获取child,太容易出错了
        starsight:@被代码淹没的小伙子 试了一下,好像可以。listener中写入一下三行,依次从上层View取到ViewHolder中的setTag的View。
        LinearLayout lin = (LinearLayout) ((FlipCard)v).getChildAt(0);
        TextView text = (TextView)lin.getChildAt(0);
        int pos = (Integer)text.getTag();
        在bindHolder中holder.textView.setTag(position) 这个思路可以吗?感觉求子View那有点虚……
        被代码淹没的小伙子:@starsight 可以通过setTag获取到

      本文标题:【进阶】RecyclerView源码解析(四)——Recycle

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