一步一步实现listview加载的性能优化

daxnight 8年前

来自: http://www.cnblogs.com/goagent/p/5158064.html

listview加载的核心是其adapter,本文针对listview加载的性能优化就是对adpter的优化,总共分四个层次:

0、最原始的加载

1、利用convertView

2、利用ViewHolder

3、实现局部刷新

[转载请保留本文地址:http://www.cnblogs.com/goagent/p/5158064.html] 

〇、最原始的加载

这里是不经任何优化的adapter,为了看起来方便,把listview的数据直接在构造函数里传给adapter了,代码如下:

 1     private class AdapterOptmL0 extends BaseAdapter {   2         private LayoutInflater mLayoutInflater;   3         private ArrayList<Integer> mListData;   4            5         public AdapterOptmL0(Context context, ArrayList<Integer> data) {   6             mLayoutInflater = LayoutInflater.from(context);   7             mListData = data;   8         }   9           10         @Override  11         public int getCount() {  12             return mListData == null ? 0 : mListData.size();  13         }  14   15         @Override  16         public Object getItem(int position) {  17             return mListData == null ? 0 : mListData.get(position);  18         }  19   20         @Override  21         public long getItemId(int position) {  22             return position;  23         }  24   25         @Override  26         public View getView(int position, View convertView, ViewGroup parent) {  27             View viewRoot = mLayoutInflater.inflate(R.layout.listitem, parent, false);  28             if (viewRoot != null) {  29                 TextView txt = (TextView)viewRoot.findViewById(R.id.listitem_txt);  30                 txt.setText(getItem(position) + "");  31             }  32             return viewRoot;  33         }  34     }

[转载请保留本文地址:http://www.cnblogs.com/goagent/p/5158064.html] 

一、利用 convertView

上述代码的第27行在Eclipse中已经提示警告:

Unconditional layout inflation from view adapter: Should use View Holder pattern (use recycled view passed into this method as the second parameter) for smoother scrolling

这个意思就是说,被移出可视区域的view是可以回收复用的,它作为getview的第二个参数已经传进来了,所以没必要每次都从xml里inflate。

经过优化后的代码如下:

 1     @Override   2     public View getView(int position, View convertView, ViewGroup parent) {   3         if (convertView == null) {   4             convertView = mLayoutInflater.inflate(R.layout.listitem, parent, false);   5         }   6         if (convertView != null) {   7             TextView txt = (TextView)convertView.findViewById(R.id.listitem_txt);   8             txt.setVisibility(View.VISIBLE);   9             txt.setText(getItem(position) + "");  10         }  11         return convertView;  12     }

上述代码加了判断,如果传入的convertView不为null,则直接复用,否则才会从xml里inflate。

按照上述代码,如果手机一屏最多同时显示5个listitem,则最多需要从xml里inflate 5 次,比AdapterOptmL0中每个listitem都需要inflate显然效率高多了。

上述的用法虽然提高了效率,但带来了一个 陷阱 , 如果复用convertView,则需要重置该view所有可能被修改过的属性 。

举个例子:

如果第一个view中的textview在getview中被设置成INVISIBLE了,而现在第一个view在滚动过程中出可视区域,并假设它作为参数传入第十个view的getview而被复用

那么,在第十个view的getview里面不仅要setText,还要重新setVisibility,因为这个被复用的view当前处于INVISIBLE状态!

[转载请保留本文地址:http://www.cnblogs.com/goagent/p/5158064.html] 

二、利用ViewHolder

从AdapterOptmL0第27行的警告中,我们还可以看到编译器推荐了一种模型叫ViewHolder,这是个什么东西呢,先看代码:

 1     private class AdapterOptmL2 extends BaseAdapter {   2         private LayoutInflater mLayoutInflater;   3         private ArrayList<Integer> mListData;   4            5         public AdapterOptmL2(Context context, ArrayList<Integer> data) {   6             mLayoutInflater = LayoutInflater.from(context);   7             mListData = data;   8         }   9           10         private class ViewHolder {  11             public ViewHolder(View viewRoot) {  12                 txt = (TextView)viewRoot.findViewById(R.id.listitem_txt);  13             }  14             public TextView txt;  15         }  16           17         @Override  18         public int getCount() {  19             return mListData == null ? 0 : mListData.size();  20         }  21   22         @Override  23         public Object getItem(int position) {  24             return mListData == null ? 0 : mListData.get(position);  25         }  26   27         @Override  28         public long getItemId(int position) {  29             return position;  30         }  31   32         @Override  33         public View getView(int position, View convertView, ViewGroup parent) {  34             if (convertView == null) {  35                 convertView = mLayoutInflater.inflate(R.layout.listitem, parent, false);  36                 ViewHolder holder = new ViewHolder(convertView);  37                 convertView.setTag(holder);  38             }  39             if (convertView != null && convertView.getTag() instanceof ViewHolder) {  40                 ViewHolder holder = (ViewHolder)convertView.getTag();  41                 holder.txt.setVisibility(View.VISIBLE);  42                 holder.txt.setText(getItem(position) + "");  43             }  44             return convertView;  45         }  46     }

从代码中可以看到,这一步做的优化是用一个类ViewHolder来保存listitem里面所有找到的子控件,这样就不用每次都通过耗时的findViewById操作了。

这一步的优化,在listitem布局越复杂的时候效果越为明显。

[转载请保留本文地址:http://www.cnblogs.com/goagent/p/5158064.html] 

三、实现局部刷新

OK,到目前为止,listview普遍需要的优化已经做的差不多了,那就该考虑实际使用场景中的优化需求了。

实际使用listview过程中,通常会在后台更新listview的数据,然后调用Adatper的notifyDataSetChanged方法来更新listview的UI。

那么问题来了,一般情况下,一次只会更新listview的一条/几条数据,而调用notifyDataSetChanged方法则会把所有可视范围内的listitem都刷新一遍,这是不科学的!

所以,进一步优化的空间在于,局部刷新listview,话不多说见代码: 

    private class AdapterOptmL3 extends BaseAdapter {          private LayoutInflater mLayoutInflater;          private ListView mListView;          private ArrayList<Integer> mListData;                    public AdapterOptmL3(Context context, ListView listview, ArrayList<Integer> data) {              mLayoutInflater = LayoutInflater.from(context);              mListView = listview;              mListData = data;          }                    private class ViewHolder {              public ViewHolder(View viewRoot) {                  txt = (TextView)viewRoot.findViewById(R.id.listitem_txt);              }              public TextView txt;          }                    @Override          public int getCount() {              return mListData == null ? 0 : mListData.size();          }            @Override          public Object getItem(int position) {              return mListData == null ? 0 : mListData.get(position);          }            @Override          public long getItemId(int position) {              return position;          }            @Override          public View getView(int position, View convertView, ViewGroup parent) {              if (convertView == null) {                  convertView = mLayoutInflater.inflate(R.layout.listitem, parent, false);                  ViewHolder holder = new ViewHolder(convertView);                  convertView.setTag(holder);              }              if (convertView != null && convertView.getTag() instanceof ViewHolder) {                  updateView((ViewHolder)convertView.getTag(), (Integer)getItem(position));              }              return convertView;          }                    public void updateView(ViewHolder holder, Integer data) {              if (holder != null && data != null) {                  holder.txt.setVisibility(View.VISIBLE);                  holder.txt.setText(data + "");              }          }                    public void notifyDataSetChanged(int position) {              final int firstVisiablePosition = mListView.getFirstVisiblePosition();              final int lastVisiablePosition = mListView.getLastVisiblePosition();              final int relativePosition = position - firstVisiablePosition;              if (position >= firstVisiablePosition && position <= lastVisiablePosition) {                  updateView((ViewHolder)mListView.getChildAt(relativePosition).getTag(), (Integer)getItem(position));              } else {                  //不在可视范围内的listitem不需要手动刷新,等其可见时会通过getView自动刷新              }          }      }

修改后的Adapter新增了一个方法 public void notifyDataSetChanged( int position) 可以根据position只更新指定的listitem。

[转载请保留本文地址:http://www.cnblogs.com/goagent/p/5158064.html] 

局部刷新番外篇

在局部刷新数据的接口中,实际上还可以再干点事情:listview正在滚动的时候不去刷新。

具体的思路是,如果当前正在滚动,则记住一个pending任务,等listview停止滚动的时候再去刷,这样不会造成滚动的时候刷新错乱。代码如下:

   1     private class AdapterOptmL3Plus extends BaseAdapter implements OnScrollListener{    2         private LayoutInflater mLayoutInflater;    3         private ListView mListView;    4         private ArrayList<Integer> mListData;    5             6         private int mScrollState = SCROLL_STATE_IDLE;    7         private List<Runnable> mPendingNotify = new ArrayList<Runnable>();    8             9         public AdapterOptmL3Plus(Context context, ListView listview, ArrayList<Integer> data) {   10             mLayoutInflater = LayoutInflater.from(context);   11             mListView = listview;   12             mListData = data;   13             mListView.setOnScrollListener(this);   14         }   15            16         private class ViewHolder {   17             public ViewHolder(View viewRoot) {   18                 txt = (TextView)viewRoot.findViewById(R.id.listitem_txt);   19             }   20             public TextView txt;   21         }   22            23         @Override   24         public int getCount() {   25             return mListData == null ? 0 : mListData.size();   26         }   27    28         @Override   29         public Object getItem(int position) {   30             return mListData == null ? 0 : mListData.get(position);   31         }   32    33         @Override   34         public long getItemId(int position) {   35             return position;   36         }   37    38         @Override   39         public View getView(int position, View convertView, ViewGroup parent) {   40             if (convertView == null) {   41                 convertView = mLayoutInflater.inflate(R.layout.listitem, parent, false);   42                 ViewHolder holder = new ViewHolder(convertView);   43                 convertView.setTag(holder);   44             }   45             if (convertView != null && convertView.getTag() instanceof ViewHolder) {   46                 updateView((ViewHolder)convertView.getTag(), (Integer)getItem(position));   47             }   48             return convertView;   49         }   50            51         public void updateView(ViewHolder holder, Integer data) {   52             if (holder != null && data != null) {   53                 holder.txt.setVisibility(View.VISIBLE);   54                 holder.txt.setText(data + "");   55             }   56         }   57            58         public void notifyDataSetChanged(final int position) {   59             final Runnable runnable = new Runnable() {   60                 @Override   61                 public void run() {   62                     final int firstVisiablePosition = mListView.getFirstVisiblePosition();   63                     final int lastVisiablePosition = mListView.getLastVisiblePosition();   64                     final int relativePosition = position - firstVisiablePosition;   65                     if (position >= firstVisiablePosition && position <= lastVisiablePosition) {   66                         if (mScrollState == SCROLL_STATE_IDLE) {   67                             //当前不在滚动,立刻刷新   68                             Log.d("Snser", "notifyDataSetChanged position=" + position + " update now");   69                             updateView((ViewHolder)mListView.getChildAt(relativePosition).getTag(), (Integer)getItem(position));   70                         } else {   71                             synchronized (mPendingNotify) {   72                                 //当前正在滚动,等滚动停止再刷新   73                                 Log.d("Snser", "notifyDataSetChanged position=" + position + " update pending");   74                                 mPendingNotify.add(this);   75                             }   76                         }   77                     } else {   78                         //不在可视范围内的listitem不需要手动刷新,等其可见时会通过getView自动刷新   79                         Log.d("Snser", "notifyDataSetChanged position=" + position + " update skip");   80                     }   81                 }   82             };   83             runnable.run();   84         }   85    86         @Override   87         public void onScrollStateChanged(AbsListView view, int scrollState) {   88             mScrollState = scrollState;   89             if (mScrollState == SCROLL_STATE_IDLE) {   90                 //滚动已停止,把需要刷新的listitem都刷新一下   91                 synchronized (mPendingNotify) {   92                     final Iterator<Runnable> iter = mPendingNotify.iterator();   93                     while (iter.hasNext()) {   94                         iter.next().run();   95                         iter.remove();   96                     }   97                 }   98             }   99         }  100   101         @Override  102         public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {  103         }  104     }    View Code