安卓中的内存泄漏

因为安卓是基于java语言的,所以我们先来看一看java中的内存泄漏,然后在此基础上来谈谈安卓中的内存泄漏。

一java中的内存泄漏:

java中的内存泄漏主要是指在堆中分配的内存,明明已经不需要的时候,还仍然保留着访问它的引用,导致GC回收不能及时回收(关于GC回收不做过多赘述),导致这种情况出现的最主要原因是长生命周期的对象持有短生命周期对象的引用,导致短生命周期的对象明明已经不需要却无法被GC回收,从而导致内存泄漏。主要包括以下几种情况:

1在一个类中创建了一个非静态内部类的静态实例,如下所示:

public class Outer
{  
    static Inner inner= null;  
          
   void doSomething()
   {
	//do something
   }
    class Inner 
    {  
      void doSomething()  
      {  
         System.out.println("dosth.");  
      }  
    }  
}  
在上述代码中,在第一次创建外部类Outer的实例时,会自动加载内部类inner(因为被static修饰),而内部类Inner持有外部类Outer的引用,且内部类inner的生命周期显然和整个程序的生命周期相同(被static修饰),这样当不需要使用外部类时,因为inner持有outer的引用,导致outer不能被GC回收,导致内存泄漏。


2使用java中的集合框架添加的对象在不需使用时未清除

如果我们把一些对象的引用加入到了集合中,当我们不需要该对象时,如果没有把它的引用从集合中清理掉,而仅仅是将其用用赋值为null,让GC去回收(而事实上GC是不会回收的),代码如下:

Vector v = new  Vector( 10 );  
for  ( int  i = 1 ;i < 100 ; i ++ ){  
Object o = new  Object();  
v.add(o);  
o = null ;  
}
如在上述代码中,我们在一个for循环中不断产生新的Object对象,然后将其添加到集合Vector中,然后将该对象的引用置为空(本意是置为null之后,让GC去回收),但事实上GC是不会去回收的,因为虽然Object对象的引用被置空,但是仍然存在其它的引用路径能够找到该对象,这个引用是从Vector集合对象中找到的,即尽管 o 引用已经被置空,但是 Object 对象仍然存在其他的引用,是可以被访问到的,所以 GC 无法将其释放掉。如果在此循环之后, Object 对象对程序已经没有任何作用,那么我们就认为此 Java 程序发生了内存泄漏。

要解决此种情况的泄漏,需要在对象不需要使用时调用remove()方法将该对象从集合中清除掉。


3不合理的使用单例模式

其实此种情况与第一种情况非常类似,都是因为static关键字导致的内存泄漏。代码如下:

//懒汉式单例类.在第一次调用的时候实例化自己 
public class Singleton {
    private Singleton() {}
    private static Singleton single=null;
    public static Singleton getInstance() {
         if (single == null) {  
             single = new Singleton();
         }  
        return single;
    }
}
可以看到单例模式的类都是被static修饰的,因此该类的实例的生命周期与整个程序的生命周期相同,因此很容易造成内存泄漏。


4数据库连接,网络连接(socket)和IO连接在不使用时未使用close()方法将连接关闭

这种情况只要注意在不需要使用资源时用close()方法将其关闭即可。


5修改hashset中对象的参数值,且参数是计算哈希值的字段:

这种情况与第二种情况非常类似,因为在第二种情况中我们已经讨论过在使用java集合框架时避免内存泄漏的关键是当对象不需使用时将其从集合中remove掉。

但当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段,否则对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中删除当前对象,造成内存泄露。


二安卓中的内存泄漏:

因为安卓是基于java语言的,所以很多情况与上述讲述的java内存泄漏是相同的。

1)注意Activity的泄漏

通常来说,Activity的泄漏是内存泄漏里面最严重的问题,它占用的内存多,影响面广,我们需要特别注意以下两种情况导致的Activity泄漏:

内部类引用导致Activity的泄漏

最典型的场景是Handler导致的Activity泄漏,如果Handler中有延迟的任务或者是等待执行的任务队列过长,都有可能因为Handler继续执行而导致Activity发生泄漏。此时的引用关系链是Looper -> MessageQueue -> Message -> Handler -> Activity。为了解决这个问题,可以在UI退出之前,执行remove Handler消息队列中的消息与runnable对象。或者是使用Static + WeakReference的方式来达到断开Handler与Activity之间存在引用关系的目的。

Activity Context被传递到其他实例中,这可能导致自身被引用而发生泄漏。

内部类引起的泄漏不仅仅会发生在Activity上,其他任何内部类出现的地方,都需要特别留意!我们可以考虑尽量使用static类型的内部类,同时使用WeakReference的机制来避免因为互相引用而出现的泄露。


2)考虑使用Application Context而不是Activity Context

对于大部分非必须使用Activity Context的情况(Dialog的Context就必须是Activity Context),我们都可以考虑使用Application Context而不是Activity的Context,这样可以避免不经意的Activity泄露。


3)构造Adapter时,没有使用缓存的convertView

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


public View getView(int position, ViewconvertView, 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对象的话,会白白浪费内存资源,会使得内存占用越来越大。


4)注意监听器的注销


在Android程序里面存在很多需要register与unregister的监听器,我们需要确保在合适的时候及时unregister那些监听器。自己手动add的listener,需要记得及时remove这个listener。

5).资源对象没关闭造成的内存泄漏

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

因此对于资源性对象在不使用的时候,应该调用它的close()函数,将其关闭掉,然后才置为null.在我们的程序退出时一定要确保我们的资源性对象已经关闭。

以下是错误代码及正确代码的示范:

Cursor cursor = getContentResolver().query(uri...); 
 
if (cursor.moveToNext()) { 
 
... ... 
 
}

正确代码:

Cursor cursor = null; 
 
try { 
 
cursor = getContentResolver().query(uri...); 
 
if (cursor != null &&cursor.moveToNext()) { 
 
... ... 
 
} 
 
} finally { 
 
if (cursor != null) { 
 
try { 
 
cursor.close(); 
 
} catch (Exception e) { 
 
//ignore this 
 
} 
 
} 
 
}


Bitmap对象不在使用时调用recycle()释放内存

虽然在大多数情况下,我们会对Bitmap增加缓存机制,但是在某些时候,部分Bitmap是需要及时回收的。例如临时创建的某个相对比较大的bitmap对象,在经过变换得到新的bitmap对象之后,应该调用Bitmap.recycle()方法尽快回收原始的bitmap,这样能够更快释放原始bitmap所占用的空间。

需要特别留意的是Bitmap类里面提供的createBitmap()方法,如图所示:


这个函数返回的bitmap有可能和source bitmap是同一个,在回收的时候,需要特别检查source bitmap与return bitmap的引用是否相同,只有在不等的情况下,才能够执行source bitmap的recycle方法。


好了以上就是本人理解的关于安卓中内存泄漏的知识,另外推荐大家看一下:http://www.jb51.net/article/77899.htm,用代码的形式详细介绍了几种内存泄漏与解决方案。如果大家觉得不错记得顶一下哦。微笑

原文地址:https://www.cnblogs.com/hainange/p/6334026.html