zoukankan      html  css  js  c++  java
  • android Adapter使用详解

    将此句说100遍, 你就会用了: 适配器的作用就是将数据绑定到条目界面的每一个显示控件上.
    ---------------------------------屎一样的分割线-------------------------------------
    1.自定义Adapter的时候的getview()方法遇到了类型转换异常.
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
    ImageView imageView = new ImageView(mContext);
    imageView.setLayoutParams(new GridView.LayoutParams(120, 120));
    imageView.setImageResource(data[position]);
    imageView.setScaleType(ScaleType.CENTER);
    return imageView;
    }
    红色部分, 要用GridView(如果你用GridView的话).不能直接导包导入ViewGroup的LayoutParams. 这样编译时不会报错, 但是运行时报这个傻X错误java.lang.ClassCastException: android.view.ViewGroup$LayoutParams.
    2. getView里的convertView参数有什么用?
    解决ListView滚动卡的问题

    写了个类似下面的GridView,滚动的时候有卡或者跳格的现象,尤其当记录比较多的时候。

    GridView和ListView机制原理是类似的,都是基于ListAdapter来处理View的控制的。在排查问题的时候也测试了用ListView替换GridView,问题依旧。

    实现的示例大致是这个样子:


    测试数据有600条左右。不过,即使减到40条左右,也是会卡的。在ddms log中监控dalvik日志,会有大量下面的信息:

    01-14 10:37:47.579: DEBUG/dalvikvm(13626): GC_EXTERNAL_ALLOC freed 58 objects / 2072 bytes in 37ms

    基本上每滚动一次,就会出10多条。这是造成卡的主要原因。也就是说,要释放大量的临时对象。

    这些临时对象还不是我自己创建的对象,我把结果集合已经一次性加载到内存中了。也不是图片显示造成的,我把图片去掉现象一样。

    另外,也考虑了比如扩大堆内存的方法,但是不对症下药,因为并不是内存达到堆的上限造成full gc。有关堆的日志可以过滤dalvikvm-heap看到。如果堆内存full gc会有类似这样的日志:

    dalvikvm-heap(11651): Heap Massage needed (768000-byte external allocation too big)
    dalvikvm-heap(11651): Full GC (don’t collect SoftReferences)

    可以初步判断,是GridView/ListView为实现滚屏产生的瞬态使用对象,随着滚动而被快速丢弃了,需要垃圾回收,造成性能问题。

    可是,为什么Android自带的通讯录却不卡呢?我的手机上的通讯录也有将近300条的记录。通过ddms日志观察,没有频繁的GC_EXTERNAL_ALLOC日志,从头滚动到尾只出现了1-2次。

    于是决定查看通讯录的源代码。这几行:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

    ……

    View v;
    if (convertView == null || convertView.getTag() == null) {
    newView = true;
    v = newView(mContext, cursor, parent);
    } else {
    newView = false;
    v = convertView;
    }

    说明了对ListView元素视图的复用。再查api:

    convertView The old view to reuse, if possible. Note: You should check that this view is non-null and of an appropriate type before using. If it is not possible to convert this view to display the correct data, this method can create a new view.

    因此,可以认为,convertView是元素的缓存,因为元素本身没有变化,因此可以判断如果非空,就可以复用。比如我示例中是这样写的:

    if (convertView == null) {
    convertView=activity.getLayoutInflater().inflate(
    R.layout.album_grid_element, parent, false);
    }

    之前是每次直接创建新的view的,没有考虑复用convertView。这是造成卡的根本原因。

    这次的另外的一个教训是,解决问题没有从api层面层层深入解决问题,即这样的次序:api是否能解决?是否需要通过自己的优化解决?是否需要系统的部署方面的配置比如堆的调整来解决等。

    结果是走了弯路,消耗了时间。

    以后还是要先通读api文档。这次出问题,是建立在以前随手写的简单示例基础上的。

    3.BaseAdapter中重写getview的心得以及发现convertView回收的机制


    下面先讲讲我遇到的几个问题:

    一.View getview(int position, View convertview, ViewGroup parent )中的第二个参数是什么含义;

    二.View的SetTag和getTag方法的用途;


    先来解决第一个问题:

    android SDK中这样讲参数 convertview :

    the old view to reuse, if possible. Note: You should check that this view is non-null and of an appropriate type before using.

    If it is not possible to convert this view to display the correct data, this method can create a new view.

    翻译:

    如果可以的话,这是旧View(这里不便翻译有的人翻成视图)的重用。 建议:在用之前,你应该检查这个View是不是非空,是不是一个合适的类型。

    如果不可能让这个VIew去显示一个恰当的数据,这个方法会创建一个新的View。


    如果我们要做的是一个ListView,在手机上显示的只有那么几条Item,而整个ListView可能有很长,可能是100条甚至是上万条,总不能让这么多条Item都驻留在内存中,所以android为你准备了一套机制,就是Recycler(反复循环器),他的具体工作原理可以到 http://www.cnblogs.com/xiaowenji/archive/2010/12/08/1900579.html去看。但是有些地方他没有讲清,所以我再讲一下。先把代码贴出来
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >
    <ListView
    android:id="@+id/result"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:cacheColorHint="#00000000" >
    </ListView>
    </LinearLayout>

    此处注意ListView的android:layout_height属性值为"fill_paternt",如果为“wrap_content"将会是另一种情况adapter的代码
    class ListViewAdapter extends BaseAdapter
    {
    private Context mContext;
    int i=0;
    public ListViewAdapter (Context context)
    {
    this.mContext=context;
    }
    @Override
    public int getCount()
    {
    return 30;

    }
    @Override
    public Object getItem(int position)
    {
    return position;
    }
    @Override
    public long getItemId(int position)
    {
    return 0;
    }
    @Override
    public View getView(int position, View convertView, ViewGroup parent)
    {
    System.out.println("getView " + position + " " + convertView);//调试语句
    Holder holder;
    if(null==convertView)
    {
    holder=new Holder();
    convertView=LayoutInflater.from(mContext).inflate(R.layout.textview, null); //mContext指的是调用的Activtty
    holder.textView=(TextView)convertView.findViewById(R.id.textview);
    convertView.setTag(holder);
    }
    else
    {
    holder=(Holder)convertView.getTag();
    }
    holder.textView.setText("position: "+position);
    return convertView;
    }
    class Holder
    {
    public TextView textView;

    }
    }
    运行程序之后发现屏幕上显示出的Item的convertview都为空,向下滑动新产生的Item的convetview都不为空。到此为止和上面链接中讲的是一致的,但是如果设置ListView的android:layout_height属性值为“wrap_content之后,发现只有第一个Item的convertview为null其他的不为空。虽然两种设置不同,结果也不同,但是convertview的机制没有变。其实到此为止我们可以总结出convertview的机制了,就是在初始显示的时候,每次显示一个item都调用一次getview方法但是每次调用的时候covertview为空(因为还没有旧的view),当显示完了之后。如果屏幕移动了之后,并且导致有些Item(也可以说是view)跑到屏幕外面,此时如果还有新的item需要产生,则这些item显示时调用的getview方法中的convertview参数就不是null,而是那些移出屏幕的view(旧view),我们所要做的就是将需要显示的item填充到这些回收的view(旧view)中去,最后注意convertview为null的不仅仅是初始显示的那些item,还有一些是已经开始移入屏幕但是还没有view被回收的那些item。最终我们用亲手写的代码实现了Recycler(反复循环器).第二个问题其实应该在第一个问题中嵌套来讲,但是为了思路清晰我分开了:view的setTag和getTag方法其实很简单,在实际编写代码的时候一个view不仅仅是为了显示一些字符串、图片,有时我们还需要他们携带一些其他的数据以便我们对该view的识别或者其他操作。于是android 的设计者们就创造了setTag(Object)方法来存放一些数据和view绑定,我们可以理解为这个是view 的标签也可以理解为view 作为一个容器存放了一些数据。而这些数据我们也可以通过getTag() 方法来取出来。到这里setTag和getTag大家应该已经明白了。再回到上面的话题,我们通过convertview的setTag方法和getTag方法来将我们要显示的数据来绑定在convertview上。如果convertview 是第一次展示我们就创建新的Holder对象与之绑定,并在最后通过return convertview 返回,去显示;如果convertview 是回收来的那么我们就不必创建新的holder对象,只需要把原来的绑定的holder取出加上新的数据就行了。至此我的问题讲完了,你的问题解决了么?


    关于adapter,现在理解得多了一点。有一类View叫做AdapterView,比如ListView就是AdapterView的一种。这类的View有一个setAdapter(Adapter adapter)方法,通过这个方法,来update View。
    Java代码
    ListView listView = (ListView)findViewById(R.layout.list_view);
    listView.setAdapter(new MyAdapter(this));
    上面的listView在初始化的时候是没有数据的,也就是没有ListItem,通过setAdapter()方法,才填充了数据。

    Adapter类的作用,就是根据数据源,来返回View,每个View最终会成为AdapterView的其中一个Child View。如同其名字一样,Adapter是一个数据到视图的适配器。可以将其理解为是一个容器的处理器,它将容器内的每一项,映射成一个View并返回。数据源可以是资源文件指定的数据集合,也可以来自SQLite,也可以是内存中的自定义对象……总之是一种数据对象的集合。而将要返回的View,是由Adapter中的getView()方法创建并返回的,可以来自layout文件的设置,也可以是自定义的View对象。


    搞清楚原理,就明白AdapterView和Adapter是怎样协同工作,生成页面的了。Adapter根据数据源,循环调用getView()方法,生成View对象的集合。然后AdapterView.setAdapter()方法,用前面生成的View对象集合,来给视图生成数据项。ListView只是AdapterView的一个子类,还有很多其他的AdapterView,但是原理都是相同的。有空的话可以看看ListAdapter类的getView()方法的源码,应该会对这个过程理解得更清晰一些。

    Dev Guide 上对于这个有专门的介绍:http://developer.android.com/guide/topics/ui/binding.html

    也可以看看它的子类到底有哪些:http://developer.android.com/reference/android/widget/AdapterView.html

    搞清楚了上一点,再来看ListActivity,其实ListActivity只是一个普通的Activity,只是它内置了一个ListView,以及提供了getListView()方法来获取内置的ListView;还提供了setListAdapter()方法,来给这个内置的ListView设置Adapter;以及诸如onListItemClick()等方法而已。在要使用ListActivity的地方,用普通的Activity也是完全可以的,只是要多写一些方法而已。

  • 相关阅读:
    Windows各系统关闭更新(winXP/win2003/win7/win8/win2012/win10)
    SSH框架搭建与整合
    Servlet转发和重定向response.sendRedirecte()区别 (转)
    el 表达式用法(转)
    数组和集合(collection)调用syso输出时,结果不一致问题
    Java 接口和抽象类可以被new么?
    Dbutils
    dbutils使用---QueryRunner(query_update)、BeanListBeanHandler、MapListMapHandler、ScalarHandler
    Hadoop参数:fs.defaultFS、 dfs.name.dir 、 dfs.data.dir
    工厂设计模式(三种)详解
  • 原文地址:https://www.cnblogs.com/olvo/p/2447109.html
Copyright © 2011-2022 走看看