转载

关于RecyclerView中Viewholder和View的缓存机制的探究

这个题目放在草稿箱里面许久了,一直没有动力提笔。趁现在公司人还没有来齐,工作量还不是很大,就挤出来时间来把它完善了。

我们知道,RecyclerView是经典的ListView的进化与升华,它比ListView更加灵活,但也因此引入了一定的复杂性。最新的v7支持包新添加了RecyclerView。

我们知道,ListView通过使用ViewHolder来提升性能。ViewHolder通过保存item中使用到的控件的引用来减少findViewById的调用,以此使ListView滑动得更加顺畅。但这种模式即使不使用也无妨。

对于还不是很熟悉RecyclerView的程序员,可以先从这两篇文章里面了解一下它的基本用法:

Android L新控件RecyclerView简介

RecyclerView和CardView简介

这里对RecyclerView的基本使用方式,就不再赘述,只讨论RecyclerView中的ViewHolder的缓存机制。其实它的缓存机制大致原理跟ListView是相似的,只是在具体的实现细节上面有些差异。对于这些RecyclerView的缓存机制, Android L新控件RecyclerView简介 这篇文章的结尾进行了总结性的概括,这里讨论的内容要远比这篇简介的内容要复杂和多。

首先,我定义了三个不同的Adapter,里面分别对应了三种情况:item类型只有一种;item类型有两种;item类型有三种。里面均重写了onCreateViewHolder方法,用于记录生成的ViewHolder或者View的数目。内容可以从Log里面查询得到。三个Adapter的细节分别是:

关于RecyclerView中Viewholder和View的缓存机制的探究
  1 /**      2  * @Title: MyRVAdapter.java    3  * @Package com.example.recyclerviewtest.adapter    4  * @Description: TODO   5  * @author SilentKnight || happychinapc[at]gmail[dot]com      6  * @date 2015 2015年1月22日 下午4:54:25    7  * @version V1.0.0      8  */   9 package com.example.recyclerviewtest.adapter;  10   11 import java.util.List;  12   13 import android.content.Context;  14 import android.support.v7.widget.RecyclerView;  15 import android.util.Log;  16 import android.view.LayoutInflater;  17 import android.view.View;  18 import android.view.ViewGroup;  19 import android.view.View.OnClickListener;  20 import android.widget.TextView;  21 import android.widget.Toast;  22   23 import com.example.recyclerviewtest.R;  24   25 /**  26  * @ClassName: MyRVAdapter  27  * @Description: TODO  28  * @author SilentKnight || happychinapc@gmail.com  29  * @date 2015年1月22日 下午4:54:25  30  *   31  */  32 public class MyRVAdapter extends RecyclerView.Adapter<MyRVAdapter.ViewHolder> {  33     private static int COUNT_CACHE_VIEW = 0;  34     private static final String ADAPTER_TAG = MyRVAdapter.class.getSimpleName();  35     private List<String> dataSet;  36     private Context context;  37   38     public MyRVAdapter(Context context, List<String> dataSet) {  39         this.context = context;  40         this.dataSet = dataSet;  41     }  42   43     /*  44      * (non-avadoc) <p>Title: getItemId</p> <p>Description: </p>  45      *   46      * @params @param position  47      *   48      * @params @return  49      *   50      * @overrided @see  51      * android.support.v7.widget.RecyclerView.Adapter#getItemId(int)  52      */  53     @Override  54     public long getItemId(int position) {  55         // TODO Auto-generated method stub  56         return position;  57     }  58   59     /*  60      * (non-avadoc) <p>Title: getItemCount</p> <p>Description: </p>  61      *   62      * @params @return  63      *   64      * @overrided @see  65      * android.support.v7.widget.RecyclerView.Adapter#getItemCount()  66      */  67     @Override  68     public int getItemCount() {  69         // TODO Auto-generated method stub  70         return dataSet.size();  71     }  72   73     /*  74      * (non-avadoc) <p>Title: onBindViewHolder</p> <p>Description: </p>  75      *   76      * @params @param arg0  77      *   78      * @params @param arg1  79      *   80      * @overrided @see  81      * android.support.v7.widget.RecyclerView.Adapter#onBindViewHolder  82      * (android.support.v7.widget.RecyclerView.ViewHolder, int)  83      */  84     @Override  85     public void onBindViewHolder(ViewHolder holder, final int arg1) {  86         // TODO Auto-generated method stub  87         holder.tv.setOnClickListener(new OnClickListener() {  88   89             @Override  90             public void onClick(View v) {  91                 // TODO Auto-generated method stub  92                 Toast.makeText(context, "You clickd TextView at index of "+arg1,  93                         Toast.LENGTH_SHORT).show();  94             }  95         });  96         holder.tv.setText(dataSet.get(arg1));  97     }  98   99     /* 100      * (non-avadoc) <p>Title: onCreateViewHolder</p> <p>Description: </p> 101      *  102      * @params @param arg0 103      *  104      * @params @param arg1 105      *  106      * @params @return 107      *  108      * @overrided @see 109      * android.support.v7.widget.RecyclerView.Adapter#onCreateViewHolder 110      * (android.view.ViewGroup, int) 111      */ 112  113     @Override 114     public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int arg1) { 115         // TODO Auto-generated method stub 116         Log.i(ADAPTER_TAG, "itemTV---" + ++COUNT_CACHE_VIEW); 117         View itemLayout = LayoutInflater.from(viewGroup.getContext()).inflate( 118                 R.layout.recycler_view_item_layout_tv, null); 119         return new ViewHolder(itemLayout); 120     } 121  122     public static class ViewHolder extends RecyclerView.ViewHolder { 123         public TextView tv; 124  125         /** 126          * <p> 127          * Title: MainActivity.java 128          * </p> 129          * <p> 130          * Description: 131          * </p> 132          *  133          * @param @param itemView 134          */ 135         public ViewHolder(View itemView) { 136             super(itemView); 137             // TODO Auto-generated constructor stub 138             tv = (TextView) itemView.findViewById(R.id.rv_item_tv); 139         } 140     } 141 }
MyRVAdapter
关于RecyclerView中Viewholder和View的缓存机制的探究
  1 /**      2  * @Title: MyRVAdapter2.java    3  * @Package com.example.recyclerviewtest.adapter    4  * @Description: TODO   5  * @author SilentKnight || happychinapc[at]gmail[dot]com      6  * @date 2015 2015年1月22日 下午4:55:31    7  * @version V1.0.0      8  */   9 package com.example.recyclerviewtest.adapter;  10   11 import java.util.List;  12   13 import android.content.Context;  14 import android.content.Intent;  15 import android.support.v7.widget.RecyclerView;  16 import android.util.Log;  17 import android.view.LayoutInflater;  18 import android.view.View;  19 import android.view.ViewGroup;  20 import android.view.View.OnClickListener;  21 import android.widget.ImageView;  22 import android.widget.TextView;  23 import android.widget.Toast;  24   25 import com.example.recyclerviewtest.GridViewHorizontalTest;  26 import com.example.recyclerviewtest.GridViewVerticalTest;  27 import com.example.recyclerviewtest.R;  28   29 /**  30  * @ClassName: MyRVAdapter2  31  * @Description: TODO  32  * @author SilentKnight || happychinapc@gmail.com  33  * @date 2015年1月22日 下午4:55:31  34  *   35  */  36 public class MyRVAdapter2 extends RecyclerView.Adapter<MyRVAdapter2.ViewHolder> {  37     private static int COUNT_CACHE_VIEW_1 = 0;  38     private static int COUNT_CACHE_VIEW_2 = 0;  39     private static final String ADAPTER_TAG = MyRVAdapter2.class  40             .getSimpleName();  41     private static final int TYPE_TV = 0x000;  42     private static final int TYPE_IV = 0x0001;  43     private List<String> dataSet;  44     private Context context;  45   46     public MyRVAdapter2(Context context, List<String> dataSet) {  47         this.context = context;  48         this.dataSet = dataSet;  49     }  50   51     /*  52      * (non-avadoc) <p>Title: getItemId</p> <p>Description: </p>  53      *   54      * @params @param position  55      *   56      * @params @return  57      *   58      * @overrided @see  59      * android.support.v7.widget.RecyclerView.Adapter#getItemId(int)  60      */  61     @Override  62     public long getItemId(int position) {  63         // TODO Auto-generated method stub  64         return position;  65     }  66   67     /*  68      * (non-avadoc) <p>Title: getItemViewType</p> <p>Description: </p>  69      *   70      * @params @param position  71      *   72      * @params @return  73      *   74      * @overrided @see  75      * android.support.v7.widget.RecyclerView.Adapter#getItemViewType(int)  76      */  77     @Override  78     public int getItemViewType(int position) {  79         // TODO Auto-generated method stub  80         if (position % 2 == 0) {  81             return TYPE_TV;  82         } else {  83             return TYPE_IV;  84         }  85     }  86   87     /*  88      * (non-avadoc) <p>Title: getItemCount</p> <p>Description: </p>  89      *   90      * @params @return  91      *   92      * @overrided @see  93      * android.support.v7.widget.RecyclerView.Adapter#getItemCount()  94      */  95     @Override  96     public int getItemCount() {  97         // TODO Auto-generated method stub  98         return dataSet.size();  99     } 100  101     /* 102      * (non-avadoc) <p>Title: onBindViewHolder</p> <p>Description: </p> 103      *  104      * @params @param arg0 105      *  106      * @params @param arg1 107      *  108      * @overrided @see 109      * android.support.v7.widget.RecyclerView.Adapter#onBindViewHolder 110      * (android.support.v7.widget.RecyclerView.ViewHolder, int) 111      */ 112     @Override 113     public void onBindViewHolder(ViewHolder holder, int arg1) { 114         // TODO Auto-generated method stub 115         if (getItemViewType(arg1) == TYPE_TV) { 116             holder.tv.setOnClickListener(new OnClickListener() { 117  118                 @Override 119                 public void onClick(View v) { 120                     // TODO Auto-generated method stub 121                     context.startActivity(new Intent(context, 122                             GridViewHorizontalTest.class)); 123                 } 124             }); 125             holder.tv.setText(dataSet.get(arg1)); 126         } else { 127             holder.iv.setOnClickListener(new OnClickListener() { 128  129                 @Override 130                 public void onClick(View v) { 131                     // TODO Auto-generated method stub 132                     context.startActivity(new Intent(context, 133                             GridViewVerticalTest.class)); 134                 } 135             }); 136         } 137     } 138  139     /* 140      * (non-avadoc) <p>Title: onCreateViewHolder</p> <p>Description: </p> 141      *  142      * @params @param arg0 143      *  144      * @params @param arg1 145      *  146      * @params @return 147      *  148      * @overrided @see 149      * android.support.v7.widget.RecyclerView.Adapter#onCreateViewHolder 150      * (android.view.ViewGroup, int) 151      */ 152  153     @Override 154     public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int arg1) { 155         // TODO Auto-generated method stub 156         if (getItemViewType(arg1) == TYPE_TV) { 157             Log.i(ADAPTER_TAG, "itemTV---" + ++COUNT_CACHE_VIEW_1); 158             View itemLayout = LayoutInflater.from(viewGroup.getContext()) 159                     .inflate(R.layout.recycler_view_item_layout_tv, null); 160             return new ViewHolder(itemLayout, TYPE_TV); 161         } else { 162             Log.i(ADAPTER_TAG, "itemIV---" + ++COUNT_CACHE_VIEW_2); 163             View itemLayout = LayoutInflater.from(viewGroup.getContext()) 164                     .inflate(R.layout.recycler_view_item_layout_iv, null); 165             return new ViewHolder(itemLayout, TYPE_IV); 166         } 167     } 168  169     public static class ViewHolder extends RecyclerView.ViewHolder { 170         public TextView tv; 171         public ImageView iv; 172  173         /** 174          * <p> 175          * Title: MainActivity.java 176          * </p> 177          * <p> 178          * Description: 179          * </p> 180          *  181          * @param @param itemView 182          */ 183         public ViewHolder(View itemView, int itemType) { 184             super(itemView); 185             // TODO Auto-generated constructor stub 186             if (itemType == TYPE_TV) { 187                 tv = (TextView) itemView.findViewById(R.id.rv_item_tv); 188             } else { 189                 iv = (ImageView) itemView.findViewById(R.id.rv_item_iv); 190             } 191         } 192     } 193 }
MyRVAdapter2
关于RecyclerView中Viewholder和View的缓存机制的探究
  1 /**      2  * @Title: MyRVAdapter3.java    3  * @Package com.example.recyclerviewtest.adapter    4  * @Description: TODO   5  * @author SilentKnight || happychinapc[at]gmail[dot]com      6  * @date 2015 2015年1月22日 下午4:56:33    7  * @version V1.0.0      8  */   9 package com.example.recyclerviewtest.adapter;  10   11 import java.util.List;  12   13 import android.content.Context;  14 import android.support.v7.widget.RecyclerView;  15 import android.util.Log;  16 import android.view.LayoutInflater;  17 import android.view.View;  18 import android.view.ViewGroup;  19 import android.view.View.OnClickListener;  20 import android.widget.ImageView;  21 import android.widget.TextView;  22 import android.widget.Toast;  23   24 import com.example.recyclerviewtest.R;  25   26 /**  27  * @ClassName: MyRVAdapter3  28  * @Description: TODO  29  * @author SilentKnight || happychinapc@gmail.com  30  * @date 2015年1月22日 下午4:56:33  31  *   32  */  33 public class MyRVAdapter3 extends RecyclerView.Adapter<MyRVAdapter3.ViewHolder> {  34     private static int COUNT_CACHE_VIEW_1 = 0;  35     private static int COUNT_CACHE_VIEW_2 = 0;  36     private static int COUNT_CACHE_VIEW_3 = 0;  37     private static final String ADAPTER_TAG = MyRVAdapter3.class  38             .getSimpleName();  39     private static final int TYPE_TV = 0x000;  40     private static final int TYPE_IV = 0x0001;  41     private static final int TYPE_TV_2 = 0x0002;  42     private List<String> dataSet;  43     private Context context;  44   45     public MyRVAdapter3(Context context, List<String> dataSet) {  46         this.context = context;  47         this.dataSet = dataSet;  48     }  49   50     /*  51      * (non-avadoc) <p>Title: getItemId</p> <p>Description: </p>  52      *   53      * @params @param position  54      *   55      * @params @return  56      *   57      * @overrided @see  58      * android.support.v7.widget.RecyclerView.Adapter#getItemId(int)  59      */  60     @Override  61     public long getItemId(int position) {  62         // TODO Auto-generated method stub  63         return position;  64     }  65   66     /*  67      * (non-avadoc) <p>Title: getItemViewType</p> <p>Description: </p>  68      *   69      * @params @param position  70      *   71      * @params @return  72      *   73      * @overrided @see  74      * android.support.v7.widget.RecyclerView.Adapter#getItemViewType(int)  75      */  76     @Override  77     public int getItemViewType(int position) {  78         // TODO Auto-generated method stub  79         if (position % 3 == 0) {  80             return TYPE_TV;  81         } else if (position % 3 == 1) {  82             return TYPE_IV;  83         } else {  84             return TYPE_TV_2;  85         }  86     }  87   88     /*  89      * (non-avadoc) <p>Title: getItemCount</p> <p>Description: </p>  90      *   91      * @params @return  92      *   93      * @overrided @see  94      * android.support.v7.widget.RecyclerView.Adapter#getItemCount()  95      */  96     @Override  97     public int getItemCount() {  98         // TODO Auto-generated method stub  99         return dataSet.size(); 100     } 101  102     /* 103      * (non-avadoc) <p>Title: onBindViewHolder</p> <p>Description: </p> 104      *  105      * @params @param arg0 106      *  107      * @params @param arg1 108      *  109      * @overrided @see 110      * android.support.v7.widget.RecyclerView.Adapter#onBindViewHolder 111      * (android.support.v7.widget.RecyclerView.ViewHolder, int) 112      */ 113     @Override 114     public void onBindViewHolder(ViewHolder holder, int arg1) { 115         // TODO Auto-generated method stub 116         if (getItemViewType(arg1) == TYPE_TV) { 117             holder.tv.setOnClickListener(new OnClickListener() { 118  119                 @Override 120                 public void onClick(View v) { 121                     // TODO Auto-generated method stub 122                     Toast.makeText(context, "You clickd TextView", 123                             Toast.LENGTH_SHORT).show(); 124                 } 125             }); 126             holder.tv.setText(dataSet.get(arg1)); 127         } else if (getItemViewType(arg1) == TYPE_IV) { 128             holder.iv.setOnClickListener(new OnClickListener() { 129  130                 @Override 131                 public void onClick(View v) { 132                     // TODO Auto-generated method stub 133  134                     Toast.makeText(context, "You clickd ImageView", 135                             Toast.LENGTH_SHORT).show(); 136                 } 137             }); 138         } else { 139  140         } 141     } 142  143     /* 144      * (non-avadoc) <p>Title: onCreateViewHolder</p> <p>Description: </p> 145      *  146      * @params @param arg0 147      *  148      * @params @param arg1 149      *  150      * @params @return 151      *  152      * @overrided @see 153      * android.support.v7.widget.RecyclerView.Adapter#onCreateViewHolder 154      * (android.view.ViewGroup, int) 155      */ 156  157     @Override 158     public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int arg1) { 159         // TODO Auto-generated method stub 160         if (getItemViewType(arg1) == TYPE_TV) { 161             Log.i(ADAPTER_TAG, "itemTV---" + ++COUNT_CACHE_VIEW_1); 162             View itemLayout = LayoutInflater.from(viewGroup.getContext()) 163                     .inflate(R.layout.recycler_view_item_layout_tv, null); 164             return new ViewHolder(itemLayout, TYPE_TV); 165         } else if (getItemViewType(arg1) == TYPE_IV) { 166             Log.i(ADAPTER_TAG, "itemIV---" + ++COUNT_CACHE_VIEW_2); 167             View itemLayout = LayoutInflater.from(viewGroup.getContext()) 168                     .inflate(R.layout.recycler_view_item_layout_iv, null); 169             return new ViewHolder(itemLayout, TYPE_IV); 170         } else { 171             Log.i(ADAPTER_TAG, "itemTV2---" + ++COUNT_CACHE_VIEW_3); 172             View itemLayout = LayoutInflater.from(viewGroup.getContext()) 173                     .inflate(R.layout.recycler_view_item_layout_tv_2, null); 174             return new ViewHolder(itemLayout, TYPE_TV_2); 175         } 176     } 177  178     public static class ViewHolder extends RecyclerView.ViewHolder { 179         public TextView tv; 180         public TextView tv2; 181         public ImageView iv; 182  183         /** 184          * <p> 185          * Title: MainActivity.java 186          * </p> 187          * <p> 188          * Description: 189          * </p> 190          *  191          * @param @param itemView 192          */ 193         public ViewHolder(View itemView, int itemType) { 194             super(itemView); 195             // TODO Auto-generated constructor stub 196             if (itemType == TYPE_TV) { 197                 tv = (TextView) itemView.findViewById(R.id.rv_item_tv); 198             } else if (itemType == TYPE_IV) { 199                 iv = (ImageView) itemView.findViewById(R.id.rv_item_iv); 200             } else { 201                 tv2 = (TextView) itemView.findViewById(R.id.rv_item_tv); 202             } 203         } 204     } 205 }
MyRVAdapter3

同时,这个测试工程里面只有一个Activity,它的详情如下:

关于RecyclerView中Viewholder和View的缓存机制的探究
 1 package com.example.recyclerviewtest;  2   3 import java.util.List;  4   5 import com.example.recyclerviewtest.adapter.MyRVAdapter;  6 import com.example.recyclerviewtest.adapter.MyRVAdapter2;  7 import com.example.recyclerviewtest.adapter.MyRVAdapter3;  8 import com.example.recyclerviewtest.util.Utils;  9  10 import android.support.v7.app.ActionBarActivity; 11 import android.support.v7.widget.LinearLayoutManager; 12 import android.support.v7.widget.RecyclerView; 13 import android.os.Bundle; 14 import android.widget.LinearLayout; 15  16 public class MainActivity extends ActionBarActivity { 17     private static final String TAG = MainActivity.class.getSimpleName(); 18  19     private RecyclerView rvHorizontal; 20     private RecyclerView rvVertical; 21  22     @Override 23     protected void onCreate(Bundle savedInstanceState) { 24         super.onCreate(savedInstanceState); 25         setContentView(R.layout.activity_main); 26         rvHorizontal = (RecyclerView) findViewById(R.id.recyclier_view_horizontal); 27         rvVertical = (RecyclerView) findViewById(R.id.recyclier_view_vertical); 28         List<String> dataSet = Utils.generateDataSet(); 29         LinearLayoutManager layoutManager = new LinearLayoutManager(this, 30                 LinearLayout.HORIZONTAL, false); 31         rvHorizontal.setLayoutManager(layoutManager); 32         layoutManager = new LinearLayoutManager(this, LinearLayout.VERTICAL, 33                 false); 34         rvVertical.setLayoutManager(layoutManager); 35         MyRVAdapter adapter = new MyRVAdapter(this, dataSet); 36         MyRVAdapter2 adapter2 = new MyRVAdapter2(this, dataSet); 37         MyRVAdapter3 adapter3 = new MyRVAdapter3(this, dataSet); 38         rvHorizontal.setAdapter(adapter2); 39         rvVertical.setAdapter(adapter); 40     } 41  42 }
MainActivity

对于你想测试的三种情况,你可以通达代码rvHorizontal .setAdapter(adapter2)和rvVertical .setAdapter(adapter)来进行测试。在测试的过程中,可以通过改变RecyclerView的宽度来测试显示ViewHolder的数目。比如,item的宽度为60dp,你可以分别测试下RecyclerView的宽度分别为60dp、120dp和180dp情况下log的打印情况。其中,activity的布局文件如下:

关于RecyclerView中Viewholder和View的缓存机制的探究
 1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  2     xmlns:tools="http://schemas.android.com/tools"  3     android:layout_width="match_parent"  4     android:layout_height="match_parent"  5     android:paddingBottom="@dimen/activity_vertical_margin"  6     android:paddingLeft="@dimen/activity_horizontal_margin"  7     android:paddingRight="@dimen/activity_horizontal_margin"  8     android:paddingTop="@dimen/activity_vertical_margin"  9     tools:context="com.example.recyclerviewtest.MainActivity" > 10  11     <android.support.v7.widget.RecyclerView 12         android:id="@+id/recyclier_view_horizontal" 13         android:layout_width="240dp" 14         android:layout_height="70dp" 15         android:layout_alignParentTop="true" 16         android:layout_centerHorizontal="true" 17         android:scrollbars="horizontal" > 18     </android.support.v7.widget.RecyclerView> 19  20     <android.support.v7.widget.RecyclerView 21         android:id="@+id/recyclier_view_vertical" 22         android:layout_width="match_parent" 23         android:layout_height="360dp" 24         android:layout_below="@id/recyclier_view_horizontal" 25         android:layout_centerHorizontal="true" 26         android:scrollbars="vertical" > 27     </android.support.v7.widget.RecyclerView> 28  29 </RelativeLayout>
activity_main.xml

其中一个item的布局如下:

关于RecyclerView中Viewholder和View的缓存机制的探究
 1 <?xml version="1.0" encoding="utf-8"?>  2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  3     android:layout_width="match_parent"  4     android:layout_height="match_parent"  5     android:gravity="center"  6     android:orientation="vertical" >  7   8     <TextView  9         android:id="@+id/rv_item_tv" 10         android:layout_width="60dp" 11         android:layout_height="20dp" 12         android:layout_gravity="center" 13         android:background="@drawable/item_selector" 14         android:clickable="true" 15         android:focusable="true" 16         android:focusableInTouchMode="true" 17         android:gravity="center" > 18     </TextView> 19  20 </LinearLayout>
recycler_view_item_layout_tv.xml

总之,测试的结论是这样的:在只有一种item的情况下,缓存的ViewHolder的数目为RecyclerView在滑动过程中所能在一屏内容纳的最大item个数+2。比如,在一个屏幕中只有item A可以显示,在滑动的过程最多可以出现6个item(这个最多是指所有item的个数,包括显示完全和显示不完全的总数),那么ViewHolder的缓存个数将会是8;而有至少两种item显示的情况下,每种item的ViewHolder的缓存个数为单种item在一屏内最大显示个数+1;比如,有3种item A,B,C,在滑动的过程,item A最多的情况下一屏显示了2个,那个item A对应的ViewHolder的缓存个数就是3个。

工程源文件可以从下面的链接打开下载。

点击下载源码

正文到此结束
Loading...