首页 > 代码库 > 关于BaseAdapter的使用及优化心得

关于BaseAdapter的使用及优化心得

 

  对于Android程序员来说,BaseAdapter肯定不会陌生,灵活而优雅是BaseAdapter最大的特点。开发者可以通过构造BaseAdapter并搭载到ListView或者GridView这类多控件布局上面,实现软件所需要的布局效果。同时,BaseAdapter也是适配器里面最基础的一个类,其他的例如SimpleAdapter、ArrayAdapter都是直接或者间接继承BaseAdapter,所以说学好BaseAdapter基本就熟练掌握了适配器的使用了。

  本文需要具备一些Android基础知识的前提下才能更好的理解,因为本文并不是介绍BaseAdapter的基本使用方法,如果对于BaseAdapter不了解可以去参考一下文档。在这里主要是介绍一下BaseAdapter的实现细节和怎么优化BaseAdapter的设计,从而节省我们宝贵的手机运行资源。对于老鸟高手来说,这可能是一个常识了,还是主要给那些像我以前一样刚刚入门的开发者的一点点参考,也希望高手来多多指正。

 话不多说,我们看一看某些社交软件的界面(网上找的。。。。。)

  这个列表项就是用ListView作为布局,搭载SimpleAdapter或者BaseAdapter作为数据源做成的界面效果。一般来说,很多软件的界面都运用这个方法来实现想要的布局效果。我在这里说明一下后面说到的名词,首先这是一个列表项,里面有各个数据项,例如图片+文件传输助手+test+时间就是一个数据项,它是一个View视图,这张图片里面一共就有8个数据项,由8个View视图按垂直方向排列而成。OK,我们开始步入正题。

  假设,我们还是用例子讲解比较好理解,所以呢我们假设这个列表里面一共有40个数据项,而作为手机界面的限制,它一次只能显示8个数据项,同时它的另外32个数据项是隐藏状态。而显示这8个数据项就需要8个View实例,也就是需要实例化8个View的子类来显示内容,此时系统就有8个View实例占据内存空间。那如果我们把手指往下一滑动呢,自然顶端的数据项就会被隐藏,而底端的数据项自然就显示出来,那么这时候需要在new一个新的View实例来吗?如果还是继续创建实例,那么系统内存空间就有9个View实例了,可是我们还是只需要8个View实例就可以在手机屏幕完全显示,而顶端的被隐藏的实例话是可以不存在的了。所以这么来说,这样就浪费了手机的运行资源了。

  为了解决这个问题,我们在BaseAdapter中可以对View进行重用,也就是将被隐藏的View改变为将要显示的View并返回出来。

  在BaseAdapter中,这个抽象方法非常重要,它就是根据position的值返回对象的数据项的View视图,所以我们主要也是围绕这个方法来达到效率上优化的目的。

   public View getView(int position, View convertView, ViewGroup parent) { return null; } 

  简单介绍一下这个函数的参数,position是ID索引值,也就是ListView的对应的数据项的序号,而convertView则是View视图复用的基础,如果为第一个页面的数据,它的值为null,所以我们可以根据convertView的值来决定我们是否要new一个View的实例出来

参见下面代码

 1 @Override 2 public View getView(int position, View convertView, ViewGroup parent) { 3   if(convertView == null){ 4     convertView = new TextView(context);//新建一个字符串控件 5     ((TextView)convertView).setText(position);//实现其是第几个数据项 6   } 7   else{ 8     ((TextView)convertView).setText(position);//重新修改其是第几个数据项 9   }10   return convertView;11 }

  为了简单起来,我这里的View只是一个简简单单的字符串控件,在代码中我们可以看到,先对convertView进行判断,如果其值为null,则说明该方法此时返回的View是第一页的视图,所以需要new出一个View的实例,然后对其进行设置显示的内容并返回(这里设置的内容为position)。如果其值不为null,则说明此时的View视图是可以复用其他已经隐藏的View实例,而convertView的索引就是这个隐藏的实例,自然我们不用重新创建一个新的View实例而直接对convertView进行重新设置数值并返回便可。

  这样就保证了程序里面只有足够界面显示的View实例的个数存在,而不会有多余的View来占用系统资源,由此也提高了一点点效率。

 

 

 

 

  这是BaseAdapter的效率优化的一个方面,接下来我们还要说一下另外一个方面,说明白了也就是一个小技巧来提高效率。对于一个BaseAdapter来说,显示的界面肯定不是仅仅的一两个字符串控件(不然我们就用ArrayAdapter了多简便),而一般是加载一个根据layout生成的布局控件,相对来说这个的布局控件会比较复杂,我们会对其的子控件进行获取并赋值返回,所以我们经常要调用findViewById()这个方法来或者子控件的引用。而findViewById()会比较消耗系统资源,频繁的调用在我们快速滑动界面时会加重手机的负担,所以我们也可以对这个地方进行一些小小的改进。

参考这个代码

 1 public class PersonAdapter extends BaseAdapter { 2     private List<Person> persons;//绑定的数据 3     private int resource;//绑定的Layout数据项界面 4     private LayoutInflater inflater;//布局填充服务 5      6     public PersonAdapter(Context context, List<Person> persons, int resource) { 7         this.persons = persons; 8         this.resource = resource; 9         inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);10     }11     @Override12     public int getCount() {13         return persons.size();14     }15     @Override16     public Object getItem(int position) {17         return persons.get(position);18     }19     @Override20     public long getItemId(int position) {21         return position;22     }23     @Override24     public View getView(int position, View convertView, ViewGroup parent) {25         TextView nameView = null;26         TextView phoneView = null;27         if(convertView == null){28             convertView = inflater.inflate(resource, null);//生成绑定数据的View界面29             nameView = (TextView) convertView.findViewById(R.id.name);30             phoneView = (TextView) convertView.findViewById(R.id.phone);31             32             ViewCache cache = new ViewCache();33             cache.nameView = nameView;34             cache.phoneView = phoneView;        35             convertView.setTag(cache);36         }else{37             ViewCache cache = (ViewCache) convertView.getTag();38             nameView = cache.nameView;39             phoneView = cache.phoneView;40         }41         Person person = persons.get(position);42         //下面代码实现数据绑定43         nameView.setText(person.getName());44         phoneView.setText(person.getPhone());45         46         return convertView;47     }48     /**49     *保存findViewById获得的子控件索引的内部类50     */51     private final class ViewCache{52         public TextView nameView;53         public TextView phoneView;54     }55 }

  重点还是放在getView()这个方法上。在代码里面,我写了一个ViewCache的私有内部类,主要用来保存要来复用的View所需要获得的子控件的索引值。稍微解释一下代码,如果convertView为null,则说明需要new出一个View实例,在这里就是调用Inflater这个系统服务根据resource这个界面的ID值来生成一个View实例,然后用findViewById这个方法来或者其中的子控件的值,并保存在内部类ViewCache中,放入Tag中,setTag(object)是可以放入一个任意的类来保存成为缓存,因为其是Object。最后设置View子控件的内容并返回。如果不为null呢,现在则需要重用View实例了,所以我们也顺带重用一下保存在Tag里面的ViewCache的类成员变量的子控件索引值,而不需要重新去一个一个调用findViewById()这个方法,在界面足够复杂时这样是可以节省很多系统资源的。

  这就是BaseAdapter提高效率的两种小技巧,如果大牛有其他更好的技巧,也希望能分享分享用来学习一下。

 

 

 

 

  另外也说一下SimplerAdapter使用遇到的一个小问题,当然也是对于新手来说的。就是关于调用notifyDataSetChanged()但是List里面的数据不更改的问题,其实也就是你的数据源更改出错了,例如下面代码它是不会改变数据的。

List<HashMap<String,Object>> datas = new ArrayList<HashMap<String,Object>>();datas = getData();//getData返回新的数据集adapter.notifyDataSetChanged();//通知数据更新显示,但是List是不会改变数据的显示的

因为在实例化这个Adapter时已经将数据源的引用传进去了,而现在只是修改了数据源data的引用让其重新指向另一个数据集合,所以notifyDataSetChanged()是没反应的,正确的方法应该是这样的。

List<HashMap<String,Object>> datas = new ArrayList<HashMap<String,Object>>();datas.clear();datas.addAll(getData());//getData返回新的数据集,返回类型为List<HashMap<String,Object>>adapter.notifyDataSetChanged();//通知数据更新显示,成功改变

  胡乱写了这篇博客,有不足之处还望各位指正。