Android的内存机制和常见泄漏情形

一、 Android的内存机制 

Android的程序由Java语言编写,所以Android的内存管理与Java的内存管理相似。程序员通过new为对象分配内存,所有对象在java堆内分配空间;然而对象的释放是由垃圾回收器来完成的。

那么GC怎么能够确认某一个对象是不是已经被废弃了呢?Java采用了有向图的原理。Java将引用关系考虑为图的有向边,有向边从引用者指向引用对象。线程对象可以作为有向图的起始顶点,该图就是从起始顶点开始的一棵树,根顶点可以到达的对象都是有效对象,GC不会回收这些对象。如果某个对象 (连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们认为这个(这些)对象不再被引用,可以被GC回收。 

二、Android的内存溢出 

Android的内存溢出是如何发生的? 

Android的虚拟机是基于寄存器的Dalvik,它的最大堆大小一般是16M,有的机器为24M。因此我们所能利用的内存空间是有限的。如果我们的内存占用超过了一定的水平就会出现OutOfMemory的错误。 

为什么会出现内存不够用的情况呢?我想原因主要有两个: 

由于我们程序的失误,长期保持某些资源(如Context)的引用,造成内存泄露,资源造成得不到释放。 

保存了多个耗用内存过大的对象(如Bitmap),造成内存超出限制。 

三、常见的内存泄漏 

1.万恶的static 

  static是Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例(Context的情况最多),这时就要谨慎对待了。 

 
 
  1. public class ClassName {       
  2.     private static Context mContext;       //省略  
  3. }  

以上的代码是很危险的,如果将Activity赋值到么mContext的话。那么即使该Activity已经onDestroy,但是由于仍有对象保存它的引用,因此该Activity依然不会被释放. 

如何才能有效的避免这种引用的发生呢? 

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

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

    第三、使用WeakReference代替强引用。比如可以使用WeakReference mContextRef; 

2.线程惹的祸 

线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程生命周期的不可控。我们来考虑下面一段代码。

 
 
  1. public class MyActivity extends Activity {      
  2. @Override      
  3. public void onCreate(Bundle savedInstanceState) {          
  4.   super.onCreate(savedInstanceState);          
  5.   setContentView(R.layout.main);          
  6.   new MyThread().start();      
  7. }        
  8. private class MyThread extends Thread{          
  9. @Override          
  10.   public void run() {              
  11.   super.run();              
  12.   //do somthing          
  13. }      
  14. }  
  15. }    

这段代码很平常也很简单,是我们经常使用的形式。我们思考一个问题:假设MyThread的run函数是一个很费时的操作,当我们开启该线程后,将设备的横 屏变为了竖屏,一般情况下当屏幕转换时会重新创建Activity,按照我们的想法,老的Activity应该会被销毁才对,然而事实上并非如此。 

由于我们的线程是Activity的内部类,所以MyThread中保存了Activity的一个引用,当MyThread的run函数没有结束时,MyThread是不会被销毁的,因此它所引用的老的Activity也不会被销毁,因此就出现了内存泄露的问题。 

这种线程导致的内存泄露问题应该如何解决呢? 

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

第二、在线程内部采用弱引用保存Context引用。 

另外,我们都知道Hanlder是线程与Activity通信的桥梁,我们在开发好多应用中会用到线程,有些人处理不当,会导致当程序结束时,线程并没有被销毁,而是一直在后台运行着,当我们重新启动应用时,又会重新启动一个线程,周而复始,你启动应用次数越多,开启的线程数就越多,你的机器就会变得越慢。 

 
 
  1. package com.tutor.thread;   
  2. import android.app.Activity;   
  3. import android.os.Bundle;   
  4. import android.os.Handler;   
  5. import android.util.Log;   
  6. public class ThreadDemo extends Activity {   
  7.     private static final String TAG = "ThreadDemo";   
  8.     private int count = 0;   
  9.     private Handler mHandler =  new Handler();   
  10.        
  11.     private Runnable mRunnable = new Runnable() {   
  12.            
  13.         public void run() {   
  14.             //为了方便 查看,我们用Log打印出来    
  15.             Log.e(TAG, Thread.currentThread().getName() + " " +count);   
  16.             count++;   
  17.             setTitle("" +count);   
  18.             //每2秒执行一次    
  19.             mHandler.postDelayed(mRunnable, 2000);   
  20.         }   
  21.            
  22.     };   
  23.     @Override   
  24.     public void onCreate(Bundle savedInstanceState) {   
  25.         super.onCreate(savedInstanceState);   
  26.         setContentView(R.layout.main);    
  27.         //通过Handler启动线程    
  28.         mHandler.post(mRunnable);   
  29.     }   
  30.        
  31. }   

所以我们在应用退出时,要将线程销毁,我们只要在Activity中的,onDestory()方法处理一下就OK了,如下代码所示: 

 
 
  1. @Override   
  2.   protected void onDestroy() {   
  3.     mHandler.removeCallbacks(mRunnable);   
  4.     super.onDestroy();   
  5.   }   

3.超级大胖子Bitmap 

可以说出现OutOfMemory问题的绝大多数人,都是因为Bitmap的问题。因为Bitmap占用的内存实在是太多了,它是一个“超级大胖子”,特别是分辨率大的图片,如果要显示多张那问题就更显著了。 

如何解决Bitmap带给我们的内存问题? 

第一、及时的销毁。 

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

第二、设置一定的采样率。 

有时候,我们要显示的区域很小,没有必要将整个图片都加载出来,而只需要记载一个缩小过的图片,这时候可以设置一定的采样率,那么就可以大大减小占用的内存。

4.行踪诡异的Cursor 

Cursor是Android查询数据后得到的一个管理数据集合的类,正常情况下,如果查询得到的数据量较小时不会有内存问题,而且虚拟机能够保证Cusor最终会被释放掉。 

然而如果Cursor的数据量特表大,特别是如果里面有Blob信息时,应该保证Cursor占用的内存被及时的释放掉,而不是等待GC来处理。并且Android明显是倾向于编程者手动的将Cursor close掉 

5.构造Adapter时,没有使用缓存的 convertView 

描述: 

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

 
 
  1. 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对象的话,即浪费资源也浪费时间,也会使得内存占用越来越大。

分享文章:Android的内存机制和常见泄漏情形
本文地址:http://www.shufengxianlan.com/qtweb/news39/410789.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联