内存泄露--contentView缓存使用与ListView优化

引起Android内存泄露有很多种原因,下面罗列了一些问题,以后会一一解决

1、构造Adapter时没有使用缓存convertView(衍生出ListView优化问题)

2、查询数据库游标没有关闭

3、Activity中生命周期对象大于Activity生命周期(关于Application Context与Activity Context)

4、Bitmap对象不使用时没有recycle掉(这里还有其他解决方案)

今天说的是第一种:如何使用缓存来优化ListView

因为如果不使用缓存convertView的话,调用getView时每次都会重新创建View,这样之前的View可能还没有销毁,加之不断的新建View势必会造成内存泄露。

使用getView时有3方案:(1)没有使用convertView,(2)使用convertView,(3)使用convertView+静态类ViewHolder

我做了一个测试,代码在下面,创建2000个View,从0拉到最后,计算总共耗,同时显示GC释放内存的大小,三种测试的结果如下:

注:这里先说下 GC_EXTERNAL_ALLOC freed 7K, 18% free 11153K/13511K, external 1632K/1672K, paused 89ms 的意思

  在Dalvik中,为一个程序分配的内存要根据机型的不同而不同,一般为32M,而虚拟机会把这些内存分别分配给,JAVA使用的堆内存(heap)和Nativie使用的内存(external)(即虚拟机中通过JNI调用本地Nativie的类中malloc分配的内存,如Bitmap,java.nio.ByteBuffers)。不过两者不同共享,也就是说Native的内存不够用了,而JAVA内存够用时是不能向JAVA申请的,必须向虚拟机申请才行,当虚拟机无法分配的时候就会报OOM的错误

freed 7k:表示GC已经释放了7K的内存

18% free 11153K/13511K:表示JAVA使用的堆内存(对象存在于此),18% free表示当前剩余18%的堆内存(heap memory),11153K表示当前已用的堆内存,13511K表示堆内存总共大小(网上有些文章这部分弄错了,很多转载都是同一个)

external 1632K/1672K:1632K表示已用external memory,总共1672K external memory(注意:这个可能只存在于Android 3.0之前)

paused 89ms:这里其实包括了两部分,一个是在调用GC之前暂停的时间,一个是调用GC后基本完成时暂停的时间

详细可参考:http://stackoverflow.com/questions/4550757/android-logs-gc-external-alloc-gc-for-malloc

(1)没有使用convertView

  没有任何处理,不建议这样写。如果数据量少可以,但是如果列表项数据量很大的时候,会每次都重新创建View,设置资源,严重影响性能,所以从一开始就不要用这种方式

@Override
        public View getView(int position, View convertView, ViewGroup parent) {
            //Get a View that displays the data at the specified position in the data set.
            //开始计时,性能测试用nanoTime会更精确,因为它是纳秒级的
            long startTime = System.nanoTime();
            View item = mInflater.inflate(R.layout.list_item, null);
            ImageView img = (ImageView)item.findViewById(R.id.img);
            TextView title = (TextView)item.findViewById(R.id.title);
            TextView info = (TextView)item.findViewById(R.id.info);
            img.setImageResource(R.drawable.ic_launcher);
            title.setText("loulijun");
            info.setText("www.cnblogs.com/loulijun");

            //停止计时
            long endTime = System.nanoTime();
            //耗时
            long spendTime = (endTime - startTime);

            sumTime += spendTime;
            Log.d("GoogleIO", "position at:"+position+"--sumTime:"+String.valueOf(sumTime));
            return item;
        }

测试结果:

目前VM只为他们分配了5767K+518k的内存,而内存峰值是32M

刚开始时,而且heap memory只申请了5767K,已用内存3353K,注意数据大小的变化:耗时:167633055ns = 0.167633055秒

当拉到1000的时候,堆内存总计已经申请了9607K,已用内存7245K,明显已经比刚开始时要大了 ,耗时:3435241667ns=3.435241667秒

当拉到2000的时候,堆内存总计13511K,已用内存11153K,耗时:6660369835ns = 6.660369835秒

---------------------------我又创建了10000个ListView,测试后直到内存泄露,证明峰值却是是32M,而不使用convertView导致的内存泄露,当内存泄露时手机会提示force close,并将错误写入/data/anr/traces.txt中,你可以adb pull下来查看具体信息

(2)使用convertView后的测试数据(优化后)

  通过缓存convertView,convertView可以缓存可视范围内的convertView,当再次向下滑动时又开始更新View,这种利用缓存convertView的方式可以判断如果缓存中不存在View才创建View,如果已经存在可以利用缓存中的View,这样会减少很多View的创建,提升了性能

@Override
        public View getView(int position, View convertView, ViewGroup parent) {
            //Get a View that displays the data at the specified position in the data set.
            if(convertView == null)
            {
                convertView = mInflater.inflate(R.layout.list_item, null);
            }
            //开始计时,性能测试用nanoTime会更精确,因为它是纳秒级的
            long startTime = System.nanoTime();

            ImageView img = (ImageView)convertView.findViewById(R.id.img);
            TextView title = (TextView)convertView.findViewById(R.id.title);
            TextView info = (TextView)convertView.findViewById(R.id.info);
            img.setImageResource(R.drawable.ic_launcher);
            title.setText("loulijun");
            info.setText("www.cnblogs.com/loulijun");

            //停止计时
            long endTime = System.nanoTime();
            //耗时
            long spendTime = (endTime - startTime);

            sumTime += spendTime;
            Log.d("GoogleIO", "position at:"+position+"--sumTime:"+String.valueOf(sumTime));
            return convertView;
        }

测试数据我还是用2000吧,10000太大了(一万年太久,只争朝夕)

测试结果:

这次一直拉到最后明显比刚才流畅多了,而且GC释放内存的次数也明显少了很多,最后用的时间和当前使用的内存也小很多,优化后的确好多了

当position为1000的时候,附近没怎么调用GC,用时:213653551ns=0.213653551秒,额,差距有点大,上面到达1000时用时达到3.43秒之多。

当position为2000的时候,已用内存只有3068K,堆总共内存6215K,而且external memory是0K,用时:378326396ns = 0.378326396秒,性能差距如此之大,都有点不敢相信。也不知道这种方式对不对,如有不妥的地方,还希望大牛能给出正确回答

(3)使用contentView+静态类ViewHolder类

  通过convertView+ViewHolder来实现,ViewHolder就是一个静态类,使用 ViewHolder 的关键好处是缓存了显示数据的视图(View),加快了 UI 的响应速度。

当我们判断 convertView == null  的时候,如果为空,就会根据设计好的List的Item布局(XML),来为convertView赋值,并生成一个viewHolder来绑定converView里面的各个View控件(XML布局里面的那些控件)。再用convertView的setTag将viewHolder设置到Tag中,以便系统第二次绘制ListView时从Tag中取出。(看下面代码中)

如果convertView不为空的时候,就会直接用convertView的getTag(),来获得一个ViewHolder。

静态类ViewHolder

//定义静态类ViewHolder
    static class ViewHolder
    {
        public ImageView img;
        public TextView title;
        public TextView info;
    }

@Override
        public View getView(int position, View convertView, ViewGroup parent) {
            //Get a View that displays the data at the specified position in the data set.

            //开始计时,性能测试用nanoTime会更精确,因为它是纳秒级的
            long startTime = System.nanoTime();
            ViewHolder holder;

            if(convertView == null)
            {
                holder = new ViewHolder();
                convertView = mInflater.inflate(R.layout.list_item, null);
                holder.img = (ImageView)convertView.findViewById(R.id.img);
                holder.title = (TextView)convertView.findViewById(R.id.title);
                holder.info = (TextView)convertView.findViewById(R.id.info);
                convertView.setTag(holder);
            }else
            {
                holder = (ViewHolder)convertView.getTag();
                holder.img.setImageResource(R.drawable.ic_launcher);
                holder.title.setText("loulijun");
                holder.info.setText("www.cnblogs.com/loulijun");
            }

            //停止计时
            long endTime = System.nanoTime();
            //耗时
            long spendTime = (endTime - startTime);

            sumTime += spendTime;
            Log.d("GoogleIO", "position at:"+position+"--sumTime:"+String.valueOf(sumTime));
            return convertView;
        }

到这里,可能会有人问ViewHolder静态类结合缓存convertView与直接使用convertView有什么区别吗,是否重复了

在这里,官方给出了解释

提升Adapter的两种方法

To work efficiently the adapter implemented here uses two techniques:
-It reuses the convertView passed to getView() to avoid inflating View when it is not necessary

(译:重用缓存convertView传递给getView()方法来避免填充不必要的视图)
-It uses the ViewHolder pattern to avoid calling findViewById() when it is not necessary

(译:使用ViewHolder模式来避免没有必要的调用findViewById():因为太多的findViewById也会影响性能)
ViewHolder类的作用
-The ViewHolder pattern consists in storing a data structure in the tag of the view
returned by getView().This data structures contains references to the views we want to bind data to,
thus avoiding calling to findViewById() every time getView() is invoked

(译:ViewHolder模式通过getView()方法返回的视图的标签(Tag)中存储一个数据结构,这个数据结构包含了指向我们

要绑定数据的视图的引用,从而避免每次调用getView()的时候调用findViewById())

测试数据:(跟直接使用convertView数据相差不多)

当position为1000时,用时:199188216ns = 0.199188216秒,堆内存的时候也没比没有使用convertView理想的多

当position为2000时,用时:336669887ns = 0.336669887秒,比直接使用convertView的方式稍微好一点点,不过性能相差不多

时间: 2024-10-19 02:12:45

内存泄露--contentView缓存使用与ListView优化的相关文章

Android常见内存泄露,学会这六招优化APP性能

很多开发者都知道,在面试的时候会经常被问到内存泄露和内存溢出的问题. 1.内存溢出(Out Of Memory,简称 OOM),通俗理解就是内存不够,即内存占用超出内存的空间大小. 2.内存泄漏(Memory Leak),简单理解就是内存使用完毕之后本该垃圾回收却未被回收. 2 在正式了解内存泄露之前,首先来简单回顾一下 Java 内存分配策略. Java 程序运行时的内存分配策略有三种,分别是静态分配.栈式分配.堆式分配,对应的主要内存空间分别是静态存储区(也称方法区).栈区.堆区. 1.静态

listView优化以及内存泄露问题

最经开发app使出现了由于ListView产生的内存泄露问题.我们知道内存泄露时很不好的.意味着,代码写的有点失败,需要做些优化改动. 经过这次的教训,以及在网上找了些资料,总结了一下,关于ListView的优化: listview优化问题: 首先,listview必须严格按照convertView及viewHolder格式书写,这样可以基本保证数据最优. 其次,如果自定义Item中有涉及到图片等等的,一定要做图片优化.bitmap释放可以不做. 第三,尽量避免在BaseAdapter中使用st

Android 内存泄露与优化,以及MAT工具

一.介绍 Android机器中,内存使用问题一直是个十分重要,引人注目的问题,当我们代码编写不当,或者逻辑没处理好,就会导致机器运行缓慢,有时候甚至死机.对于程序员来说,这很致命,所以要去理解内存的使用,去避免内存的泄露,不断优化内存,而当出现内存泄露导致的问题,我们能够分析log,并且会用工具MAT. 二.什么场景会导致内存泄露 内存泄露其实就是占用内存的对象使用后没有被回收.在这种现象下,当java程序运行一段时间,占用的内存越来越大,导致该进程的内存占用达到Android为进程分配的内存使

Android加载数据ListView优化内存实现

问题分析 在移动端项目开发中,基于加载大数据量的原因,经常出现内存溢出的现象,那么减少内存占用,在真实开发中着实很重要,下面介绍下ListView加载数据量大时解决思路: 我们应该碰到过这样的情况,对于加载的listview,我们慢慢滑动数据时,系统加载数据没有问题,但是当加快滑动数据时,就会出现内存溢出的问题(这里不考虑分页加载方法).总结这是因为手机屏幕显示的原因,每次加载数据是有限的,慢慢滑动数据,在加载数据的同时,系统能有时间回收不用的内存,才不会暴露问题,但是,加快滑动,内存回收GC来

Chrome V8系列--浅析Chrome V8引擎中的垃圾回收机制和内存泄露优化策略[转]

V8 实现了准确式 GC,GC 算法采用了分代式垃圾回收机制.因此,V8 将内存(堆)分为新生代和老生代两部分. 一.前言 V8的垃圾回收机制:JavaScript使用垃圾回收机制来自动管理内存.垃圾回收是一把双刃剑,其好处是可以大幅简化程序的内存管理代码,降低程序员的负担,减少因 长时间运转而带来的内存泄露问题. 但使用了垃圾回收即意味着程序员将无法掌控内存.ECMAScript没有暴露任何垃圾回收器的接口.我们无法强迫其进 行垃圾回收,更无法干预内存管理 内存管理问题:在浏览器中,Chrom

内存泄露 Memory Leaks 内存优化【总结】

什么是内存泄露 内存管理一直是Java 所鼓吹的强大优点.开发者只需要简单地创建对象,而Java的垃圾收集器将会自动管理内存空间的分配和释放. 但在很多情况下,事情并不那么简单,在 Java程序中总是会频繁地发生内存泄露(Memory Leaks). 内存泄漏就是:当某些对象不再被应用程序所使用,但是由于仍然被引用而导致垃圾收集器不能释放他们. 或者说是:我们对某一内存空间使用完成后没有释放. 用白话来说就是:该回收的内存没被回收. 要理解这个定义,我们需要理解内存中的对象状态.下图展示了什么是

Java集群优化——必须了解的内存溢出与内存泄露

概念: 内存溢出 out of memory 是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory:比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出. 内存泄露 memory leak 是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光. 联系: memory leak会最终会导致out of memory,内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满

ListView优化-ViewHolder缓存

安卓开发中ListView控件是一个使用频率相当的高级控件,通常用于展示一系列相似度极高的数据,当数据量极大或布局相当复杂时,ListView的性能优化就显得非常重要.所以在开发中不但功能上要满足,而且在性能上也不能马虎.        使用ListView是一个标准的适配器设计模式,ListView把数据.XML布局和Adapter有极地结合在一起完成一系列的展示功能.在ListView的数据展示中,每次滑动ListView都会触发getView()方法的调用,并且XML定义的布局是在也是在适

Android性能优化第(二)篇---Memory Monitor检测内存泄露

上篇说了一些性能优化的理论部分,主要是回顾一下,有了理论,小平同志又讲了,实践是检验真理的唯一标准,对于内存泄露的问题,现在通过Android Studio自带工具Memory Monitor 检测出来.性能优化的重要性不需要在强调,但是要强调一下,我并不是一个老司机,嘿嘿!没用过这个工具的,请睁大眼睛.如果你用过,那么就不用在看这篇博客了. 先看一段会发生内存泄露的代码 public class UserManger { private static UserManger instance;