listView优化以及内存泄露问题

最经开发app使出现了因为ListView产生的内存泄露问题。我们知道内存泄露时非常不好的。

意味着。代码写的有点失败。须要做些优化修改。

经过这次的教训,以及在网上找了些资料。总结了一下。关于ListView的优化:

listview优化问题:
首先。listview必须严格依照convertView及viewHolder格式书写,这样能够基本保证数据最优。

其次,假设自己定义Item中有涉及到图片等等的,一定要做图片优化。bitmap释放能够不做。

第三。尽量避免在BaseAdapter中使用static 来定义全局静态变量,这个影响非常大。static是Java中的一个keyword。

当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用static修饰的变量,它的生命周期是
非常长的。假设用它来引用一些资源耗费过多的实例(比方Context的情况最多),这时就要尽量避免使用了..

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

最后,假设上述你都做到的话。你的listview已经优化的非常好了。针对你的问题,你的listview控件高度是否
设置为fill_parent。由于wrap会导致listview滑动中无限计算自身高度。你的文本载入是否做过线程以及多
次反复载入的问题处理。你的item中变量是否多次无限生成新的内存对象等等。



我们经常在做项目过程中遇到内存溢出的问题,同一时候面试中关于OOM的问题也经常出现。
这里。我将前辈们解决Andorid内存溢出的方法又一次整理一番,方便自己以后使用。

一、Android的内存机制

android应用层是由java开发的,android的davlik虚拟机与jvm也类似。仅仅只是它是基于寄存器的。在java中,通过new为对象分配内存,全部对象在java堆内分配空间。而内存的释放是由垃圾收集器(GC)来回收的。 Java採用了有向图的原理。

Java将引用关系考虑为图的有向边,有向边从引用者指向引用对象。线程对象能够作为有向图的起始顶点。该图就是从起始顶点(GC roots)開始的一棵树,根顶点能够到达的对象都是有效对象。GC不会回收这些对象。假设某个对象 (连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们觉得这个(这些)对象不再被引用。能够被GC回收。


二、Android的内存溢出原因

1、内存泄露导致

因为我们程序的失误。长期保持某些资源(如Context)的引用,垃圾回收器就无法回收它,当然该对象占用的内存就无法被使用,这就造成内存泄露。



Android 中常见就是Activity被引用在调用finish之后却没有释放,第二次打开activity又又一次创建,这种内存泄露不断的发生,则会导致内存的溢出。

Android的每一个应用程序都会使用一个专有的Dalvik虚拟机实例来执行。它是由Zygote服务进程孵化出来的,也就是说每一个应用程序都是在属于自己的进程中执行的。Android为不同类型的进程分配了不同的内存使用上限。假设程序在执行过程中出现了内存泄漏的而造成应用进程使用的内存超过了这个上限。则会被系统视为内存泄漏,从而被kill掉。这使得只自己的进程被kill掉,而不会影响其它进程.

2、占用内存较多的对象

保存了多个耗用内存过大的对象(如Bitmap)或载入单个超大的图片。造成内存超出限制。

 

三、常见的内存泄漏问题及其解决方式


1、引用没释放造成的内存泄露

1.1注冊没取消造成的内存泄露

 这样的Android的内存泄露比纯Java的内存泄漏还要严重,由于其它一些Android程序可能引用系统的Android程序的对象(比方注冊机制)。即使Android程序已经结束了。可是别的应用程序仍然还有对Android程序的某个对象的引用。泄漏的内存依旧不能被垃圾回收。

1.2集合中对象没清理造成的内存泄露

我们通常把一些对象的引用增加到了集合中,当我们不须要该对象时,并没有把它的引用从集合中清理掉,这样这个集合就会越来越大。假设这个集合是static的话,那情况就更严重了。

1.3 static

 static是Java中的一个keyword。当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。

    private static ActivitymContext;       //省略

怎样才干有效的避免这样的引用的发生呢?

    第一,应该尽量避免static成员变量引用资源耗费过多的实例。比方Context。



    第二、Context尽量使用ApplicationContext,由于Application的Context的生命周期比較长,引用它不会出现内存泄露的问题。

    看使用的周期是否在activity周期内,假设超出,必须用application;常见的情景包含:AsyncTask。Thread。第三方库初始化等等。
    还有些情景,仅仅能用activity:比方。对话框,各种View,须要startActivity的等。
    总之,尽可能使用Application。



    第三、使用WeakReference取代强引用。

比方能够使用WeakReference<Context>mContextRef;

1.4、线程(内部类的使用)

线程产生内存泄露的主要原因在于线程生命周期的不可控。假设我们的线程是Activity的内部类,所以MyThread中保存了Activity的一个引用。当MyThread的run函数没有结束时。MyThread是不会被销毁的,因此它所引用的老的Activity也不会被销毁,因此就出现了内存泄露的问题。



假设非静态内部类的方法中,有生命周期大于其所在类的。那就有问题了。比方:AsyncTask、Handler。这两个类都是方便开发人员运行异步任务的,可是,这两个都跳出了Activity/Fragment的生命周期。


1、
解决方式

    第一、将线程的内部类,改为静态内部类。

       原因:

由于非静态内部类会自己主动持有一个所属类的实例,假设所属类的实例已经结束生命周期。但内部类的方法仍在运行,就会hold其主体(引用)。也就使主体不能被释放,亦即内存泄露。静态类编译后和非内部类是一样的,有自己独立的类名。不会悄悄引用所属类的实例,所以就不easy泄露。

    第二、假设须要引用Acitivity。使用弱引用。

 

2、资源对象没关闭造成的内存泄露

资源性对象比方(Cursor,File文件等)往往都用了一些缓冲。我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。而不是等待GC来处理。它们的缓冲不仅存在于java虚拟机内,还存在于java虚拟机外。假设我们不过把它的引用设置为null,而不关闭它们。往往会造成内存泄露。

由于有些资源性对象,比方SQLiteCursor(在析构函数finalize(),假设我们没有关闭它,它自己会调close()关闭),假设我们没有关闭它。系统在回收它时也会关闭它,可是这种效率太低了。

并且android数据库中对Cursor资源的是又限制个数的,假设不及时close掉,会导致别的地方无法获得。

3、一些不良代码成内存压力

有些代码并不造成内存泄露。可是它们,或是对没使用的内存没进行有效及时的释放,或是没有有效的利用已有的对象而是频繁的申请新内存,对内存的回收和分配造成非常大影响的,easy迫使虚拟机不得不给该应用进程分配很多其它的内存,造成不必要的内存开支。

3.1 Bitmap没调用recycle()

Bitmap对象在不使用时,我们应该先调用recycle()释放内存,然后才设置为null.

尽管recycle()从源代码上看,调用它应该能马上释放Bitmap的主要内存。可是測试结果显示它并没能马上释放内存。可是我猜应该还是能大大的加速Bitmap的主要内存的释放。

3.2 构造Adapter时。没有使用缓存的 convertView

 以构造ListView的BaseAdapter为例,在BaseAdapter中提共了方法:

public View getView(int position, View convertView, ViewGroup parent)

来向ListView提供每个item所须要的view对象。

初始时ListView会从BaseAdapter中依据当前的屏幕布局实例化一定数量的view对象,同一时候ListView会将这些view对象缓存起来。

当向上滚动ListView时,原先位于最上面的list item的view对象会被回收,然后被用来构造新出现的最以下的list item。这个构造过程就是由getView()方法完毕的,getView()的第二个形參 View convertView就是被缓存起来的list item的view对象(初始化时缓存中没有view对象则convertView是null)。



    由此能够看出。假设我们不去使用convertView,而是每次都在getView()中又一次实例化一个View对象的话,即浪费时间,也造成内存垃圾,给垃圾回收添加压力。假设垃圾回收来不及的话。虚拟机将不得不给该应用进程分配很多其它的内存。造成不必要的内存开支。

 

四、占用内存较多的对象(图片过大)造成内存溢出及其解决方式

由于Bitmap占用的内存实在是太多了。特别是分辨率大的图片。假设要显示多张那问题就更显著了。

Android分配给Bitmap的大小仅仅有8M。

方法1:等比例缩小图片

有时候。我们要显示的区域非常小,没有必要将整个图片都载入出来,而仅仅须要记载一个缩小过的图片,这时候能够设置一定的採样率。那么就能够大大减小占用的内存。

1
    BitmapFactory.Options options = new BitmapFactory.Options();
2
    options.inSampleSize = 2;//图片宽高都为原来的二分之中的一个,即图片为原来的四分之中的一个
尽量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource来设置一张大图,
由于这些函数在完毕decode后,终于都是通过java层的createBitmap来完毕的,须要消耗很多其它内存。



因此,改用先通过BitmapFactory.decodeStream方法,创建出一个bitmap,再将其设为ImageView的 source,
decodeStream最大的秘密在于其直接调用JNI>>nativeDecodeAsset()来完毕decode,
无需再使用java层的createBitmap,从而节省了java层的空间。



方法2:对图片採用软引用。及时地进行recycle()操作

尽管,系统可以确认Bitmap分配的内存终于会被销毁,可是因为它占用的内存过多,所以非常可能会超过java堆的限制。因此。在用完Bitmap时,要及时的recycle掉。recycle并不能确定马上就会将Bitmap释放掉,可是会给虚拟机一个暗示:“该图片可以释放了”。

    SoftReference<Bitmap> bitmap;
                   bitmap = new SoftReference<Bitmap>(pBitmap);
      if(bitmap != null){
              if(bitmap.get() != null && !bitmap.get().isRecycled()){
                  bitmap.get().recycle();
                  bitmap = null;
              }
          }


方法3: 单个页面。横竖屏切换N次后 OOM

1. 看看页面布局其中有没有大的图片。比方背景图之类的。

去除xml中相关设置,改在程序中设置背景图(放在onCreate()方法中):

1
    Drawable bg = getResources().getDrawable(R.drawable.bg);
2
     XXX.setBackgroundDrawable(rlAdDetailone_bg);


在Activity destory时注意,bg.setCallback(null); 防止Activity得不到及时的释放。



2. 跟上面方法相似。直接把xml配置文件载入成view 再放到一个容器里。然后直接调用 this.setContentView(View view);避免xml的反复载入。

 

方法4:在页面切换时尽可能少地反复使用一些代码。比方:反复调用数据库,反复使用某些对象等等.....

 

方法5:Android堆内存也能够自定义大小和优化Dalvik虚拟机的内存

--參考资料:http://blog.csdn.net/wenhaiyan/article/details/5519567

    注意:若使用这样的方法:project build target 仅仅能选择 <= 2.2 版本号。否则编译将通只是。

所以不建议用这样的方式。

五、Android中内存泄露监測

内存监測工具 DDMS --> Heap


用法比較简单:

·        选择DDMS视图,并打开Devices视图和Heap视图

·        点击选择要监控的进程,比方:上图中我选择的是system_process

·        选中Devices视图界面上的"update heap" 图标

·        点击Heap视图中的"Cause GC" button(相当于向虚拟机发送了一次GC请求的操作)

在Heap视图中选择想要监控的Type。一般我们会观察dataobject的 total size的变化。正常情况下total size的值会稳定在一个有限的范围内。也就说程序中的代码良好,没有造成程序中的对象不被回收的情况。

假设代码中存在没有释放对象引用的情况。那么data object的total size在每次GC之后都不会有明显的回落。随着操作次数的添加而total size也在不断的添加。(说明:选择好data object后。不断的操作应用,这样才干够看出total size的变化)。假设totalsize确实是在不断添加而没有回落。说明程序中有没有被释放的资源引用。那么我们应该怎么来定位呢?

Android中内存泄露定位

通过DDMS工具能够推断应用程序中是否存在内存泄漏的问题。那又怎样定位到详细出现故障的代码片段,终于找到问题所在呢?内存分析工具MAT Memory Analyzer Tool攻克了这一难题。MAT工具是一个Eclipse 插件,同一时候也有单独的RCP client,MAT工具的解析文件是.hprof,这个文件存放了某进程的内存快照。MAT工具定位内存泄漏详细位置的方法例如以下:

   ① 生成.hprof文件。Eclipse中生成.hprof文件的方法有非常多。不同Android版本号中生成.hprof的方式也稍有区别,但它们总体思路是一样的。我们在DDMS界面选中想要分析的应用进程,在Devices视图界面上方的一行图标button中,同一时候选中“Update Heap”和“Dump HPROF file”两个button,这时DDMS将会自己主动生成当前选中进程的.hprof文件。

   ② 将.hprof 文件导入到MAT工具中,MAT工具会自己主动解析并生成报告,点击“Dominator Tree”button。并按包分组,选择已定义的包类点右键。在弹出的菜单中选择List objects�﹥With incoming references,这时会列出全部可疑的类。右键点击某一项,并选择Path to GC Roots�﹥excludeweak/soft references。MAT工具会进一步筛选出跟程序相关的全部内存泄漏的类。这样就能够追踪到某一个产生内存泄漏的类的详细代码中。

   使用MAT内存分析工具查找内存泄漏的根本思路是找到哪个类的对象的引用没有被释放,然后分析没有被释放的原因,终于定位到代码中哪些片段存在着内存泄漏。


原文地址:https://www.cnblogs.com/wgwyanfs/p/7351261.html