android中的内存泄漏

来源:互联网 发布:淘宝云客服靠谱吗 编辑:程序博客网 时间:2024/06/06 02:24

从网上搜集整理了一些相关的文章做了一个汇集:

什么是内存泄露?GC无法回收原本应该被回收的对象,这个对象就引发了内存泄露

1.Bitmap使用完忘记回收,因为bitmap实现部分是通过JNI调用了Native方法,GC机制无法正常回收Bitmap申请的这部分内存空间(API10之前是这样的,之后分配在Heap中,不过为了兼容老版本...显示的调用一下recycled,也能让GC更快的回收调自己);

那Bitmap应该怎样回收呢?

   // bitmap!=null要放在前面,如果bitmap为空再去调用isRecycled方法便会报错

     if(bitmap!=null&&!bitmap.isRecycled){

         bitmap.recycled(); //回收bitmap

         bitmap = null;      //使bitmap对象变为虚引用的状态,让GC更快的回收

    }

接下来,讲两个更为常见的内存泄露,大家根据这两个可以自行扩展

   btn_hint.setOnClickListener(new View.OnClickListener() {

   @Override

       public void onClick(View v) {

           Toast.makeText(MainActivity.this, "Hello",Toast.LENGTH_SHORT).show();

      }

   });

是不是很常见,平时可能也是这样写的(将其封装意义一样,只要引用了当前activity),你可能想问,这里有什么问题吗?问题在于如果用户在Toast消失之前,用户按了返回键,这个Activity就引起了内存泄露,原因? Toast持有了当前Activity,导致Activity无法被GC销毁。解决方法:让Toast持有ApplicationContext。其实只要不是Layout,Context都可以使用ApplicationContext。顺便讲个小技巧:在非Activity中,正常是不能直接getContext来拿到Context的,获取资源有需要靠Context,这时可以考虑在自己的Application中维护一个全局的Context,供无法直接拿到Context的类使用,省的参数传来传去(视图相关的不建议使用ApplicationContext)。

   private static Context mContext;

   public static MyApplication getInstance() { //供外界调用...

        return mApplication;

    }

   @Override

   public void onCreate() {

       super.onCreate(); 

       mContext = getApplicationContext();   

    }

另一个常见的...内存泄露

   new Thread() {

       public void run() {

       //网络请求

       };

   }.start();

在Activity中新建一个线程,进行网络请求,如果线程未结束,用户按了返回键,同样内存泄露。原因:该Thread是匿名内部类,所以会隐式的持有外部类(这里也就是Activity)。解决方式:多种多样; 不使用匿名内部类,或者整个应用维护一个线程池,或者维护一个线程队列,后两种都是让线程不依赖于Activity从而达到避免内存泄露的目的。

资源获取等等很多地方都需要用到Context,很多地方都会用到匿名内部类,这也就导致了这里存在很大的内存泄露隐患,但平时很多小伙伴可能还没有注意到,希望这篇文章能给你一些启发。

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

避免Android中Context引起的内存泄露

Context是我们在编写Android程序经常使用到的对象,意思为上下文对象。 常用的有Activity的Context还是有Application的Context。Activity用来展示活动界面,包含了很多的视图,而视图又含有图片,文字等资源。在Android中内存泄露很容易出现,而持有很多对象内存占用的Activity更加容易出现内存泄露,开发者需要特别注意这个问题。

本文讲介绍Android中Context,更具体的说是Activity内存泄露的情况,以及如何避免Activity内存泄露,加速应用性能。

Drawable引起的内存泄露(该问题已经在新版本中修正)

Drawable引起内存泄露这个问题是比较隐晦,难以察觉的。在阅读了Romain Guy的Avoiding memory leaks,结合grepcode查看源码才明白了。

在Android系统中,当我们进行了屏幕旋转,默认情况下,会销毁掉当前的Activity,并创建一个新的Activity并保持之前的状态。在这个过程中,Android系统会重新加载程序的UI视图和资源。假设我们有一个程序用到了一个很大的Bitmap图像,我们不想每次屏幕旋转时都重新加载这个Bitmap对象,最简单的办法就是将这个Bitmap对象使用static修饰。

private static Drawable sBackground;

 

@Override

protected void onCreate(Bundle state) {

 super.onCreate(state);

 TextView label = new TextView(this);

 label.setText("Leaks are bad");

  if(sBackground == null) {

   sBackground = getDrawable(R.drawable.large_bitmap);

  }

 label.setBackgroundDrawable(sBackground);

 

 setContentView(label);

}

但是上面的方法在屏幕旋转时有可能引起内存泄露,无论是咋一看还是仔细看这段代码,都很难发现哪里引起了内存泄露。

当一个Drawable绑定到了View上,实际上这个View对象就会成为这个Drawable的一个callback成员变量,上面的例子中静态的sBackground持有TextView对象lable的引用,而lable只有Activity的引用,而Activity会持有其他更多对象的引用。sBackground生命周期要长于Activity。当屏幕旋转时,Activity无法被销毁,这样就产生了内存泄露问题。

2.3.7及以下版本Drawable的setCallback方法的实现

public final void setCallback(Callback cb){

   mCallback = cb;

}

好在从4.0.1开始,引入了弱引用处理这个问题,弱引用在GC回收时,不会阻止GC回收其指向的对象,避免了内存泄露问题。

public final void setCallback(Callback cb){

   mCallback = new WeakReference<Callback>(cb);

}

单例引起的内存泄露(在Activity对象之外,用一个比Activity对象生命周期更长的内容引用Activity对象)

单例是我们比较简单常用的一种设计模式,然而如果单例使用不当也会导致内存泄露。比如这样一个例子,我们使用饿汉式初始化单例,AppSettings我们需要持有一个Context作为成员变量,如果我们按照下面的实现其实是有问题。

public class AppSettings {   

   private Context mAppContext;

   private static AppSettings sInstance = new AppSettings();

   //some other codes

   public static AppSettings getInstance() {

     return sInstance;

    }

    public final void setup(Context context) {

       mAppContext = context;

    }

}

sInstance作为静态对象,其生命周期要长于普通的对象,其中也包含Activity,当我们进行屏幕旋转,默认情况下,系统会销毁当前Activity,然后当前的Activity被一个单例持有,导致垃圾回收器无法进行回收,进而产生了内存泄露。解决的方法就是不持有Activity的引用,而是持有Application的Context引用。代码如下修改

public final void setup(Context context) {

   mAppContext = context.getApplicationContext();

}

通常我们想要获取Context对象,主要有以下四种方法

View.getContext,返回当前View对象的Context对象,通常是当前正在展示的Activity对象。

Activity.getApplicationContext,获取当前Activity所在的(应用)进程的Context对象,通常我们使用Context对象时,要优先考虑这个全局的进程Context。

ContextWrapper.getBaseContext():用来获取一个ContextWrapper进行装饰之前的Context,可以使用这个方法,这个方法在实际开发中使用并不多,也不建议使用。

Activity.this 返回当前的Activity实例,如果是UI控件需要使用Activity作为Context对象,但是默认的Toast实际上使用ApplicationContext也可以。

避免内存泄露须谨记

l  不要让生命周期长于Activity的对象持有到Activity的引用

l  尽量使用Application的Context而不是Activity的Context

l  尽量不要在Activity中使用非静态内部类,因为非静态内部类会隐式持有外部类实例的引用。如果使用静态内部类,将外部实例引用作为弱引用持有。

l  垃圾回收不能解决内存泄露,了解Android中垃圾回收机制

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

什么导致了Context泄露:Handler&内部类

思考下面代码

1 public class SampleActivity extendsActivity {

2

3  private final Handler mLeakyHandler = new Handler() {

4    @Override

5    public void handleMessage(Message msg) {

6      // ...

7    }

8   }

9 }

如果没有仔细观察,上面的代码可能导致严重的内存泄露。Android Lint会给出下面的警告:

   In Android, Handler classes should be static or leaks might occur.

但是到底是泄漏,如何发生的?让我们确定问题的根源,先写下我们所知道的

1、当一个Android应用程序第一次启动时,Android框架为应用程序的主线程创建一个Looper对象。一个Looper实现了一个简单的消息队列,在一个循环中处理Message对象。所有主要的应用程序框架事件(如活动生命周期方法调用,单击按钮,等等)都包含在Message对象,它被添加到Looper的消息队列然后一个个被处理。主线程的Looper在应用程序的整个生命周期中存在。

2、当一个Handle在主线程被实例化,它就被关联到Looper的消息队列。被发送到消息队列的消息会持有一个Handler的引用,以便Android框架可以在Looper最终处理这个消息的时候,调用Handler#handleMessage(Message)。

3、在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    // 延时10分钟发送一个消息

15    mLeakyHandler.postDelayed(new Runnable() {

16      @Override

17      public void run() { }

18    }, 60 * 10 * 1000);

19 

20    // 返回前一个Activity

21    finish();

22  }

23 }

当这个Activity被finished后,延时发送的消息会继续在主线程的消息队列中存活10分钟,直到他们被处理。这个消息持有这个Activity的Handler引用,这个Handler又隐式地持有他的外部类(在这个例子中是SampleActivity)。直到消息被处理前,这个引用都不会被释放。因此Activity不会被垃圾回收机制回收,泄露他所持有的应用程序资源。注意,第15行的匿名Runnable类也一样。匿名类的非静态实例持有一个隐式的外部类引用,因此context将被泄露。

为了解决这个问题,Handler的子类应该定义在一个新文件中或使用静态内部类。静态内部类不会隐式持有外部类的引用。所以不会导致它的Activity泄露。如果你需要在Handle内部调用外部Activity的方法,那么让Handler持有一个Activity的弱引用(WeakReference)以便你不会意外导致context泄露。为了解决我们实例化匿名Runnable类可能导致的内存泄露,我们将用一个静态变量来引用他(因为匿名类的静态实例不会隐式持有他们外部类的引用)。

1 public class SampleActivity extendsActivity {

 2    /**

 3     * 匿名类的静态实例不会隐式持有他们外部类的引用

 4     */

 5    private static final Runnable sRunnable = new Runnable() {

 6            @Override

 7            public void run() {

 8            }

 9        };

10

11    private final MyHandler mHandler = new MyHandler(this);

12

13    @Override

14    protected void onCreate(Bundle savedInstanceState) {

15        super.onCreate(savedInstanceState);

16

17        // 延时10分钟发送一个消息.

18        mHandler.postDelayed(sRunnable, 60 * 10 * 1000);

19

20        // 返回前一个Activity

21        finish();

22    }

23

24    /**

25    * 静态内部类的实例不会隐式持有他们外部类的引用。

26    */

27    private static class MyHandler extends Handler {

28        private final WeakReference<SampleActivity> mActivity;

29

30        public MyHandler(SampleActivity activity) {

31             mActivity = newWeakReference<SampleActivity>(activity);

32        }

33

34        @Override

35        public void handleMessage(Message msg) {

36             SampleActivity activity =mActivity.get();

37

38             if (activity != null) {

39                 // ...

40             }

41        }

42    }

43 }

静态和非静态内部类的区别是比较难懂的,但每一个Android开发人员都应该了解。开发中不能碰的雷区是什么?不在一个Activity中使用非静态内部类,以防它的生命周期比Activity长。相反,尽量使用持有Activity弱引用的静态内部类。

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

Android中的Activitys, Threads和内存泄露

Android编程中一个共同的困难就是协调Activity的生命周期和长时间运行的任务(task),并且要避免可能的内存泄露。思考下面Activity的代码,在它启动的时候开启一个线程并循环执行任务。

/**

 * 一个展示线程如何在配置变化中存活下来的例子(配置变化会导致创

 * 建线程的Activity被销毁)。代码中的Activity泄露了,因为线程被实

 * 例为一个匿名类实例,它隐式地持有外部Activity实例,因此阻止Activity

 * 被回收。

 */

public class MainActivity extends Activity{

 

 @Override

 protected void onCreate(Bundle savedInstanceState) {

   super.onCreate(savedInstanceState);

   exampleOne();

  }

 

 private void exampleOne() {

   new Thread() {

     @Override

     public void run() {

       while (true) {

         SystemClock.sleep(1000);

       }

     }

   }.start();

  }

}

当配置发生变化(如横竖屏切换)时,会导致整个Activity被销毁并重新创建,很容易假定Android将会为我们清理和回收跟Activity相关的内存及它运行中的线程。然而,这并非如此。这两者都会导致内存泄露而且不会被回收, 后果是性能可能显著地下降。

怎么样让一个Activity泄露

如果你读过我前一篇关于Handler和内部类的文章,那么第一种内存泄露应该很容易理解。在Java中,非静态匿名类隐式地持有他们的外部类的引用。如果你不小心,保存这个引用可能导致Activity在可以被GC回收的时候被保存下来。Activity持有一个指向它们整个View继承树和它所持有的所有资源的引用,所以如果你泄露了一个,很多内存都会连带着被泄露。配置发生变化只加剧了这个问题,它发出一个信号让Activity销毁并重新创建。比如,基于上面的代码进行10次横竖屏变化后,我们可以看到(使用Eclipse Memory Analyzer)由于那些隐式的引用,每一个Activity对象其实都留存在内存中。每一次配置发生变化后,Android系统都会创建一个新的Activity并让旧的Activity可以被回收。然而,隐式持有旧Activity引用的线程,阻止他们被回收。所以每次泄露一个新的Activity,都会导致所有跟他们关联的资源都没有办法被回收。

解决方法也很简单,在我们确定了问题的根源,那么只要将线程定义为private static内部类,如下所示:

/**

 * 这个例子通过将线程实例声明为private static型的内部类,从而避免导致Activity泄

 * 露,但是这个线程依旧会跨越配置变化存活下来。DVM有一个指向所有运行中线程的

 * 引用(无论这些线程是否可以被垃圾回收),而线程能存活多长时间以及什么时候可

 * 以被回收跟Activity的生命周期没有任何关系。

 * 活动线程会一直运行下去,直到系统将你的应用程序销毁。

 */

public class MainActivity extends Activity{

 

 @Override

 protected void onCreate(Bundle savedInstanceState) {

   super.onCreate(savedInstanceState);

   exampleTwo();

  }

 

 private void exampleTwo() {

   new MyThread().start();

  }

 

 private static class MyThread extends Thread {

   @Override

   public void run() {

     while (true) {

       SystemClock.sleep(1000);

     }

    }

  }

}

新的线程不会隐式地持有Activity的引用,并且Activity在配置发生变化后都会变得可以被回收。

怎么使一个Thread泄露

第二个问题是每当创建了一个新Activity,就会导致一个thread泄露并且不会被回收。在Java中,thread是GC Root也就是说在系统中的Dalvik Virtual Machine (DVM)保存对所有活动中线程的强引用,这就导致了这些线程留存下来继续运行并且不会达到可以被回收的条件。因此你必须要考虑怎样停止后台线程。下面是一个例子:

/**

 * 跟例子2一样,除了这次我们实现了取消线程的机制,从而保证它不会泄露。

 *onDestroy()常常被用来在Activity推出前取消线程。

 */

public class MainActivity extends Activity{

 private MyThread mThread;

 

 @Override

 protected void onCreate(Bundle savedInstanceState) {

   super.onCreate(savedInstanceState);

   exampleThree();

  }

 

 private void exampleThree() {

   mThread = new MyThread();

   mThread.start();

  }

 

  /**

    *静态内部类不会隐式地持有他们外部类的引用,所以Activity实例不会在配置变化

    *中被泄露

   */

 private static class MyThread extends Thread {

   private boolean mRunning = false;

 

   @Override

   public void run() {

     mRunning = true;

     while (mRunning) {

       SystemClock.sleep(1000);

     }

    }

 

   public void close() {

     mRunning = false;

    }

  }

 

 @Override

 protected void onDestroy() {

   super.onDestroy();

   mThread.close();

  }

}

 

在上面的代码中,我们在onDestroy()中关闭线程保证了线程不会意外泄露。如果你想要在配置变化的时候保存线程的状态(而不是每次都要关闭并重新创建一个新的线程)。考虑使用可留存(在配置变化中不会被销毁)、没有UI的fragment来执行长时间任务。看看我的博客,叫做《用Fragment解决屏幕旋转(状态发生变化)状态不能保持的问题》,里面有一个例子说明实现这点。API Demo中也一个全面的例子。

总结:

在Android中处理Activity生命周期与长时间运行的任务的关系可能很困难并且可能导致内存泄露。下面有一些值得考虑的通用建议:

l  优先使用静态内部类而不是非静态的。非静态内部类的每个实例都会有一个对它外部Activity实例的引用。当Activity可以被GC回收时,存储在非静态内部类中的外部Activity引用可能导致垃圾回收失败。如果你的静态内部类需要宿主 Activity的引用来执行某些东西,你要将这个引用封装在一个WeakReference中,避免意外导致Activity泄露。

l  不要假定Java最后总会为你清理运行中的线程。在上面的例子中,很容易错误地认为用户退出 Activity后,Activity就会被回收,任何跟这个Activity关联的线程也都将一并被回收。事实上不是这样的。Java线程会继续运行下去,直到他们被显式地关闭或者整个process被Android系统杀掉。因此,一定要记得为后台线程实现对应的取消策略,并且在Activity 生命周期事件发生的时候使用合理的措施。

l  考虑你是否真的应该使用线程。Android Framework提供了很多旨在为开发者简化后台线程开发的类。比如,考虑使用Loader而不是线程。当你需要配合Activity生命周期做一些短时间的异步后台任务查询类任务考虑使用Service,然后使用BrocastReceiver向UI反馈进度、结果。最后,记住本篇文章中一切关于线程的讨论也适用于AsyncTask(因为Asynctask类使用ExecutorService来执行它的任务)。然而,鉴于AsyncTask 只应该用于短时间的操作(最多几秒钟,参照文档),它倒不至于会导致像Activity或线程泄露那么大的问题。

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

Android学习系列(36)--App调试内存泄露之Context篇(上)

Context作为最基本的上下文,承载着Activity,Service等最基本组件。当有对象引用到Activity,并不能被回收释放,必将造成大范围的对象无法被回收释放,进而造成内存泄漏。

下面针对一些常用场景逐一分析。

1. CallBack对象的引用

@Override

protectedvoid onCreate(Bundle state){

  super.onCreate(state);

 TextView label =new TextView(this);

 label.setText("Leaks are bad");

  

 setContentView(label);

}

    大家看看有什么问题吗?没问题是吧,继续看:

private static Drawable sBackground;

@Override

protected void onCreate(Bundle state){

 super.onCreate(state);

 TextView label =new TextView(this);

 label.setText("Leaks are bad");

 if(sBackground ==null){

   sBackground = getDrawable(R.drawable.large_bitmap);

  }

 label.setBackgroundDrawable(sBackground);

 setContentView(label);

}

    有问题吗?哈哈,先Hold住一下,先来说一下android各版本发布的历史:

/*

2.2       2010-3-20,Froyo

2.3       2010-12-6, Gingerbread

3.0       2011-2-22, Honeycomb

4.0       2011-10-11 Ice Cream Sandwich

*/

    了解源码的历史,是很有益于我们分析android代码的。

    好,开始分析代码。

    首先,查看setBackgroundDrawable(Drawablebackground)方法源码里面有一行代码引起我们的注意:

public void setBackgroundDrawable(Drawablebackground) {

   // ... ...

   background.setCallback(this);

   // ... ...

}

    所以sBackground对view保持了一个引用,view对activity保持了一个引用。

    当退出当前Activity时,当前Activity本该释放,但是因为sBackground是静态变量,它的生命周期并没有结束,而sBackground间接保持对Activity的引用,导致当前Activity对象不能被释放,进而导致内存泄露。

    所以结论是:有内存泄露!

    这是Android官方文档的例子,到此结束了吗?我发现网上太多直接抄或者间接抄这篇文章,一搜一大片,并且吸引了大量的Android初学者不断的转载学习。但是经过本人深入分析Drawable源码,事情发生了一些变化。Android官方文档的这篇文章是写于2009年1月的,当时的Android Source至少是Froyo之前的。Froyo的Drawable的setCallback()方法的实现是这样的:

public final void setCallback(Callback cb){

       mCallback = cb;

}

    在GingerBread的代码还是如此的。

    但是当进入HoneyComb,也就是3.0之后的代码我们发现Drawable的setCallback()方法的实现变成了:

public final void setCallback(Callback cb){

       mCallback = new WeakReference<Callback>(cb);

}

    也就是说3.0之后,Drawable使用了软引用,把这个泄露的例子问题修复了。(至于软引用怎么解决了以后有机会再分析吧)

    所以最终结论是,在android3.0之前是有内存泄露,在3.0之后无内存泄露!

    如果认真比较代码的话,Android3.0前后的代码改进了大量类似代码,前面的Cursor篇里的例子也是在3.0之后修复了。

    从这个例子中,我们很好的发现了内存是怎么通过回调泄露的,同时通过官方代码的update也了解到了怎么修复类似的内存泄露。

2. System Service对象

    通过各种系统服务,我们能够做一些系统设计好的底层功能:

//ContextImpl.java

@Override

public Object getSystemService(String name){

   ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);

   return fetcher == null ? null : fetcher.getService(this);

}

 

static {

   registerService(ACCESSIBILITY_SERVICE, new ServiceFetcher() {

           public Object getService(ContextImpl ctx) {

           return AccessibilityManager.getInstance(ctx);

           }});

 

   registerService(CAPTIONING_SERVICE, new ServiceFetcher() {

           public Object getService(ContextImpl ctx) {

           return new CaptioningManager(ctx);

           }});

 

   registerService(ACCOUNT_SERVICE, new ServiceFetcher() {

           public Object createService(ContextImpl ctx) {

           IBinder b = ServiceManager.getService(ACCOUNT_SERVICE);

           IAccountManager service = IAccountManager.Stub.asInterface(b);

           return new AccountManager(ctx, service);

           }});

   // ... ...

}

  这些其实就是定义在Context里的,按理说这些都是系统的服务,应该都没问题,但是代码到了各家厂商一改,事情发生了一些变化。一些厂商定义的服务,或者厂商自己修改了一些新的代码导致系统服务引用了Context对象不能及时释放,我曾经碰到过Wifi,Storage服务都有内存泄露。我们改不了这些系统级应用,我们只能修改自己的应用。解决方案就是:使用ApplicationContext代替Context。

举个例子吧:

// For example

mStorageManager = (StorageManager)getSystemService(Context.STORAGE_SERVICE);

改成:

mStorageManager =

 (StorageManager)getApplicationContext().getSystemService(Context.STORAGE_SERVICE);

3. Handler对象

    先看一段代码:

public class MainActivity extends QActivity{

       // lint tip: This Handler class should be static or leaks might occur

   class MyHandler extends Handler {

       ... ...

    }

}

   Handler泄露的关键点有两个:

   1). 内部类

   2). 生命周期和Activity不一定一致

    第一点,Handler使用的比较多,经常需要在Activity中创建内部类,所以这种场景还是很多的。内部类持有外部类Activity的引用,当Handler对象有Message在排队,则无法释放,进而导致Activity对象不能释放。如果是声明为static,则该内部类不持有外部Acitivity的引用,则不会阻塞Activity对象的释放。

    如果声明为static后,可在其内部声明一个弱引用(WeakReference)引用外部类。

public class MainActivity extends Activity{

   private CustomHandler mHandler;

   @Override

   protected void onCreate(Bundle savedInstanceState) {

       super.onCreate(savedInstanceState);

       mHandler = new CustomHandler(this);

    }

   static class CustomHandlerextends Handler {

       // 内部声明一个弱引用,引用外部类

       private WeakReference<MainActivity > activityWeakReference;

       public MyHandler(MyActivity activity) {

           activityWeakReference= new WeakReference<MainActivity >(activity);

       }

                // ... ...  

    }

}

    第二点,其实不单指内部类,而是所有Handler对象,如何解决上面说的Handler对象有Message在排队,而不阻塞Activity对象释放?解决方案也很简单,在Activity onStop或者onDestroy的时候,取消掉该Handler对象的Message和Runnable。

    通过查看Handler的API,它有几个方法:removeCallbacks(Runnabler)和removeMessages(int what)等。

// 一切都是为了不要让mHandler拖泥带水

@Override

public void onDestroy() {

   mHandler.removeMessages(MESSAGE_1);

   mHandler.removeMessages(MESSAGE_2);

   mHandler.removeMessages(MESSAGE_3);

   mHandler.removeMessages(MESSAGE_4);

   // ... ...

   mHandler.removeCallbacks(mRunnable);

   // ... ...

}

   上面的代码太长?好吧,出大招:

@Override

public void onDestroy() {

   //  If null, all callbacks andmessages will be removed.

   mHandler.removeCallbacksAndMessages(null);

}

    有人会问,当Activity退出的时候,我还有好多事情要做,怎么办?我想一定有办法的,比如用Service等等.

4. Thread对象

    同Handler对象可能造成内存泄露的原理一样,Thread的生命周期不一定是和Activity生命周期一致。而且因为Thread主要面向多任务,往往会造成大量的Thread实例。

    据此,Thread对象有2个需要注意的泄漏点:

   1). 创建过多的Thread对象

   2). Thread对象在Activity退出后依然在后台执行

    解决方案是:

   1). 使用ThreadPoolExecutor,在同时做很多异步事件的时候是很常用的,这个不细说。

   2). 当Activity退出的时候,退出Thread。

    第一点,例子太多,建议大家参考一下afinal中AsyncTask的实现学习。

    第二点,如何正常退出Thread,我在之前的博文中也提到过。示例代码如下:

// refhttp://docs.oracle.com/javase/1.5.0/docs/guide/misc/threadPrimitiveDeprecation.html

private volatile Thread blinker;

public void stop() {

   blinker = null;

}

 

public void run() {

   Thread thisThread = Thread.currentThread();

   while (blinker == thisThread) {

       try {

           thisThread.sleep(interval);

       } catch (InterruptedException e){

       }

       repaint();

    }

}

有人会问,当Activity退出的时候,我还有好多事情要做,怎么办?请看上面Handler的分析最后一行。

5. AsyncTask对象

    但是AsyncTask确实需要额外注意一下。它的泄露原理和前面Handler,Thread泄露的原理差不多,它的生命周期和Activity不一定一致。

    解决方案是:在activity退出的时候,终止AsyncTask中的后台任务。

    但是,问题是如何终止?

   AsyncTask提供了对应的API:public final boolean cancel (boolean mayInterruptIfRunning)。

    它的说明有这么一句话:

// Attempts to cancel execution of thistask. This attempt will fail if the task has already completed, already beencancelled, or could not be cancelled for some other reason.

// If successful, and this task has notstarted when cancel is called, this task should never run. If the task hasalready started, then the mayInterruptIfRunning parameter determines whetherthe thread executing this task should be interrupted in an attempt to stop thetask.

   cancel是不一定成功的,如果正在运行,它可能会中断后台任务。怎么感觉这话说的这么不靠谱呢?是的,就是不靠谱。

那么,怎么才能靠谱点呢?我们看看官方的示例:

private class DownloadFilesTask extendsAsyncTask<URL, Integer, Long> {

    protected Long doInBackground(URL... urls) {

        int count = urls.length;

        long totalSize = 0;

        for (int i = 0; i < count; i++) {

            totalSize += Downloader.downloadFile(urls[i]);

            publishProgress((int) ((i / (float) count) * 100));

            // Escape early if cancel() is called

            // 注意下面这行,如果检测到cancel,则及时退出

            if (isCancelled()) break;

        }

        return totalSize;

    }

    protected void onProgressUpdate(Integer... progress) {

        setProgressPercent(progress[0]);

    }

    protected void onPostExecute(Long result) {

        showDialog("Downloaded " + result + " bytes");

    }

 }

  官方的例子是很好的,在后台循环中时刻监听cancel状态,防止没有及时退出。

     为了提醒大家,google特意在AsyncTask的说明中撂下了一大段英文:

// AsyncTask is designed to be a helperclass around Thread and Handler and does not constitute a generic threadingframework. AsyncTasks should ideally be used for short operations (a fewseconds at the most.) If you need to keep threads running for long periods oftime, it is highly recommended you use the various APIs provided by thejava.util.concurrent pacakge such as Executor, ThreadPoolExecutor andFutureTask.

   AsyncTask适用于短耗时操作,最多几秒钟。如果你想长时间耗时操作,请使用其他java.util.concurrent包下的API,比如Executor, ThreadPoolExecutor 和 FutureTask.

6. BroadcastReceiver对象

   ... has leaked IntentReceiver ... Are you missing a call tounregisterReceiver()?

    这个直接说了,种种原因没有调用到unregister()方法。

    解决方法很简单,就是确保调用到unregister()方法。

    顺带说一下一种相反的情况,receiver对象没有registerReceiver()成功(没有调用到),于是unregister的时候提示出错:

// java.lang.IllegalArgumentException:Receiver not registered ...

    有两种解决方案:

    方案一:在registerReceiver()后设置一个FLAG,根据FLAG判断是否unregister()。网上搜到的文章几乎都这么写,我以前碰到这种bug,也是一直都这么解。但是不可否认,这种代码看上去确实有点丑陋。

    方案二:我后来无意中听到某大牛提醒,在Android源码中看到一种更通用的写法:

// just sample, 可以写入工具类

// 第一眼我看到这段代码,靠,太粗暴了,但是回头一想,要的就是这么简单粗暴,不要把一些简单的东西搞的那么复杂。

private voidunregisterReceiverSafe(BroadcastReceiver receiver) {

   try {

       getContext().unregisterReceiver(receiver);

    }catch (IllegalArgumentException e) {

       // ignore

    }

}

7. TimerTask对象

   TimerTask对象在和Timer的schedule()方法配合使用的时候极容易造成内存泄露。

private void startTimer(){

   if (mTimer == null) {

       mTimer = new Timer();

    }

   if (mTimerTask == null) {

       mTimerTask = new TimerTask() {

           @Override

           public void run() {

                // todo

           }

       };

    }

   if(mTimer != null && mTimerTask != null )

       mTimer.schedule(mTimerTask, 1000, 1000);

}

  泄露的点是,忘记cancel掉Timer和TimerTask实例。cancel的时机同cursor篇说的,在合适的时候cancel。

private void cancelTimer(){

       if (mTimer != null) {

           mTimer.cancel();

           mTimer = null;

       }

       if (mTimerTask != null) {

           mTimerTask.cancel();

           mTimerTask = null;

       }

    }

8. Observer对象。

   Observer对象的泄露,也是一种常见、易发现、易解决的泄露类型。

    先看一段正常的代码:

// 其实也非常简单,只不过ContentObserver是系统的例子,有必要单独拿出来提示一下大家,不可掉以轻心

private final ContentObservermSettingsObserver = new ContentObserver(new Handler()) {

   @Override

   public void onChange(boolean selfChange, Uri uri) {

       // todo

    }

};

@Override

public void onStart() {

   super.onStart();

   // register the observer

   getContentResolver().registerContentObserver(Settings.Global.getUriFor(

           xxx), false, mSettingsObserver);

}

@Override

public void onStop() {

   super.onStop();

    //unregister it when stoping

   getContentResolver().unregisterContentObserver(mSettingsObserver);

 

}

 看完示例,我们来看看病例:

private final class SettingsObserverimplements Observer {

   public void update(Observable o, Object arg) {

       // todo ...

   } 

}

mContentQueryMap = newContentQueryMap(mCursor, Settings.System.XXX, true, null);

 mContentQueryMap.addObserver(newSettingsObserver());

    靠,谁这么偷懒,把SettingObserver搞个匿名对象传进去,这可如何是好?

    所以,有些懒是不能偷的,有些语法糖是不能吃的。

    解决方案就是, 在不需要或退出的时候delete这个Observer。

private Observer mSettingsObserver;

@Override

public void onResume() {

   super.onResume();

   if (mSettingsObserver == null) {

       mSettingsObserver = new SettingsObserver();

   } 

   mContentQueryMap.addObserver(mSettingsObserver);

}

 

@Override

public void onStop() {

   super.onStop();

   if (mSettingsObserver != null) {

       mContentQueryMap.deleteObserver(mSettingsObserver);

   } 

   mContentQueryMap.close();

}

  注意一点,不同的注册方法,不同的反注册方法。

// 只是参考,不必死板

addCallback             <==>     removeCallback

registerReceiver        <==>     unregisterReceiver

addObserver             <==>     deleteObserver

registerContentObserver <==>     unregisterContentObserver

9. Dialog对象

android.view.WindowManager$BadTokenException:Unable to add window -- token android.os.BinderProxy@438afa60 is not valid; isyour activity running?

一般发生于Handler的MESSAGE在排队,Activity已退出,然后Handler才开始处理Dialog相关事情。    关键点就是,怎么判断Activity是退出了,有人说,在onDestroy中设置一个FLAG。我很遗憾的告诉你,这个错误很有可能还会出来。

    解决方案是:使用isFinishing()判断Activity是否退出。

Handler handler = new Handler() {

   public void handleMessage(Message msg) {

       switch (msg.what) {

       case MESSAGE_1:

           // isFinishing == true, 则不处理,尽快结束

           if (!isFinishing()) {

                // 不退出

                // removeDialog()

                // showDialog()

           } 

           break;

       default:

           break;

       } 

        super.handleMessage(msg);

   } 

};

  早完早释放!

10. 其它对象

    以Listener对象为主,"把自己搭进去了,切记一定要及时把自己放出来"。

0 0