首页 > 系统 > Android > 正文

Listview加载的性能优化是如何实现的

2020-04-11 10:54:28
字体:
来源:转载
供稿:网友

在android开发中Listview是一个很重要的组件,它以列表的形式根据数据的长自适应展示具体内容,用户可以自由的定义listview每一列的布局,但当listview有大量的数据需要加载的时候,会占据大量内存,影响性能,这时候就需要按需填充并重新使用view来减少对象的创建。

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

0、最原始的加载

1、利用convertView

2、利用ViewHolder

3、实现局部刷新

、最原始的加载

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

private class AdapterOptmL extends BaseAdapter {private LayoutInflater mLayoutInflater;private ArrayList<Integer> mListData;public AdapterOptmL(Context context, ArrayList<Integer> data) {mLayoutInflater = LayoutInflater.from(context);mListData = data;}@Overridepublic int getCount() {return mListData == null ? : mListData.size();}@Overridepublic Object getItem(int position) {return mListData == null ? : mListData.get(position);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {View viewRoot = mLayoutInflater.inflate(R.layout.listitem, parent, false);if (viewRoot != null) {TextView txt = (TextView)viewRoot.findViewById(R.id.listitem_txt);txt.setText(getItem(position) + "");}return viewRoot;}}

一、利用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。

经过优化后的代码如下:

@Overridepublic View getView(int position, View convertView, ViewGroup parent) {if (convertView == null) {convertView = mLayoutInflater.inflate(R.layout.listitem, parent, false);}if (convertView != null) {TextView txt = (TextView)convertView.findViewById(R.id.listitem_txt);txt.setVisibility(View.VISIBLE);txt.setText(getItem(position) + "");}return convertView;}

上述代码加了判断,如果传入的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状态!

二、利用ViewHolder

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

private class AdapterOptmL extends BaseAdapter {

private LayoutInflater mLayoutInflater;private ArrayList<Integer> mListData;public AdapterOptmL(Context context, ArrayList<Integer> data) {mLayoutInflater = LayoutInflater.from(context);mListData = data;}private class ViewHolder {public ViewHolder(View viewRoot) {txt = (TextView)viewRoot.findViewById(R.id.listitem_txt);}public TextView txt;}@Overridepublic int getCount() {return mListData == null ? : mListData.size();}@Overridepublic Object getItem(int position) {return mListData == null ? : mListData.get(position);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic 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) {ViewHolder holder = (ViewHolder)convertView.getTag();holder.txt.setVisibility(View.VISIBLE);holder.txt.setText(getItem(position) + "");}return convertView;}}

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

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

三、实现局部刷新

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;}@Overridepublic int getCount() {return mListData == null ? 0 : mListData.size();}@Overridepublic Object getItem(int position) {return mListData == null ? 0 : mListData.get(position);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic 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。

局部刷新番外篇

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

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

private class AdapterOptmLPlus extends BaseAdapter implements OnScrollListener{private LayoutInflater mLayoutInflater;private ListView mListView;private ArrayList<Integer> mListData;private int mScrollState = SCROLL_STATE_IDLE;private List<Runnable> mPendingNotify = new ArrayList<Runnable>();public AdapterOptmLPlus(Context context, ListView listview, ArrayList<Integer> data) {mLayoutInflater = LayoutInflater.from(context);mListView = listview;mListData = data;mListView.setOnScrollListener(this);}private class ViewHolder {public ViewHolder(View viewRoot) {txt = (TextView)viewRoot.findViewById(R.id.listitem_txt);}public TextView txt;}@Overridepublic int getCount() {return mListData == null ? : mListData.size();}@Overridepublic Object getItem(int position) {return mListData == null ? : mListData.get(position);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic 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(final int position) {final Runnable runnable = new Runnable() {@Overridepublic void run() {final int firstVisiablePosition = mListView.getFirstVisiblePosition();final int lastVisiablePosition = mListView.getLastVisiblePosition();final int relativePosition = position - firstVisiablePosition;if (position >= firstVisiablePosition && position <= lastVisiablePosition) {if (mScrollState == SCROLL_STATE_IDLE) {//当前不在滚动,立刻刷新Log.d("Snser", "notifyDataSetChanged position=" + position + " update now");updateView((ViewHolder)mListView.getChildAt(relativePosition).getTag(), (Integer)getItem(position));} else {synchronized (mPendingNotify) {//当前正在滚动,等滚动停止再刷新Log.d("Snser", "notifyDataSetChanged position=" + position + " update pending");mPendingNotify.add(this);}}} else {//不在可视范围内的listitem不需要手动刷新,等其可见时会通过getView自动刷新Log.d("Snser", "notifyDataSetChanged position=" + position + " update skip");}}};runnable.run();}@Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) {mScrollState = scrollState;if (mScrollState == SCROLL_STATE_IDLE) {//滚动已停止,把需要刷新的listitem都刷新一下synchronized (mPendingNotify) {final Iterator<Runnable> iter = mPendingNotify.iterator();while (iter.hasNext()) {iter.next().run();iter.remove();}}}}@Overridepublic void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {}}

以上所述是针对Listview加载的性能优化是如何实现的全部叙述,希望对大家有所帮助。

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表