您的位置:首页 > 移动开发 > Android开发

android中listview的性能优化

2014-07-20 23:57 295 查看
一、原理:Adapter在Android中占据一个重要的角色,它是数据和UI(View)之间一个重要的纽带。在常见的View(ListView,GridView)等地方都需要用到Adapter。如图1直观的表达了Data、Adapter、View三者的关系。





图1 Adapter、数据、UI三者关系(PS:此图来自Google I/O)

二、性能优化

先看一段示例代码:

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仅仅缓存了可视范围内的View,随后的滚动都是对这些View进行数据更新。不管你有多少数据,他都只用ArrayList缓存可视范围内的View,这样保证了性能。必须注意的是,ListView即缓存View结构,也缓存了数据。此外需要提醒的是这里也是用空间换时间的做法,View本身因为setTag而会占用更多的内存,还会增加代码量;而findViewById会临时消耗更多的内存,所以不可盲目使用,依实际情况而定。

执行程序,查看日志:






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






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






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






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






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

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

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

2.其次,像小马前面讲的,说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项中最恶心的,处理图片的方法大致有以下几种:

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

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

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

2.4:在getView中做图片转换时,产生的中间变量一定及时释放,用以下形式:


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

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

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

记下小马自己的错误:

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

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

6.2:在线程内部采用弱引用保存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 
        }


五、推荐文章
    Android,谁动了我的内存(1)

    Android 内存泄漏调试



内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: