Android 内存以及相应的泄漏问题收集中

context是如何泄漏的 - Handlers和内部类

博客分类:  Java android

本人翻译, 略有改动, 原文地址如下:

http://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.html

 

考虑如下代码:

 

Java代码   收藏代码
  1. public class SampleActivity extends Activity {   
  2.     private final Handler mLeakyHandler = new Handler() {  
  3.         @Override  
  4.         public void handleMessage(Message msg) {  
  5.           /* ... */  
  6.         }  
  7.     }  
  8. }  

 

你可能看不出来这段代码会造成内存泄漏, 确实, 它不那么容易被发现. 如果你运行Android的lint工具, 它会给你一个警告, 提示你把handler定义成静态的(static), 否则可能造成内存泄漏. 但内存泄漏是怎么发生的呢?

 

首先, 我们应该知道如下几点:

1: 当一个android程序启动时, 框架层(framework)会为程序的主线程创建一个Looper对象. 该对象实现了一个简单的消息队列, 循环不断的处理队列上的消息对象(Message), 主线程上的Looper对象在程序的整个生命周期中一直存在.

2: 当一个Handler在主线程上被实例化时, 它就与Looper的消息队列关联到一起了. 队列上的Message对象持有一个handler的引用, 这使得Looper处理到某一个Message时, 能够调用handler.handleMessage()方法.

3: 在java里, 非静态内部类和匿名内部类持有一个隐式的外部类引用. 相反, 静态的内部类就没有该隐式引用.

 

那么, 泄露在哪里发生呢? 这个有点微妙, 考虑下面一个例子:

Java代码   收藏代码
  1. public class SampleActivity extends Activity {  
  2.    
  3.   private final Handler mLeakyHandler = new Handler() {  
  4.     @Override  
  5.     public void handleMessage(Message msg) {  
  6.       /* ... */  
  7.     }  
  8.   }  
  9.    
  10.   @Override  
  11.   protected void onCreate(Bundle savedInstanceState) {  
  12.     super.onCreate(savedInstanceState);  
  13.    
  14.     // Post a message and delay its execution for 10 minutes.  
  15.     mLeakyHandler.postDelayed(new Runnable() {  
  16.       public void run() { }  
  17.     }, 600000);  
  18.        
  19.     // Go back to the previous Activity.  
  20.     finish();  
  21.   }  
  22. }  
  23.    

 

当Activityfinish后, 我们发出的那个"延迟处理消息"将在主线程的消息队列中保持10分钟, 直到该消息最终被处理. 由于消息持有一个handler的引用, 而handler又持有一个它的外部类-SampleActivity的引用, 这样就阻止了activity的context被垃圾回收, 从而泄漏了Activty引用的所有的应用资源. 注意上述例子中的匿名的Runnable对象也一样造成了context的泄露.

 

要避免这个问题, 就需要将Handler改为静态内部类. 如果你需要在Handler中调用Activity外部类的方法, 你可以在handler中使用一个WeakReference来持有activity对象.

(注意我们将Handler和Runnable都定义成了static的)

Java代码   收藏代码
  1. public class SampleActivity extends Activity {  
  2.   private static class MyHandler extends Handler {  
  3.     private final WeakReference mActivity;  
  4.    
  5.     public MyHandler(SampleActivity activity) {  
  6.       mActivity = new WeakReference(activity);  
  7.     }  
  8.    
  9.     @Override  
  10.     public void handleMessage(Message msg) {  
  11.       SampleActivity activity = mActivity.get();  
  12.       if (activity != null) {  
  13.         /* ... */  
  14.       }  
  15.     }  
  16.   }  
  17.    
  18.   private final MyHandler mHandler = new MyHandler(this);  
  19.    
  20.   // Instances of anonymous classes do not hold an implicit  
  21.   // reference to their outer class when they are "static".  
  22.   private final static Runnable sRunnable = new Runnable() {  
  23.       public void run() { }  
  24.   };  
  25.    
  26.   @Override  
  27.   protected void onCreate(Bundle savedInstanceState) {  
  28.     super.onCreate(savedInstanceState);  
  29.    
  30.     // Post a message and delay its execution for 10 minutes.  
  31.     mHandler.postDelayed(sRunnable, 600000);  
  32.        
  33.     // Go back to the previous Activity.  
  34.     finish();  
  35.   }  
  36. }  

 

 

结论: 在Activity中使用非静态的内部类时, 尽量避免内部类生命周期超出了Activity之外. 类似的例子还有AsyncTask.

=================================

补充例子:

如果你看过PendingIntent的源代码, 你会看到它有一些send(Handler...)的方法, 如果某个Activity调用了PendingIntent.send(...), 并且传入一个非静态的内部Handler类, 当activity被销毁后, 内部类仍然持有它的引用, 导致它无法被垃圾收集.

当然, 如果你没有像这样不当的发布一个Handler到其它的类, 你就不用担心泄露发生.

=================================​

如果你不想每次都创建一个WeakReference, 可以先创建这样一个通用类:

 

Java代码   收藏代码
  1. public abstract class WeakReferenceHandler extends Handler {  
  2.     private WeakReference mReference;  
  3.    
  4.     public WeakReferenceHandler(T reference) {  
  5.         mReference = new WeakReference(reference);  
  6.     }  
  7.    
  8.     @Override  
  9.     public void handleMessage(Message msg) {  
  10.         if (mReference.get() == null)  
  11.             return;  
  12.         handleMessage(mReference.get(), msg);  
  13.     }  
  14.    
  15.     protected abstract void handleMessage(T reference, Message msg);  
  16. }  
  17.    

 

 

 

 

--------------------END------------------------


两篇很好的博文:

http://blog.csdn.net/gemmem/article/details/8920039


http://blog.csdn.net/cyq1028/article/details/19980369



两种方式解决handler导致的内存泄漏问题

Android中使用Handler造成内存泄露的分析和解决


什么是内存泄露?

Java使用有向图机制,通过GC自动检查内存中的对象(什么时候检查由虚拟机决定),如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收。也就是说,一个对象不被任何引用所指向,则该对象会在被GC发现的时候被回收;另外,如果一组对象中只包含互相的引用,而没有来自它们外部的引用(例如有两个对象A和B互相持有引用,但没有任何外部对象持有指向A或B的引用),这仍然属于不可到达,同样会被GC回收。

Android中使用Handler造成内存泄露的原因

Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        mImageView.setImageBitmap(mBitmap);
    }
}

上面是一段简单的Handler的使用。当使用内部类(包括匿名类)来创建Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用(不然你怎么可能通过Handler来操作Activity中的View?)。而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用(不然它怎么发消息给Handler?),这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。另外,如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。

内存泄露的危害

只有一个,那就是虚拟机占用内存过高,导致OOM(内存溢出),程序出错。对于Android应用来说,就是你的用户打开一个Activity,使用完之后关闭它,内存泄露;又打开,又关闭,又泄露;几次之后,程序占用内存超过系统限制,FC。

使用Handler导致内存泄露的解决方法

方法一:通过程序逻辑来进行保护。

1.在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。

2.如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了。

方法二:将Handler声明为静态类。

静态类不持有外部类的对象,所以你的Activity可以随意被回收。代码如下:

static class MyHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        mImageView.setImageBitmap(mBitmap);
    }
}

但其实没这么简单。使用了以上代码之后,你会发现,由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference):

static class MyHandler extends Handler {
    WeakReference mActivityReference;

    MyHandler(Activity activity) {
        mActivityReference= new WeakReference(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        final Activity activity = mActivityReference.get();
        if (activity != null) {
            mImageView.setImageBitmap(mBitmap);
        }
    }
}

将代码改为以上形式之后,就算完成了。



你可能感兴趣的:(ANdroid,内存,Android,内存,内存泄漏)