文章摘自: http://www.eoeandroid.com/thread-211498-1-1.html  

原文:http://www.cnblogs.com/loulijun/archive/2012/04/10/2437888.html

引起android内存泄漏的原因很多,下面罗列一些原因:

1.使用BaseAdapter自定义ListView的适配器的时候没有使用convertView对ListView的View对象进行优化(衍生出了listView的优化与内存的问题)

2.使用SQLite数据库保存数据的时候,使用完毕之后没有关闭游标指针,浪费了大量的内存在Curse对象

3.Activty生命周期对象大于Activity的生命周期<Activity>Appliaction>即Activity关闭之后,没有销毁Activity中创建的对象

4.加载图片的时候,使用的Bitmap对象没有释放,Bitmap是将图片读取到内存中,所以一般占用比较大的内存,使用不当的话,很容易造成ANR(Application Not Response)


首先我们对第一个内存的问题进行讨论:使用convertView对ListView进行优化

因为如果不使用缓存convertView的话,调用getView时每次都会重新创建View,这样之前的View可能还没有销毁,加之不断的新建View势必会造成内存泄露。
使用getView时有3方案:(1)没有使用convertView,(2)使用convertView,(3)使用convertView+静态类ViewHolder


使用测试程序,对上面的三种情况进行计时计算,在ListView的项item比较少的时候,将看不出来明显的时间差异,所以测试的时候,将item加载的比较多的时候,来看看测试不同方式所使用的时间的差距 。选择的Item的项为0-2000项<从第0项一直下拉到第2000项>计算总共的时耗,同时显示GC释放的内存的大小

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

在Dalvik中,为一个应用程序(Application)分配的内存一般是32M,但有时候也得根据机型的不同,分配稍微有些差异,JAVA使用的堆内存(Heap Memory)和Native使用的(external 即虚拟机中通过JNI调用本地的Native类中malloc分配的内存如Bitmap,java.nio.ByteBuffers)不过两者不同共享,也就是说当Native的内存不够用的时候,而Java内存够用的时候,不能够向Java申请内存的,他们俩属于不用的内存模块,必须向虚拟机申请内存,当虚拟机的内存出现不够的情况下就会出现OOM(out of memory)

free 7K表示已经释放了7K的内存

18%free 11153K/13511K:表示Java使用的堆内存(对象存在于此),18% free表示当前剩余18&的内存(heap memory )11153K表示当前已经用了的堆内存,13511K表示总共堆内存的大小

external 1632K/1672K 1632K表示已用的external memory,总共1672K external memory(注意,这些可能只存在Android3.0之前)

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

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

(没有使用convertView)

没有使用任何的处理,不建议这样写,如果数据量比较少的情况下还算可行,但如果列表项的数据量很大的情况下,回调方法getView在每次滑动的时候都会调用,产生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的时候,堆内存已经申请了9067K,已用内存7245K 明显比刚开始的时候要大了,耗时:167633055ns = 0.167633055秒

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


当又创建1000个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为两千的时候,已用内存只有3068K,堆内存只用了6215K,而且external memory是0K,用时:378326396ns = 0.378326396秒,性能差距如此之大,都有点不敢相信。也不知道这种方式对不对,如有不妥的地方,还希望大牛能给出正确回答

<3>使用ConverView+ViewHolder静态类

通过ConvertIView+ViewHolder类来实现,ViewHolder就是一个静态类,使用静态类的好处关键在于缓存了显示数据的视图(View),加快了View的响应速度,当我们判断ConvertView==null的时候,如果为空,就会根据设计好的List加载Item布局(XML)

静态类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的方式稍微好一点点,不过性能相差不多



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

更多相关文章

  1. Android使用FFmpeg(一)--编译ffmpeg
  2. Android系统在超级终端下必会的命令大全(adb shell命令大全)
  3. 强制 Android(安卓)应用使用某个 Locale语言
  4. Android定时器的使用,
  5. Android常用动画alpha和rotate同时使用
  6. 检测Android本地存储是否有敏感信息
  7. Android下使用Socket连接网络电脑
  8. RK3326 Android(安卓)8.1 为自己的APK的服务默认开启无障碍权限,
  9. android Alertdialog.Bulider简单使用

随机推荐

  1. Android播放ogg声音文件
  2. Android(安卓)高亮指示层提示
  3. Android(安卓)反射获取私有方法,成员变量
  4. Android(安卓)图表--MPAndroidChart
  5. android 服务与多线程
  6. Service
  7. Android之Http请求基础
  8. android camera preview方向
  9. Android创建文件夹及文件并写入数据
  10. Android(安卓)杀掉自己进程的方法