Android之RecyclerView性能优化

概述

RecyclerView有着极高的灵活性,能实现ListView、GridView的所有功能。在日常开发中,使用非常广泛,如果使用不当将会影响到应用的整体性能,所以有必要了解一下如何更高效的使用。

数据处理与视图绑定分离

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

优化前:

class Task {
    Date dateDue;
    String title;
    String description;

    // getters and setters here
}

class MyRecyclerView.Adapter extends RecyclerView.Adapter {

    static final TODAYS_DATE = new Date();
    static final DATE_FORMAT = new SimpleDateFormat("MM dd, yyyy");

    public onBindViewHolder(Task.ViewHolder tvh, int position) {
        Task task = getItem(position);

        if (TODAYS_DATE.compareTo(task.dateDue) > 0) {
            tvh.backgroundView.setColor(Color.GREEN);
        } else {
            tvh.backgroundView.setColor(Color.RED);
        }

        String dueDateFormatted = DATE_FORMAT.format(task.getDateDue());
        tvh.dateTextView.setDate(dueDateFormatted);
    }
}

上面的onBindViewHolder方法中进行了日期的比较和日期的格式化,这个是很耗时的,在onBindViewHolder方法中,应该只是将数据set到视图中,而不应进行业务的处理。

优化后:

public class TaskViewModel {
    int overdueColor;
    String dateDue;
}

public onBindViewHolder(Task.ViewHolder tvh, int position) {
    TaskViewModel taskViewModel = getItem(position);
    tvh.backgroundView.setColor(taskViewModel.getOverdueColor());
    tvh.dateTextView.setDate(taskViewModel.getDateDue());
}

数据优化

  1. 分页加载远端数据,对拉取的远端数据进行缓存,提高二次加载速度;
  2. 对于新增或删除数据通过DiffUtil,来进行局部数据刷新,而不是一味的全局刷新数据。

DiffUtil是support包下新增的一个工具类,用来判断新数据和旧数据的差别,从而进行局部刷新。

DiffUtil的使用,在原来调用mAdapter.notifyDataSetChanged()的地方:

// mAdapter.notifyDataSetChanged()
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(oldDatas, newDatas), true);
diffResult.dispatchUpdatesTo(mAdapter);

DiffUtil最终是调用Adapter的下面几个方法来进行局部刷新:

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

布局优化

减少过度绘制

减少布局层级,可以考虑使用自定义View来减少层级,或者更合理的设置布局来减少层级。

Note: 目前不推荐在RecyclerView中使用ConstraintLayout,在ConstraintLayout1.1.2版中,性能还是表现不佳,后续的版本可能这个问题就解决了,需要持续关注。

减少xml文件inflate时间

xml文件包括:layout、drawable的xml,xml文件inflate出ItemView是通过耗时的IO操作。可以使用代码去生成布局,即new View()的方式。这种方式是比较麻烦,但是在布局太过复杂,或对性能要求比较高的时候可以使用。

减少View对象的创建

一个稍微复杂的 Item 会包含大量的 View,而大量的 View 的创建也会消耗大量时间,所以要尽可能简化 ItemView;设计 ItemType 时,对多 ViewType 能够共用的部分尽量设计成自定义 View,减少 View 的构造和嵌套。

设置高度固定

如果item高度是固定的话,可以使用RecyclerView.setHasFixedSize(true);来避免requestLayout浪费资源。

共用RecycledViewPool

在嵌套RecyclerView中,如果子RecyclerView具有相同的adapter,那么可以设置RecyclerView.setRecycledViewPool(pool)来共用一个RecycledViewPool。

Note: 如果LayoutManager是LinearLayoutManager或其子类,需要手动开启这个特性:layout.setRecycleChildrenOnDetach(true)

class OuterAdapter extends RecyclerView.Adapter<OuterAdapter.ViewHolder> {
    RecyclerView.RecycledViewPool mSharedPool = new RecyclerView.RecycledViewPool();

...

    @Override
    public OuterAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        RecyclerView innerLLM = new RecyclerView(inflater.getContext());

        LinearLayoutManager innerLLM = new LinearLayoutManager(parent.getContext(), LinearLayoutManager.HORIZONTAL);
        innerLLM.setRecycleChildrenOnDetach(true);
        innerRv.setLayoutManager(innerLLM);
        innerRv.setRecycledViewPool(mSharedPool);
        return new OuterAdapter.ViewHolder(innerRv);
    }

 

RecyclerView数据预取

RecyclerView25.1.0及以上版本增加了Prefetch功能。

用于嵌套RecyclerView获取最佳性能。

详细分析:RecyclerView 数据预取

Note: 只适合横向嵌套

// 在嵌套内部的LayoutManager中调用LinearLayoutManger的设置方法
// num的取值:如果列表刚刚展示4个半item,则设置为5
innerLLM.setInitialItemsPrefetchCount(num);

加大RecyclerView的缓存

用空间换时间,来提高滚动的流畅性。

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

增加RecyclerView预留的额外空间

额外空间:显示范围之外,应该额外缓存的空间

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

减少ItemView监听器的创建

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

优化滑动操作

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

处理刷新闪烁

调用notifyDataSetChange时,适配器不知道整个数据集中的那些内容以及存在,再重新匹配ViewHolder时会花生闪烁。

设置adapter.setHasStableIds(true),并重写getItemId()来给每个Item一个唯一的ID

回收资源

通过重写RecyclerView.onViewRecycled(holder)来回收资源。

 

 

 

 

续上:

RecyclerView点击事件的具体代码实现

 1、给item设置点击事件、长按事件

     通常情况下我们设置点击事件或者长按事件都是通过下面的方式设置:

@Override
public void onBindViewHolder(XXViewHolder holder, int position) {
    holder.mItem.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //点击事件处理
            }
     });

     holder.mItem.setOnLongClickListener(new View.OnLongClickListener() {
 
            @Override
            public boolean onLongClick(View v) {
                //长按事件处理
                return false;
            }
     });
}

通过上面这种方法处理,简单暴力,但是如果假如数据有上万条,快速滑动,内存中会创建大量的对象,很显然这种方法不是很好,需要优化,怎么优化呢?还好rec给我提供了addOnItemTouchListener这个方法:

public  class OnRecyclerItemClickListener implements RecyclerView.OnItemTouchListener {
    private GestureDetectorCompat mGestureDetectorCompat;//手势探测器
    private RecyclerView mRecyclerView;
 
    public OnRecyclerItemClickListener(RecyclerView recyclerView) {
        mRecyclerView = recyclerView;
        mGestureDetectorCompat=new GestureDetectorCompat(recyclerView.getContext(),new   GestureDetector.SimpleOnGestureListener(){
            @Override
            public boolean onSingleTapUp(MotionEvent e) {
                View childViewUnder = mRecyclerView.findChildViewUnder(e.getX(), e.getY());
                if (childViewUnder != null) {
                    RecyclerView.ViewHolder childViewHolder = mRecyclerView.getChildViewHolder(childViewUnder);
                    onItemClick(childViewHolder);
                }
                return true;
            }
 
            @Override
            public void onLongPress(MotionEvent e) {
                View childViewUnder = mRecyclerView.findChildViewUnder(e.getX(), e.getY());
                if (childViewUnder != null) {
                    RecyclerView.ViewHolder childViewHolder = mRecyclerView.getChildViewHolder(childViewUnder);
                    onLongClick(childViewHolder);
                }
            }
        });
    }
 
    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        mGestureDetectorCompat.onTouchEvent(e);
        return false;
    }
 
    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
        mGestureDetectorCompat.onTouchEvent(e);
    }
 
    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    }
 
    public void onItemClick(RecyclerView.ViewHolder viewHolder) {
    }
 
    public void onLongClick(RecyclerView.ViewHolder viewHolder) {
    }
}

如何使用呢,也很简单:
rec.addOnItemTouchListener(new OnRecyclerItemClickListener(rec) {
            @Override
            public void onItemClick(RecyclerView.ViewHolder viewHolder) {
                
            }
});

好了,到此我们就可以优雅的给rec设置item点击事件了。

2、给item、item中的任意控件设置点击、长按事件

   下面说的这种方法既可以给item、也可给item中的任意控件设置点击、长按事件,不过给item设置还是建议上面的方法比较优雅。好了,还是那句话,如果不进行优化处理我们还是会通过上面的方法给item的控件设置事件,问题也一样,需要优化,优化代码如下:

1)在onBindViewHolder方法中将position通过tag方式绑定到控件上
    @Override
    public void onBindViewHolder(AViewHolder holder, int position) {
        //设置数据,绑定tag
        holder.mBtn.setText(mDatas.get(position));
        holder.mBtn.setTag(position);
    }

2) viewholder中给控件设置点击事件,但是需要在构造器中将数据传递进来
static class AViewHolder extends RecyclerView.ViewHolder {
        Button mBtn;
        // 将数据通过构造器传递进来
        ArrayList<String> mDatas;

        public AViewHolder(View itemView, ArrayList<String> data) {
            super(itemView);
            this.mDatas = data;
            mBtn = (Button) itemView.findViewById(R.id.button);
            mBtn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    int position = (int) v.getTag();
                    Log.e("TAG", "onClick: " + mDatas.get(position).toString());
                }
            });
        }
}

通过这种方式我们内存中会创建多少个点击事件对象呢?经过测试发现时屏幕显示个数+3个(有些不是很懂为什么多3个)
--------------------- 
原文:https://blog.csdn.net/zhq217217/article/details/79082093 

 

滚动RecyclerView加载图片时的流畅度优化的具体实现

实现:使用onScrollStateChanged回调检测滚动状态,并在RecyclerViewAdapter内部设置类似isScrolling的状态值来控制网络图片的加载。 
下面是代码举例:

// BaseAdapter中添加如下代码
public abstract class BaseRecyclerViewAdapter<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    ……
    protected boolean isScrolling = false;
    ……
    public void setScrolling(boolean scrolling) {
        isScrolling = scrolling;
    }
}

// 在子Adapter中的onBind里进行控制
public class SubRecyclerViewAdapter extends BaseRecyclerViewAdapter<DataGuardRanking> {
    ……
    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
        RecyclerViewHolder holder = (RecyclerViewHolder) viewHolder;
        ……
        if (!TextUtils.isEmpty(data.getAvatarUrl()) && !isScrolling) {
            // 这里可以用Glide等网络图片加载库
        } else {
            holder.avatarImg.setImageResource(占位图本地资源);
        }
        super.onBindViewHolder(holder, position);
    }
}

在UI层进行监听:
// 外部对RecyclerView设置监听
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
        // 查看源码可知State有三种状态:SCROLL_STATE_IDLE(静止)、SCROLL_STATE_DRAGGING(上升)、SCROLL_STATE_SETTLING(下落)
        if (newState == SCROLL_STATE_IDLE) { // 滚动静止时才加载图片资源,极大提升流畅度
            mRecyclerViewAdapter.setScrolling(false);
            mRecyclerViewAdapter.notifyDataSetChanged(); // notify调用后onBindViewHolder会响应调用
        } else
            mRecyclerViewAdapter.setScrolling(true);
        super.onScrollStateChanged(recyclerView, newState);
    }
});
mRecyclerView.setAdapter(mRecyclerViewAdapter);

 

 

参考链接

  1. RecyclerView 性能优化
  2. Android recycleView 的一些优化与相关问题
  3. RecyclerView Scrolling Performance
  4. Optimizing RecyclerView/ListView
  5. RecyclerView 列表类控件卡顿优化
  6. RecyclerView 数据预取
  7. DiffUtil新工具类,让你的RecyclerView飞一会
  8. 关于RecyclerView你知道的不知道的都在这了(上)
  9. 关于RecyclerView你知道的不知道的都在这了(下)

作者:编码前线
链接:https://www.jianshu.com/p/bd432a3527d6
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值