Android RecyclerView 性能优化总结

2021年09月15日 阅读数:3
这篇文章主要向大家介绍Android RecyclerView 性能优化总结,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

 

 

 

recyclerView.setHasFixedSize(true);

当Item的高度如是固定的,设置这个属性为true能够提升性能,尤为是当RecyclerView有条目插入、删除时性能提高更明显。javascript

RecyclerView在条目数量改变,会从新测量、布局各个item,若是设置了 setHasFixedSize(true),因为item的宽高都是固定的,adapter的内容改变时,RecyclerView不会整个布局都重绘。具体可用如下伪代码表示:java

void onItemsInsertedOrRemoved() {
   if (hasFixedSize) layoutChildren();
   else requestLayout();

LinearLayoutManager.setInitialItemPrefetchCount()

RecyclerView 嵌套 RecyclerView 实现横向滑动,设置 LinearLayoutManager.setInitialItemPrefetchCount() 设置 横向 滚动布局预加载个数,避免UI 卡顿android

  • 只有 LinearLayoutManager 有这个api,其余 LayoutManager 没有
  • 只有 RecyclerView 嵌套 RecyclerView 才有效

局部刷新

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)

DiffUtil

javascript:void(0)算法

DiffUtil.Callback 是一个抽象类api

    public abstract static class Callback {
        public abstract int getOldListSize();//老数据集size

        public abstract int getNewListSize();//新数据集size

        public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition);//新老数据集在同一个postion的Item是不是一个对象?(可能内容不一样,若是这里返回true,会调用下面的方法)

        public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition);//这个方法仅仅是上面方法返回ture才会调用,个人理解是只有notifyItemRangeChanged()才会调用,判断item的内容是否有变化

        //该方法在DiffUtil高级用法中用到 ,暂且不提
        @Nullable
        public Object getChangePayload(int oldItemPosition, int newItemPosition) {
            return null;
        }
    }

一、继承自DiffUtil.Callback的类,实现它的四个abstract方法缓存

/**
 * 介绍:核心类 用来判断 新旧Item是否相等
 * 做者:zhangxutong
 * 邮箱:zhangxutong@imcoming.com
 * 时间: 2016/9/12.
 */

public class DiffCallBack extends DiffUtil.Callback {
    private List<TestBean> mOldDatas, mNewDatas;//看名字

    public DiffCallBack(List<TestBean> mOldDatas, List<TestBean> mNewDatas) {
        this.mOldDatas = mOldDatas;
        this.mNewDatas = mNewDatas;
    }

    //老数据集size
    @Override
    public int getOldListSize() {
        return mOldDatas != null ? mOldDatas.size() : 0;
    }

    //新数据集size
    @Override
    public int getNewListSize() {
        return mNewDatas != null ? mNewDatas.size() : 0;
    }

    /**
     * Called by the DiffUtil to decide whether two object represent the same Item.
     * 被DiffUtil调用,用来判断 两个对象是不是相同的Item。
     * For example, if your items have unique ids, this method should check their id equality.
     * 例如,若是你的Item有惟一的id字段,这个方法就 判断id是否相等。
     * 本例判断name字段是否一致
     *
     * @param oldItemPosition The position of the item in the old list
     * @param newItemPosition The position of the item in the new list
     * @return True if the two items represent the same object or false if they are different.
     */
    @Override
    public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
        return mOldDatas.get(oldItemPosition).getName().equals(mNewDatas.get(newItemPosition).getName());
    }

    /**
     * Called by the DiffUtil when it wants to check whether two items have the same data.
     * 被DiffUtil调用,用来检查 两个item是否含有相同的数据
     * DiffUtil uses this information to detect if the contents of an item has changed.
     * DiffUtil用返回的信息(true false)来检测当前item的内容是否发生了变化
     * DiffUtil uses this method to check equality instead of {@link Object#equals(Object)}
     * DiffUtil 用这个方法替代equals方法去检查是否相等。
     * so that you can change its behavior depending on your UI.
     * 因此你能够根据你的UI去改变它的返回值
     * For example, if you are using DiffUtil with a
     * {@link android.support.v7.widget.RecyclerView.Adapter RecyclerView.Adapter}, you should
     * return whether the items' visual representations are the same.
     * 例如,若是你用RecyclerView.Adapter 配合DiffUtil使用,你须要返回Item的视觉表现是否相同。
     * This method is called only if {@link #areItemsTheSame(int, int)} returns
     * {@code true} for these items.
     * 这个方法仅仅在areItemsTheSame()返回true时,才调用。
     * @param oldItemPosition The position of the item in the old list
     * @param newItemPosition The position of the item in the new list which replaces the
     *                        oldItem
     * @return True if the contents of the items are the same or false if they are different.
     */
    @Override
    public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
        TestBean beanOld = mOldDatas.get(oldItemPosition);
        TestBean beanNew = mNewDatas.get(newItemPosition);
        if (!beanOld.getDesc().equals(beanNew.getDesc())) {
            return false;//若是有内容不一样,就返回false
        }
        if (beanOld.getPic() != beanNew.getPic()) {
            return false;//若是有内容不一样,就返回false
        }
        return true; //默认两个data内容是相同的
    }

梳理流程图以下
Android RecyclerView 性能优化总结_RecyclerView优化性能优化

二、使用方法ide

注释掉你之前写的notifyDatasetChanged()方法吧布局

在将newDatas 设置给Adapter以前,先调用DiffUtil.calculateDiff()方法,计算出新老数据集转化的最小更新集,就是DiffUtil.DiffResult对象。post

//第一个参数是DiffUtil.Callback对象,
//第二个参数表明是否检测Item的移动,改成false算法效率更高,按需设置,咱们这里是true。
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(mDatas, newDatas), true);

三、DiffUtil.DiffResult 对象的 dispatchUpdatesTo()方法,传入RecyclerView的Adapter,替代原来的mAdapter.notifyDataSetChanged()方法。

diffResult.dispatchUpdatesTo(mAdapter);

Android RecyclerView 性能优化总结_赵彦军_02

AsyncListDiff

public class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserViewHodler> {
    private AsyncListDiffer<User> mDiffer;

    private DiffUtil.ItemCallback<User> diffCallback = new DiffUtil.ItemCallback<User>() {
        @Override
        public boolean areItemsTheSame(User oldItem, User newItem) {
            return TextUtils.equals(oldItem.getId(), newItem.getId());
        }

        @Override
        public boolean areContentsTheSame(User oldItem, User newItem) {
            return oldItem.getAge() == newItem.getAge();
        }
    };

    public UserAdapter() {
        mDiffer = new AsyncListDiffer<>(this, diffCallback);
    }

    @Override
    public int getItemCount() {
        return mDiffer.getCurrentList().size();
    }

    public void submitList(List<User> data) {
        mDiffer.submitList(data);
    }

    public User getItem(int position) {
        return mDiffer.getCurrentList().get(position);
    }

    @NonNull
    @Override
    public UserAdapter.UserViewHodler onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_user_list, parent, false);
        return new UserViewHodler(itemView);
    }

    @Override
    public void onBindViewHolder(@NonNull UserAdapter.UserViewHodler holder, int position) {
        holder.setData(getItem(position));
    }

    class UserViewHodler extends RecyclerView.ViewHolder {
         //此处省略一段

        public UserViewHodler(View itemView) {
            super(itemView);
        }

        public void setData(User data) {
            tvName.setText(data.getName());
            tvAge.setText(String.valueOf(data.getAge()));
        }
    }
}

使用方法:

List<User> users = new ArrayList<>();
for (int i = 0; i < 10; i++) {
    users.add(new User(String.valueOf(i), "用户" + i, i + 20));
}
mAdapter.submitList(users);

getExtraLayoutSpace

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

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

这个机制会致使一个问题,启动应用以后,在屏幕可见范围内,若是只有一张卡片可见,当滚动的时 候,RecyclerView找不到能够重用的view了,它将建立一个新的,所以在滑动到第二个feed的时候就会有必定的延时,可是第二个feed之 后的滚动是流畅的,由于这个时候RecyclerView已经有能重用的view了。

如何解决这个问题呢,其实只需重写getExtraLayoutSpace()方法。根据官方文档的描述 getExtraLayoutSpace将返回LayoutManager应该预留的额外空间(显示范围以外,应该额外缓存的空间)。

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

避免建立过多 OnClickListener 对象

onCreateViewHolder 和 onBindViewHolder 对时间都比较敏感,尽可能避免繁琐的操做和循环建立对象。例如建立 OnClickListener,能够全局建立一个。

同时onBindViewHolder调用次数会多于onCreateViewHolder的次数,如从RecyclerViewPool缓存池中取到的View都须要从新bindView,因此咱们能够把监听放到CreateView中进行。

优化前:

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

优化后:

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
        }
}

复用RecycledViewPool

在TabLayout+ViewPager+RecyclerView的场景中,当多个RecyclerView有相同的item布局结构时,多个RecyclerView共用一个RecycledViewPool能够避免建立ViewHolder的开销,避免GC。RecycledViewPool对象可经过RecyclerView对象获取,也能够本身实现。

RecycledViewPool mPool = mRecyclerView1.getRecycledViewPool();
//下一个RecyclerView可直接进行setRecycledViewPool

mRecyclerView2.setRecycledViewPool(mPool);

mRecyclerView3.setRecycledViewPool(mPool);

减小过分绘制

减小布局层级