博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Android 中 Handler 引起的内存泄露
阅读量:4884 次
发布时间:2019-06-11

本文共 3506 字,大约阅读时间需要 11 分钟。

在Android常用编程中,Handler在进行异步操作并处理返回结果时经常被使用。通常我们的代码会这样实现。

  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.   } 

但是,其实上面的代码可能导致内存泄露,当你使用Android lint工具的话,会得到这样的警告

In Android, Handler classes should be static or leaks might occur, Messages enqueued on the application thread’s MessageQueue also retain their target Handler. If the Handler is an inner class, its outer class will be retained as well. To avoid leaking the outer class, declare the Handler as a static nested class with a WeakReference to its outer class

看到这里,可能还是有一些搞不清楚,代码中哪里可能导致内存泄露,又是如何导致内存泄露的呢?由移动应用安全团队-爱内测(www.ineice.com)的技术工程师为我们分析下:

1.当一个Android应用启动的时候,会自动创建一个供应用主线程使用的Looper实例。Looper的主要工作就是一个一个处理消息队列中 的消息对象。在Android中,所有Android框架的事件(比如Activity的生命周期方法调用和按钮点击等)都是放入到消息中,然后加入到 Looper要处理的消息队列中,由Looper负责一条一条地进行处理。主线程中的Looper生命周期和当前应用一样长。

2.当一个Handler在主线程进行了初始化之后,我们发送一个target为这个Handler的消息到Looper处理的消息队列时,实际上 已经发送的消息已经包含了一个Handler实例的引用,只有这样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. 10.   @Override 
  11. 11.   protected void onCreate(Bundle savedInstanceState) { 
  12. 12.     super.onCreate(savedInstanceState); 
  13. 13.  
  14. 14.     // Post a message and delay its execution for 10 minutes. 
  15. 15.     mLeakyHandler.postDelayed(new Runnable() { 
  16. 16.       @Override 
  17. 17.       public void run() { /* ... */ } 
  18. 18.     }, 1000 * 60 * 10); 
  19. 19.  
  20. 20.     // Go back to the previous Activity. 
  21. 21.     finish(); 
  22. 22.   } 

23. } 

分析一下上面的代码,当我们执行了Activity的finish方法,被延迟的消息会在被处理之前存在于主线程消息队列中10分钟,而这个消息中 又包含了Handler的引用,而Handler是一个匿名内部类的实例,其持有外面的SampleActivity的引用,所以这导致了 SampleActivity无法回收,进行导致SampleActivity持有的很多资源都无法回收,这就是我们常说的内存泄露。

注意上面的new Runnable这里也是匿名内部类实现的,同样也会持有SampleActivity的引用,也会阻止SampleActivity被回收。

要解决这种问题,思路就是不适用非静态内部类,继承Handler时,要么是放在单独的类文件中,要么就是使用静态内部类。因为静态的内部类不会持有外部类的引用,所以不会导致外部类实例的内存泄露。当你需要在静态内部类中调用外部的Activity时,我们可以使用来处理。另外关于同样也需要将Runnable设置为静态的成员属性。注意:一个静态的匿名内部类实例不会持有外部类的引用。 修改后不会导致内存泄露的代码如下

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

44. } 

其实在Android中很多的内存泄露都是由于在Activity中使用了非静态内部类导致的,就像本文提到的一样,所以当我们使用时要非静态内部 类时要格外注意,如果其实例的持有对象的生命周期大于其外部类对象,那么就有可能导致内存泄露。

转载于:https://www.cnblogs.com/skytwo/p/4707177.html

你可能感兴趣的文章
IAR中 C语言位定义
查看>>
Windows 7下硬盘安装Ubuntu 14.04图文教程
查看>>
android中XML文件解析遇到“not well-formed (invalid token)”解决办法
查看>>
软件测试第二次作业:初识JUNIT的单元测试方法
查看>>
acm寒假特辑1月18日 HDU-1420(数学优化算法)
查看>>
double类型数据在内存中中存储格式
查看>>
[App Store Connect帮助]三、管理 App 和版本(2.3)输入 App 信息:提供自定许可协议...
查看>>
[Swift]LeetCode728. 自除数 | Self Dividing Numbers
查看>>
macOS 10.13 安装Virtualbox失败
查看>>
正则表达式之我见—后向引用
查看>>
[Cypress] Test XHR Failure Conditions with Cypress
查看>>
[RxJS] Completing a Stream with TakeWhile
查看>>
一、基础篇--1.1Java基础-MVC设计思想
查看>>
将eclipse的应用程序打包成.exe
查看>>
Apache mod_fcgid fcgid_header_bucket_read函数缓冲区溢出漏洞
查看>>
hdu1255 覆盖面积
查看>>
公开课平台推荐
查看>>
从1到n整数中1出现的次数
查看>>
【Mysql】mysql乐观锁总结和实践
查看>>
poj2728 Desert King
查看>>