ListView的原理

转载 2015年11月19日 14:49:48

Listview在项目开发使用率是非常高的,一直以来都只是会使用,今天突然想看看ListView和Adapter是怎么配合的。

列表的显示需要三个元素:

  • ListVeiw: 用来展示列表的View。
  • 适配器 : 用来把数据映射到ListView上
  • 数据: 具体的将被映射的字符串,图片,或者基本组件。

ListView 针对每个item,要求 adapter “返回一个视图” (getView),也就是说ListView在开始绘制的时候,系统要首先用getCount()函数得到要绘制的这个列表的长度,然后开始绘制第一行,怎么绘制呢?调用getView()函数。在这个函数里面首先获得一个View(这个看实际情况,如果是一个简单的显示则是View,如果是一个自定义的里面包含很多控件的时候它其实是一个ViewGroup),然后再实例化并设置各个组件及其数据内容并显示它。好了,绘制完这一行了。那 再绘制下一行,直到绘完为止。

getItem()getItemId()方法将会在调用ListView的响应方法的时候被调用到。所以要保证ListView的各个方法有效的话,这两个方法也得重写。比如:没有完成getItemId方法的功能实现的话,当调用ListView的getItemIdAtPosition方法时将会得不到想要的结果,因为该方法就是调用了对应的适配器的getItemId方法。

下图是ListView加载数据的工作原理:
这里写图片描述

下面简单说下上图的原理:

如果你有几千几万甚至更多的选项(item)时,其中只有可见的项目存在内存(内存内存哦,说的优化就是说在内存中的优化!!!)中,其他的在Recycler中
ListView先请求一个type1视图(getView)然后请求其他可见的项目。convertView在getView中是空(null)的
当item1滚出屏幕,并且一个新的项目从屏幕低端上来时,ListView再请求一个type1视图。convertView此时不是空值了,它的值是item1。你只需设定新的数据然后返回convertView,不必重新创建一个视图

代码块:

public class MultipleItemsList extends ListActivity {

    private MyCustomAdapter mAdapter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mAdapter = new MyCustomAdapter();
        for (int i = 0; i < 50; i++) {
            mAdapter.addItem("item " + i);
        }
        setListAdapter(mAdapter);
    }

    private class MyCustomAdapter extends BaseAdapter {

        private ArrayList mData = new ArrayList();
        private LayoutInflater mInflater;

        public MyCustomAdapter() {
            mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        }

        public void addItem(final String item) {
            mData.add(item);
            notifyDataSetChanged();
        }

        @Override
        public int getCount() {
            return mData.size();
        }

        @Override
        public String getItem(int position) {
            return mData.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            System.out.println("getView " + position + " " + convertView);
            ViewHolder holder = null;
            if (convertView == null) {
                convertView = mInflater.inflate(R.layout.item1, null);
                holder = new ViewHolder();
                holder.textView = (TextView)convertView.findViewById(R.id.text);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder)convertView.getTag();
            }
            holder.textView.setText(mData.get(position));
            return convertView;
        }

    }

    public static class ViewHolder {
        public TextView textView;
    }
}

执行程序,查看日志:
这里写图片描述

getView 被调用 9 次 ,convertView 对于所有的可见项目是空值(如下):

这里写图片描述

然后稍微向下滚动List,直到item10出现:

这里写图片描述

convertView仍然是空值,因为recycler中没有视图(item1的边缘仍然可见,在顶端)再滚动列表,继续滚动:

这里写图片描述

convertView不是空值了!item1离开屏幕到Recycler中去了,然后item11被创建,再滚动下:

这里写图片描述

此时的convertView非空了,在item11离开屏幕之后,它的视图(…0f8)作为convertView容纳item12了,好啦,结合以上原理,下面来看看今天最主要的话题,主角ListView的优化:

首先,这个地方先记两个ListView优化的一个小点:

  • ExpandableListView 与 ListActivity
    由官方提供的,里面要使用到的ListView是已经经过优化的ListView,如果大家的需求可以用Google自带的ListView满足的的话尽量用官方的,绝对没错!

  • ListView优化,其实并不是指其它的优化,就是内存是的优化,提到内存…(想到OOM,折腾了我不少时间),很多很多,先来写下,如果我们的ListView中的选项仅仅是一些简单的TextView的话,就好办啦,消耗不了多少的,但如果你的Item是自定义的Item的话,例如你的自定义Item布局ViewGroup中包含:按钮、图片、flash、CheckBox、RadioButton等一系列你能想到的控件的话, 你要在getView中单单使用文章开头提到的ViewHolder是远远不够的,如果数据过多,加载的图片过多过大,你BitmapFactory.decode的猛多的话,OOM搞死你,这个地方再警告下大家,是警告……….也提醒下自己:

小马碰到的问题大家应该也都碰到过的,自定义的ListView项乱序问题,我很天真的在getView()中强制清除了下ListView的缓存数据convertView,也就是convertView = null了,虽然当时是解决了这个问题让其它每次重绘,但是犯了大错了,如果数据太多的话,出现最最恶心的错,手机卡死或强制关机,关机啊哥哥们……O_O,客户杀了我都有可能,但大家以后别犯这样的错了,单单使用清除缓存convertView是解决不了实际问题的,继续……

下面来列举下真正意义上的优化吧:

  • ViewHolder Tag 必不可少,这个不多说!

  • 如果自定义Item中有涉及到图片等等的,一定要狠狠的处理图片,图片占的内存是ListView项中最恶心的,处理图片的方法大致有以下几种:

    • 不要直接拿个路径就去循环decodeFile();这是找死….用Option保存图片大小、不要加载图片到内存去;

    • 拿到的图片一定要经过边界压缩

    • 在ListView中取图片时也不要直接拿个路径去取图片,而是以WeakReference(使用WeakReference代替强引用。比如可以使
      用WeakReference
      mContextRef)、SoftReference、WeakHashMap等的来存储图片信息,是图片信息不是图片哦!

    • 在getView中做图片转换时,产生的中间变量一定及时释放

  • 尽量避免在BaseAdapter中使用static 来定义全局静态变量,我以为这个没影响,这个影响很大,static是Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例(比如Context的情况最多),这时就要尽量避免使用了..

  • 如果为了满足需求下必须使用Context的话:Context尽量使用ApplicationContext,因为Application的Context的生命周期比较长,引用它不会出现内存泄露的问题

  • 尽量避免在ListView适配器中使用线程,因为线程产生内存泄露的主要原因在于线程生命周期的不可控制

记下小马自己的错误:
之前使用的自定义ListView中适配数据时使用AsyncTask自行开启线程的,这个比用Thread更危险,因为Thread只有在run函数不结束时才出现这种内存泄露问题,然而AsyncTask内部的实现机制是运用了线程执行池(ThreadPoolExcutor,要想了解这个类的话大家加下我们的Android开发群五号,因为其它群的存储空间快满了,所以只上传到五群里了,看下小马上传的Gallery源码,你会对线程执行池、软、弱、强引用有个更深入的认识),这个类产生的Thread对象的生命周期是不确定的,是应用程序无法控制的,因此如果AsyncTask作为Activity的内部类,就更容易出现内存泄露的问题。这个问题的解决办法小马当时网上查到了记在txt里了,如下:

  • 将线程的内部类,改为静态内部类。

  • 在线程内部采用弱引用保存Context引用

示例代码如下:

public abstract class WeakAsyncTask<Params, Progress, Result, WeakTarget> extends
            AsyncTask<Params, Progress, Result> {
        protected WeakReference<WeakTarget> mTarget;  

        public WeakAsyncTask(WeakTarget target) {
            mTarget = new WeakReference<WeakTarget>(target);
        }  

        /** {@inheritDoc} */
        @Override
        protected final void onPreExecute() {
            final WeakTarget target = mTarget.get();
            if (target != null) {
                this.onPreExecute(target);
            }
        }  

        /** {@inheritDoc} */
        @Override
        protected final Result doInBackground(Params... params) {
            final WeakTarget target = mTarget.get();
            if (target != null) {
                return this.doInBackground(target, params);
            } else {
                return null;
            }
        }  

        /** {@inheritDoc} */
        @Override
        protected final void onPostExecute(Result result) {
            final WeakTarget target = mTarget.get();
            if (target != null) {
                this.onPostExecute(target, result);
            }
        }  

        protected void onPreExecute(WeakTarget target) {
            // No default action
        }  

        protected abstract Result doInBackground(WeakTarget target, Params... params);  

        protected void onPostExecute(WeakTarget target, Result result) {
            // No default action
        }
    }

转载:http://www.xuanyusong.com/archives/1252

解析Android ListView工作原理及其缓存机制

这一篇对郭大神的《Android ListView工作原理完全解析,带你从源码的角度彻底理解》博客学习总结。...
  • Libmill
  • Libmill
  • 2015年11月04日 22:10
  • 4658

Android ListView下拉/上拉刷新:设计原理与实现

 《Android ListView下拉/上拉刷新:设计原理与实现》 Android上ListView的第三方开源的下拉刷新框架很多,应用场景很多很普遍,几乎成为现在APP的通用设计典范,甚至...
  • zhangphil
  • zhangphil
  • 2015年07月24日 09:45
  • 5491

ListView 下拉刷新的原理解析

1. 流程分析     下拉刷新最主要的流程是:     (1). 下拉,显示提示头部界面(HeaderView),这个过程提示用户"下拉刷新"     (2). 下拉到一定程度,超出了刷新最基...
  • chenqiuping_ls
  • chenqiuping_ls
  • 2017年02月09日 11:52
  • 435

Android ListView工作原理完全解析,带你从源码的角度彻底理解

在Android所有常用的原生控件当中,用法最复杂的应该就是ListView了,它专门用于处理那种内容元素很多,手机屏幕无法展示出所有内容的情况。ListView可以使用列表的形式来展示内容,超出屏幕...
  • sinyu890807
  • sinyu890807
  • 2015年07月23日 09:09
  • 101029

Listview绘制原理 与adapter

Listview绘制原理 与adapter   ListView绘制的过程如下: 首先,系统在绘制ListView之前,将会先调用getCount方法来获取Item的个数。 之后每绘制一个It...
  • wangwei890702
  • wangwei890702
  • 2013年06月26日 16:30
  • 3681

深入了解ListView工作原理(一) -- 之viewHolder优化篇

看到题目,大家可能不禁要问,在网上一搜满大街的是讲ListView工作原理的,还再写一篇这个干吗?不急,没有一点新意,我是绝对不会写重复的东西,若非精品,分享也没有意义。所以,在这里保证,只要耐心看完...
  • qingtiantianqing
  • qingtiantianqing
  • 2016年02月26日 17:44
  • 1644

ListView中convertView和ViewHolder的复用机制工作原理

我们都知道在getView方法中的操作是这样的:先从xml中创建view对象(inflate操作,我们采用了重用convertView方法优化),然后在这个view去findViewById,找到每一...
  • dxcami
  • dxcami
  • 2016年04月09日 21:47
  • 1873

RecycleBin原理解析,带你领会ListView的View重用机制

ListView无疑是Android开发中使用最多的组件之一了,可以肯定是99%以上的应用中都是用了ListView,不过ListView也不是万能的,很多时候你会觉得ListView提供给我们的功能...
  • qinjunni2014
  • qinjunni2014
  • 2015年05月12日 00:42
  • 1206

ListView刷新原理及实现

ListView刷新原理及实现ListView优化方式 界面缓存:ViewHolder+convertView 分页加载:上拉刷新 图片缓存 快速滑动ListView禁止刷新 效果上拉加载更多原理及实...
  • wgyscsf
  • wgyscsf
  • 2016年05月17日 17:54
  • 1625

ListView乱谈之ListView的滚动原理

简单的分析了ListView的滚动原理
  • chunqiuwei
  • chunqiuwei
  • 2015年09月26日 22:30
  • 2789
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:ListView的原理
举报原因:
原因补充:

(最多只允许输入30个字)