ListView的原理

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加载数据的工作原理:
ListView的原理_第1张图片

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

如果你有几千几万甚至更多的选项(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;
    }
}

执行程序,查看日志:
ListView的原理_第2张图片

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

ListView的原理_第3张图片

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

ListView的原理_第4张图片

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

ListView的原理_第5张图片

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

ListView的原理_第6张图片

此时的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

你可能感兴趣的:(ListView)