Listview加载的性能优化是如何实现的
在android开发中Listview是一个很重要的组件,它以列表的形式根据数据的长自适应展示具体内容,用户可以自由的定义listview每一列的布局,但当listview有大量的数据需要加载的时候,会占据大量内存,影响性能,这时候就需要按需填充并重新使用view来减少对象的创建。
listview加载的核心是其adapter,本文针对listview加载的性能优化就是对adpter的优化,总共分四个层次:
0、最原始的加载
1、利用convertView
2、利用ViewHolder
3、实现局部刷新
〇、最原始的加载
这里是不经任何优化的adapter,为了看起来方便,把listview的数据直接在构造函数里传给adapter了,代码如下:
privateclassAdapterOptmLextendsBaseAdapter{
privateLayoutInflatermLayoutInflater;
privateArrayList<Integer>mListData;
publicAdapterOptmL(Contextcontext,ArrayList<Integer>data){
mLayoutInflater=LayoutInflater.from(context);
mListData=data;
}
@Override
publicintgetCount(){
returnmListData==null?:mListData.size();
}
@Override
publicObjectgetItem(intposition){
returnmListData==null?:mListData.get(position);
}
@Override
publiclonggetItemId(intposition){
returnposition;
}
@Override
publicViewgetView(intposition,ViewconvertView,ViewGroupparent){
ViewviewRoot=mLayoutInflater.inflate(R.layout.listitem,parent,false);
if(viewRoot!=null){
TextViewtxt=(TextView)viewRoot.findViewById(R.id.listitem_txt);
txt.setText(getItem(position)+"");
}
returnviewRoot;
}
}
一、利用convertView
上述代码的第27行在Eclipse中已经提示警告:
Unconditionallayoutinflationfromviewadapter:ShoulduseViewHolderpattern(userecycledviewpassedintothismethodasthesecondparameter)forsmootherscrolling
这个意思就是说,被移出可视区域的view是可以回收复用的,它作为getview的第二个参数已经传进来了,所以没必要每次都从xml里inflate。
经过优化后的代码如下:
@Override
publicViewgetView(intposition,ViewconvertView,ViewGroupparent){
if(convertView==null){
convertView=mLayoutInflater.inflate(R.layout.listitem,parent,false);
}
if(convertView!=null){
TextViewtxt=(TextView)convertView.findViewById(R.id.listitem_txt);
txt.setVisibility(View.VISIBLE);
txt.setText(getItem(position)+"");
}
returnconvertView;
}
上述代码加了判断,如果传入的convertView不为null,则直接复用,否则才会从xml里inflate。
按照上述代码,如果手机一屏最多同时显示5个listitem,则最多需要从xml里inflate5次,比AdapterOptmL0中每个listitem都需要inflate显然效率高多了。
上述的用法虽然提高了效率,但带来了一个陷阱,如果复用convertView,则需要重置该view所有可能被修改过的属性。
举个例子:
如果第一个view中的textview在getview中被设置成INVISIBLE了,而现在第一个view在滚动过程中出可视区域,并假设它作为参数传入第十个view的getview而被复用
那么,在第十个view的getview里面不仅要setText,还要重新setVisibility,因为这个被复用的view当前处于INVISIBLE状态!
二、利用ViewHolder
从AdapterOptmL0第27行的警告中,我们还可以看到编译器推荐了一种模型叫ViewHolder,这是个什么东西呢,先看代码:
privateclassAdapterOptmLextendsBaseAdapter{
privateLayoutInflatermLayoutInflater;
privateArrayList<Integer>mListData;
publicAdapterOptmL(Contextcontext,ArrayList<Integer>data){
mLayoutInflater=LayoutInflater.from(context);
mListData=data;
}
privateclassViewHolder{
publicViewHolder(ViewviewRoot){
txt=(TextView)viewRoot.findViewById(R.id.listitem_txt);
}
publicTextViewtxt;
}
@Override
publicintgetCount(){
returnmListData==null?:mListData.size();
}
@Override
publicObjectgetItem(intposition){
returnmListData==null?:mListData.get(position);
}
@Override
publiclonggetItemId(intposition){
returnposition;
}
@Override
publicViewgetView(intposition,ViewconvertView,ViewGroupparent){
if(convertView==null){
convertView=mLayoutInflater.inflate(R.layout.listitem,parent,false);
ViewHolderholder=newViewHolder(convertView);
convertView.setTag(holder);
}
if(convertView!=null&&convertView.getTag()instanceofViewHolder){
ViewHolderholder=(ViewHolder)convertView.getTag();
holder.txt.setVisibility(View.VISIBLE);
holder.txt.setText(getItem(position)+"");
}
returnconvertView;
}
}
从代码中可以看到,这一步做的优化是用一个类ViewHolder来保存listitem里面所有找到的子控件,这样就不用每次都通过耗时的findViewById操作了。
这一步的优化,在listitem布局越复杂的时候效果越为明显。
三、实现局部刷新
OK,到目前为止,listview普遍需要的优化已经做的差不多了,那就该考虑实际使用场景中的优化需求了。
实际使用listview过程中,通常会在后台更新listview的数据,然后调用Adatper的notifyDataSetChanged方法来更新listview的UI。
那么问题来了,一般情况下,一次只会更新listview的一条/几条数据,而调用notifyDataSetChanged方法则会把所有可视范围内的listitem都刷新一遍,这是不科学的!
所以,进一步优化的空间在于,局部刷新listview,话不多说见代码:
privateclassAdapterOptmL3extendsBaseAdapter{
privateLayoutInflatermLayoutInflater;
privateListViewmListView;
privateArrayList<Integer>mListData;
publicAdapterOptmL3(Contextcontext,ListViewlistview,ArrayList<Integer>data){
mLayoutInflater=LayoutInflater.from(context);
mListView=listview;
mListData=data;
}
privateclassViewHolder{
publicViewHolder(ViewviewRoot){
txt=(TextView)viewRoot.findViewById(R.id.listitem_txt);
}
publicTextViewtxt;
}
@Override
publicintgetCount(){
returnmListData==null?0:mListData.size();
}
@Override
publicObjectgetItem(intposition){
returnmListData==null?0:mListData.get(position);
}
@Override
publiclonggetItemId(intposition){
returnposition;
}
@Override
publicViewgetView(intposition,ViewconvertView,ViewGroupparent){
if(convertView==null){
convertView=mLayoutInflater.inflate(R.layout.listitem,parent,false);
ViewHolderholder=newViewHolder(convertView);
convertView.setTag(holder);
}
if(convertView!=null&&convertView.getTag()instanceofViewHolder){
updateView((ViewHolder)convertView.getTag(),(Integer)getItem(position));
}
returnconvertView;
}
publicvoidupdateView(ViewHolderholder,Integerdata){
if(holder!=null&&data!=null){
holder.txt.setVisibility(View.VISIBLE);
holder.txt.setText(data+"");
}
}
publicvoidnotifyDataSetChanged(intposition){
finalintfirstVisiablePosition=mListView.getFirstVisiblePosition();
finalintlastVisiablePosition=mListView.getLastVisiblePosition();
finalintrelativePosition=position-firstVisiablePosition;
if(position>=firstVisiablePosition&&position<=lastVisiablePosition){
updateView((ViewHolder)mListView.getChildAt(relativePosition).getTag(),(Integer)getItem(position));
}else{
//不在可视范围内的listitem不需要手动刷新,等其可见时会通过getView自动刷新
}
}
}
修改后的Adapter新增了一个方法publicvoidnotifyDataSetChanged(intposition)可以根据position只更新指定的listitem。
局部刷新番外篇
在局部刷新数据的接口中,实际上还可以再干点事情:listview正在滚动的时候不去刷新。
具体的思路是,如果当前正在滚动,则记住一个pending任务,等listview停止滚动的时候再去刷,这样不会造成滚动的时候刷新错乱。代码如下:
privateclassAdapterOptmLPlusextendsBaseAdapterimplementsOnScrollListener{
privateLayoutInflatermLayoutInflater;
privateListViewmListView;
privateArrayList<Integer>mListData;
privateintmScrollState=SCROLL_STATE_IDLE;
privateList<Runnable>mPendingNotify=newArrayList<Runnable>();
publicAdapterOptmLPlus(Contextcontext,ListViewlistview,ArrayList<Integer>data){
mLayoutInflater=LayoutInflater.from(context);
mListView=listview;
mListData=data;
mListView.setOnScrollListener(this);
}
privateclassViewHolder{
publicViewHolder(ViewviewRoot){
txt=(TextView)viewRoot.findViewById(R.id.listitem_txt);
}
publicTextViewtxt;
}
@Override
publicintgetCount(){
returnmListData==null?:mListData.size();
}
@Override
publicObjectgetItem(intposition){
returnmListData==null?:mListData.get(position);
}
@Override
publiclonggetItemId(intposition){
returnposition;
}
@Override
publicViewgetView(intposition,ViewconvertView,ViewGroupparent){
if(convertView==null){
convertView=mLayoutInflater.inflate(R.layout.listitem,parent,false);
ViewHolderholder=newViewHolder(convertView);
convertView.setTag(holder);
}
if(convertView!=null&&convertView.getTag()instanceofViewHolder){
updateView((ViewHolder)convertView.getTag(),(Integer)getItem(position));
}
returnconvertView;
}
publicvoidupdateView(ViewHolderholder,Integerdata){
if(holder!=null&&data!=null){
holder.txt.setVisibility(View.VISIBLE);
holder.txt.setText(data+"");
}
}
publicvoidnotifyDataSetChanged(finalintposition){
finalRunnablerunnable=newRunnable(){
@Override
publicvoidrun(){
finalintfirstVisiablePosition=mListView.getFirstVisiblePosition();
finalintlastVisiablePosition=mListView.getLastVisiblePosition();
finalintrelativePosition=position-firstVisiablePosition;
if(position>=firstVisiablePosition&&position<=lastVisiablePosition){
if(mScrollState==SCROLL_STATE_IDLE){
//当前不在滚动,立刻刷新
Log.d("Snser","notifyDataSetChangedposition="+position+"updatenow");
updateView((ViewHolder)mListView.getChildAt(relativePosition).getTag(),(Integer)getItem(position));
}else{
synchronized(mPendingNotify){
//当前正在滚动,等滚动停止再刷新
Log.d("Snser","notifyDataSetChangedposition="+position+"updatepending");
mPendingNotify.add(this);
}
}
}else{
//不在可视范围内的listitem不需要手动刷新,等其可见时会通过getView自动刷新
Log.d("Snser","notifyDataSetChangedposition="+position+"updateskip");
}
}
};
runnable.run();
}
@Override
publicvoidonScrollStateChanged(AbsListViewview,intscrollState){
mScrollState=scrollState;
if(mScrollState==SCROLL_STATE_IDLE){
//滚动已停止,把需要刷新的listitem都刷新一下
synchronized(mPendingNotify){
finalIterator<Runnable>iter=mPendingNotify.iterator();
while(iter.hasNext()){
iter.next().run();
iter.remove();
}
}
}
}
@Override
publicvoidonScroll(AbsListViewview,intfirstVisibleItem,intvisibleItemCount,inttotalItemCount){
}
}
以上所述是针对Listview加载的性能优化是如何实现的全部叙述,希望对大家有所帮助。