RecyclerView的优化:RecycledViewPool

原文出处:https://yrom.net/blog/2015/05/30/recyclerView-tips-1-recycledviewpool/

想必Tabs+ViewPager+ListView 结合使用的场景在你的Android手机中的各大应用里并不少见,比如最为典型的网易新闻。

众所周知,用RecyclerView可以非常简单的替代掉ListView。可仅仅就为了将ListView换成RecyclerView,这换汤不换药的做法显然不足以让人心动。

如果我说,再用上RecycledViewPool,可以使你的布局渲染速度、ViewPager滑动流畅度上升一个档次,你会心动并行动吗?

RecycledViewPool是什么?

关于RecycledViewPool,官方文档是这样说的:

Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views. This can be useful if you have multiple RecyclerViews with adapters that use the same view types, for example if you have several data sets with the same kinds of item views displayed by a ViewPager. RecyclerView automatically creates a pool for itself if you don’t provide one.

简言之就是,你可以给RecyclerView设置一个ViewHolder的对象池,这个池称为RecycledViewPool,这个对象池可以节省你创建ViewHolder的开销,更能避免GC。即便你不给它设置,它也会自己创建一个。

如此说来,如果多个RecylerView间共用一个RecycledViewPool是不是能让你的UI更加的“顺滑”?

使用RecycledViewPool

RecycledViewPool使用起来也是非常的简单:先从某个RecyclerView对象中获得它创建的RecycledViewPool对象,或者是自己实现一个RecycledViewPool对象,然后设置个接下来创建的每一个RecyclerView即可。

需要注意的是,如果你使用的LayoutManager是LinearLayoutManager或其子类(如GridLayoutManager),需要手动开启这个特性:

layout.setRecycleChildrenOnDetach(true)
RecyclerView view1 = new RecyclerView(context);
LinearLayoutManager layout = new LinearLayoutManager(context);
layout.setRecycleChildrenOnDetach(true);
view1.setLayoutManager(layout);
RecycledViewPool pool = view1.getRecycledViewPool();
//...
RecyclerView view2 = new RecyclerView(context);
//... (set layout manager)
view2.setRecycledViewPool(pool);
//...
RecyclerView view3 = new RecyclerView(context);
//...(set layout manager)
view3.setRecycledViewPool(pool);

ViewPager中使用原理同上,只是你得通过ViewPagerAdapter传递个下一个RecyclerView。

代码示例如下:

public class PagerActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_pager);
        ViewPager pager = (ViewPager) findViewById(R.id.pager);
        pager.setAdapter(new PageAdapter(getSupportFragmentManager()));
    }
    static class PageAdapter extends FragmentPagerAdapter{
        RecyclerView.RecycledViewPool mPool = new RecyclerView.RecycledViewPool();
        public PageAdapter(FragmentManager fm) {
            super(fm);
        }
        @Override
        public Fragment getItem(int i) {
            RecyclerViewFragment f = new RecyclerViewFragment();
            f.mPool = mPool;
            return f;
        }
        // ...
    }
    public static class RecyclerViewFragment extends Fragment{
        RecyclerView.RecycledViewPool mPool;
        @Nullable
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            RecyclerView view = new RecyclerView(inflater.getContext());
            LinearLayoutManager layout = new LinearLayoutManager(inflater.getContext());
            layout.setRecycleChildrenOnDetach(true);
            view.setLayoutManager(layout);
            if (mPool != null) {
                view.setRecycledViewPool(mPool);
            }
            view.setAdapter(...);
            return view;
        }
        // ...
    }
}

布局xml:activity_pager.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent">
    <android.support.v4.view.ViewPager  android:id="@+id/pager" android:layout_width="match_parent" android:layout_height="match_parent">
        <android.support.v4.view.PagerTitleStrip  android:id="@+id/tabs" android:layout_width="match_parent" android:layout_height="wrap_content"/>
    </android.support.v4.view.ViewPager>
</LinearLayout>

详见 https://gist.github.com/yrom/728237025575005a8fd3

More Tips

RecycledViewPool是依据ItemViewType来索引ViewHolder的,所以你必须确保共享的RecyclerView的Adapter是同一个,或view type 是不会冲突的。

RecycledViewPool可以自主控制需要缓存的ViewHolder数量:
mPool.setMaxRecycledViews(itemViewType, number); 毕竟池子里的水并不是越深越好。

RecyclerView可以设置自己所需要的ViewHolder数量,只有超过这个数量的detached ViewHolder才会丢进ViewPool中与别的RecyclerView共享。

recyclerView.setItemViewCacheSize(10);

在合适的时机,RecycledViewPool会自我清除掉所持有的ViewHolder对象引用,不用担心池子会“侧漏”。当然你也可以在你认为合适的时机手动调用clear()

用RecyclerView快速实现列表页面

如一个简单的列表场景:TodoList。

分页加载现有Todo
现有数据基础上增、删、改
RecyclerView的使用在此就不赘述了,本文主要讨论RecyclerView.Adapter的实现

使用最简单的ArrayList实现,如下:

class ListAdapter extends RecyclerView.Adapter<TodoViewHolder> {
    final ArrayList<Item> mData;
    final LayoutInflater mLayoutInflater;
    public SortedListAdapter(Context context) {
        mLayoutInflater = LayoutInflater.from(context);
        mData = new ArrayList<>();
    }
    public void addItem(Item item) {
        mData.add(item); // 需要自己通知更新
    }
    @Override
    public TodoViewHolder onCreateViewHolder(final ViewGroup parent, int viewType) {
        return new TodoViewHolder (
                mLayoutInflater.inflate(R.layout.list_todo_item, parent, false));
    }
    @Override
    public void onBindViewHolder(TodoViewHolder holder, int position) {
        holder.bindTo(mData.get(position));
    }
    @Override
    public int getItemCount() {
        return mData.size();
    }
}

这样的Adapter一个显而易见的问题就是,如何做数据的去重。

添加一项数据:最简单的是在addItem()之前,遍历一次mData,定位后再决定是插入还是更新现有数据,并调用notifyItemInserted(pos)。
添加多个数据:多次重复上面的方法…
对于少量数据来说这样做并不见得有什么问题,而且写得多了,都有自己封装好的诸如ArrayObjectAdapter之类方便使用。

这样就够了吗?

答案肯定是不。Android Support Library 悄悄给我们提供了一个叫SortedList的工具类,它默默的藏在support库的角落中,鲜为人知。

SortedList?

文档对它的定义:

  1. 是一个有序列表
  2. 数据变动会触发回调SortedList.Callback的方法,如onChanged()

构造一个SortedList需要实现它的回调SortedList.Callback,并由其来定义数据的排序和数据的唯一性。它有一个实现类SortedListAdapterCallback就是RecyclerView.Adapter与SortedList交互的秘密武器。

示例

改造后的ListAdapter:

class SortedListAdapter extends RecyclerView.Adapter<TodoViewHolder> {
    final SortedList<Item> mData;
    final LayoutInflater mLayoutInflater;
    public SortedListAdapter(Context context) {
        mLayoutInflater = LayoutInflater.from(context);
        mData = new SortedList<Item>(Item.class, new SortedListAdapterCallback<Item>(this){
            @Override
            public int compare(Item t0, Item t1) {
                // 实现这个方法来定义Item的显示顺序
                int txtComp = t0.mText.compareTo(t1.mText);
                if (txtComp != 0) {
                    return txtComp;
                }
                if (t0.id < t1.id) {
                    return -1;
                } else if (t0.id > t1.id) {
                    return 1;
                }
                return 0;
            }
            @Override
            public boolean areContentsTheSame(Item oldItem,
                    Item newItem) {
                // 比较两个Item的内容是否一致,如不一致则会调用adapter的notifyItemChanged()
                return oldItem.mText.equals(newItem.mText);
            }
            @Override
            public boolean areItemsTheSame(Item item1, Item item2) {
                // 两个Item是不是同一个东西,
                // 它们的内容或许不一样,但id相同代表就是同一个
                return item1.id == item2.id;
            }
        });
    }
    public void addItem(Item item) {
        mData.add(item);
        // 会通过SortedListAdapterCallback自动通知更新
    }
    ...
    @Override
    public int getItemCount() {
        return mData.size();
    }
}

虽然相对ListAdapter代码量变多了,但是调用者却再也不用关心数据的去重与通知更新的问题。这一切都有SortedListAdapterCallback帮你自动处理好了。

单单只这一个好处其实并不值得劳师动众去改掉现有Adapter使用SortedList,但它另外还有一个令人称赞并喜爱的功能:批量更新(Batched Updates)。

就如实现添加多个数据:

void addItems(List<Item> items){
    mData.beginBatchedUpdates();  // 开始批量更新
    mData.addAll(items);          // 更新一批数据
    mData.endBatchedUpdates();    // 结束更新
}

批量删除:

void deleteItems(List<Item> items){
    mData.beginBatchedUpdates();  // 开始批量更新
    for(Item item : items){       // 删除一批数据
         mData.remove(item);
    }
    mData.endBatchedUpdates();    // 结束更新
}

等等。
如例子所示,调用beginBatchedUpdates()之后,所有的对SortedList操作都会等到endBatchedUpdates()之后一起生效。

完整的示例见:官方Support库Samples ($ANDROID_SDK/extras/android/support/samples/Support7Demos)

More Tips

列表无序的情况,可以用其id或原始数据List的index来比较排序,只要确保能正确实现compare即可
如无需批量更新,或无频繁的增删改,其实用前面的ListAdapter比较好。
总之:如果你的列表需要批量更新或者频繁删改,且刚好有明确的先后顺序,快使用SortedList。

    原文作者:Jack-Chan
    原文地址: https://blog.csdn.net/axi295309066/article/details/52741810
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞