免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1446 | 回复: 1
打印 上一主题 下一主题

[Android] Android防止内存泄漏以及MAT的使用 [复制链接]

论坛徽章:
80
20周年集字徽章-庆
日期:2020-10-28 14:09:1215-16赛季CBA联赛之北京
日期:2020-10-28 13:32:5315-16赛季CBA联赛之北控
日期:2020-10-28 13:32:4815-16赛季CBA联赛之天津
日期:2020-10-28 13:13:35黑曼巴
日期:2020-10-28 12:29:1520周年集字徽章-周	
日期:2020-10-31 15:10:0720周年集字徽章-20	
日期:2020-10-31 15:10:07ChinaUnix元老
日期:2015-09-29 11:56:3020周年集字徽章-年
日期:2020-10-28 14:14:56
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2015-09-09 13:50 |只看该作者 |倒序浏览
Android发生内存泄漏最普遍的一种情况就是长期保持对Context,特别是Activity的引用,使得Activity无法被销毁。这也就意味着Activity中所有的成员变量也没办法销毁。本文仅介绍如何避免这种情况的发生,其他如Bitmap没有及时回收导致的OOM异常暂不讨论。

一、防止内存泄漏
什么情况下会长时间保持对某个Activity的引用呢?主要有以下两种情况:

1、某个static变量保持对Activity的引用

2、线程保持Activity的引用

由于静态变量是长驻内存的,甚至在你调用了exit方法后仍不会被销毁。而线程的生命周期是无法预料的。一旦发生了切屏,Activity就无法被销毁。下面来看一个简单的例子:
  1. public class MainActivity extends Activity {

  2.         @Override
  3.         protected void onCreate(Bundle savedInstanceState) {
  4.                 super.onCreate(savedInstanceState);
  5.                 setContentView(R.layout.activity_main);
  6.         }
  7.        
  8.         public void onClick(View v){
  9.                 new MyThread().start();
  10.         }
  11.        
  12.         class MyThread extends Thread{
  13.                 public void run(){
  14.                         try {
  15.                                 Thread.sleep(60000);
  16.                         } catch (InterruptedException e) {
  17.                                 // TODO Auto-generated catch block
  18.                                 e.printStackTrace();
  19.                         }
  20.                 }
  21.         }
  22. }
复制代码
线程MyThread是Activity的内部类,这有什么问题呢?我们知道,内部类是会持有外部类的引用的,这就是上面说到的第二种情况。那么怎么优化,使线程不持有Activity的引用呢?熟悉Java的马上就会想到静态内部类,没错,只要在定义MyThread的时候加一个static,这样这个内部类就不会持有外部类的引用了。
然而在实际应用中,线程的问题往往要比这复杂很多。我们可能还要用到AsyncTask或者Handler加Thread来异步获取数据并刷新界面。此时如果只是简单的将AsyncTask或者Handler定义成静态的,那么无法实现刷新界面的功能。之前的做法是使用弱引用(WeakReference),但是2.3以后的Android加强了对弱引用及软引用的回收,如果在内存不是很足的情况下,很可能导致你的引用过早被系统回收,而无法做刷新界面的操作。接下来的这个例子将提供一种新的解决方案:
  1. public class MainActivity extends Activity {  
  2.     private MyThread t;  
  3.     private String TAG = "tag";  
  4.   
  5.     @Override  
  6.     protected void onCreate(Bundle savedInstanceState) {  
  7.         super.onCreate(savedInstanceState);  
  8.         setContentView(R.layout.activity_main);  
  9.     }  
  10.       
  11.     @Override  
  12.     protected void onDestroy() {  
  13.         if(t != null){  
  14.             t.detach();  
  15.         }  
  16.         super.onDestroy();  
  17.     }  
  18.       
  19.     public void onClick(View v){  
  20.         if(t != null){  
  21.             t.detach();  
  22.         }  
  23.         t = new MyThread();  
  24.         Callback callback = new Callback(){  
  25.   
  26.             @Override  
  27.             public void finish() {  
  28.                 Log.v(TAG, "finish");  
  29.             }  
  30.               
  31.         };  
  32.         t.attach(callback);  
  33.         t.start();  
  34.     }  
  35.       
  36.     static interface Callback{  
  37.         void finish();  
  38.     }  
  39.       
  40.     static class MyThread extends Thread {  
  41.         Callback callback;  
  42.          
  43.         void attach(Callback callback){  
  44.             this.callback = callback;  
  45.         }  
  46.          
  47.         void detach(){  
  48.             if(callback != null){  
  49.                 callback = null;  
  50.             }  
  51.         }  
  52.          
  53.         public void run(){  
  54.             try {  
  55.                 Thread.sleep(60000);  
  56.             } catch (InterruptedException e) {  
  57.                 // TODO Auto-generated catch block  
  58.                 e.printStackTrace();  
  59.             }  
  60.             if(callback != null){  
  61.                 callback.finish();  
  62.             }  
  63.         }  
  64.     }  
  65. }
复制代码
第二个例子在第一个例子的基础上加了回调,如果是用AsyncTask或者Handler的话,可以在回调内做更新界面的操作。最关键的部分在于线程的detach方法,它将持有Activity引用的回调成员置空。那么我们只要保证在Activity被销毁前,所有持有该引用的变量都能被置空,这样,长时间持有Activity引用的条件也就不成立了。所以可以看到在Activity的onDestroy方法和new一个线程之前,都要将现有线程的回调置空。
二、使用MAT检测内存泄漏
MAT是eclipse的内存分析工具。接下来我会从安装开始,并结合上面的例子来简单介绍一下如何使用这个工具。

1、安装

Help--> Install New Software --> Add,链接是http://download.eclipse.org/mat/1.3/update-site/,最好要勾选“Contact all update sites...”这个选项,不然你的eclipse可能没有这个插件依赖的一些包。点击确定就等它龟速下载吧。安装完之后重启eclipse就可以用了。

2、使用

这个工具可以结合DDMS来用。先将我们的程序跑起来,然后打开DDMS,选择要分析的程序,点击Dump HPROF File按钮,稍等片刻就可以看到内存的详细使用情况了。



3、结合上面的例子分析

我们首先看一下不置空回调的情况,把ondestroy和new线程那部分代码注释掉。运行程序之后打开DDMS,并开启Update Heap(为了能使用GC)和Update Thread(查看线程)。之后我们运行线程,并切换横屏,如下图:




可以看到此时多了一个线程(图中ID为9)。打开MAT,点击"Open Dominator Tree for entrie heap",最后再点开“Show as Histogram”,在这里就可以查看我们各个类的引用情况了。如下图:


可以看到此时的MainActivity有两个引用。返回到DDMS,点击“Cause GC”(那个垃圾桶图标),打开新的MAT,继续找MainActivity,如果没有意外出现的话,MainActivity的引用仍为2,也就是说,并没有回收前一个Activity。

但是如果取消刚才的注释,同样的操作,点击GC之后,发现MainActivity的引用又变回1了。

注意:以上操作最好在线程执行完毕之前完成,不熟悉的可以将线程执行时间设置长一些来看效果。

三、建议
1、尽量不要长时间保持Activity的引用。

2、尝试用Application的context来代替Activity的context。因为Application是存在于程序的整个生命周期的,不像Activity一样随时有可能被销毁。

3、避免使用静态的持有Activity引用的成员变量,巧妙使用静态内部类。

4、适当运用弱引用。

5、如果确实需要保持对Activity的引用,必须确保在Activity的生命周期结束前,取消该引用。

论坛徽章:
80
20周年集字徽章-庆
日期:2020-10-28 14:09:1215-16赛季CBA联赛之北京
日期:2020-10-28 13:32:5315-16赛季CBA联赛之北控
日期:2020-10-28 13:32:4815-16赛季CBA联赛之天津
日期:2020-10-28 13:13:35黑曼巴
日期:2020-10-28 12:29:1520周年集字徽章-周	
日期:2020-10-31 15:10:0720周年集字徽章-20	
日期:2020-10-31 15:10:07ChinaUnix元老
日期:2015-09-29 11:56:3020周年集字徽章-年
日期:2020-10-28 14:14:56
2 [报告]
发表于 2015-09-17 09:36 |只看该作者
转给有需要的人
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP