美文网首页
内存泄漏详解

内存泄漏详解

作者: Skypew | 来源:发表于2017-11-14 10:27 被阅读36次

造成 Android 内存泄漏的最根本原因就是生命周期较长的对象持有生命周期较短的对象的引用

通常内存泄漏 有以下几种情况

1.单例模式引用context 对象

这里有 Context 对象,如果 Activity 中调用 getInstance 方法并传入 this 时, 就持有了此 Activity 的引用,当退出 Activity 时,Activity 就无法回收,造成内存泄漏,所以应该修改它的构造方法:为context.getApplicationContext()
通过 getApplicationContext 来获取 Application 的 Context,让它被单例持有,这样退出 Activity 时,Activity 对象就能正常被回收了,而 Application 的 Context 的生命周期和单例的生命周期是一致的,所有再整个 App 运行过程中都不会造成内存泄漏。

 public static LocalBroadcastManager getInstance(Context context) {
        synchronized (mLock) {
            if (mInstance == null) {
                mInstance = new LocalBroadcastManager(context.getApplicationContext()); //不要直接使用context
            }
            return mInstance;
        }
    }
也可以: onDestroy 中取消 单例的绑定

例子:

@Override
  protected void onDestroy() {
    super.onDestroy();
    NastyManager.getInstance().removeListener(this);
  }

2.非静态内部类造成的内存泄漏

public class MainActivity extends AppCompatActivity { 

    private static Test test; 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_main); 
        if (test == null) { 
            test = new Test(); 
 //由于静态对象 test 的生命周期和整个应用的生命周期一致,
 //而非静态内部类 Test 持有外部类 MainActivity 的引用,
 //导致 MainActivity 退出的时候不能被回收,从而造成内存泄漏
        } 
    } 

    private class Test { 

    } 

}
解决方案:

解决的方法也很简单,把 test 改成非静态,这样 test 的生命周期和 MainActivity 是一样的了,就避免了内存泄漏。或者也可以把 Test 改成静态内部类,让 test 不持有 MainActivity 的引用,不过一般没有这种操作。

3.Handler 或 Runnable 作为非静态内部类

handler 和 runnable 都有定时器的功能,当它们作为非静态内部类的时候,同样会持有外部类的引用,如果它们的内部有延迟操作,在延迟操作还没有发生的时候,销毁了外部类,那么外部类对象无法回收,从而造成内存泄漏

public class MainActivity extends AppCompatActivity {  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  

      //Handler 和 Runnable 作为匿名内部类,都会持有 MainActivity 的引用
//而它们内部有一个 10 秒钟的定时器,如果在打开 MainActivity 的 10 秒内关闭了 MainActivity,
//那么由于 Handler 和 Runnable 的生命周期比 MainActivity 长,会导致 MainActivity 无法被回收,从而造成内存泄漏。

        new Handler().postDelayed(new Runnable() {  
            @Override  
            public void run() {  

            }  
        }, 10 * 1000);  
    }  
}
解决方案:

一般套路就是把 Handler 和 Runnable 定义为静态内部类,这样它们就不再持有 MainActivity 的引用了

public class MainActivity extends AppCompatActivity {  

    private Handler handler;  

    private Runnable runnable;  

    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.activity_main);  

        handler = new TestHandler();  
        runnable = new TestRunnable();  
        handler.postDelayed(runnable, 10 * 1000);  
    }  

    //静态内部类
    private static class TestHandler extends Handler {  

    }  

    //静态内部类
    private static class TestRunnable implements Runnable {  
        @Override  
        public void run() {  
            Log.d(TAG, "run: ");  
        }  
    }  

    private static final String TAG = "MainActivity";  
}

// onDestory 调用 handler 的 removeCallbacks 方法来移除 Message,这样不但能避免内存泄漏,而且在退出 Activity 时取消了定时器,保证 10 秒以后也不会执行 run
@Override  
protected void onDestroy() {  
    super.onDestroy();  
    handler.removeCallbacks(runnable);  
}

特殊情况

如果 Handler 或者 Runnable 中持有 Context 对象,那么即使使用静态内部类,还是会发生内存泄漏:
public class MainActivity extends AppCompatActivity { 

    private Handler handler; 

    private Runnable runnable; 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_main); 
      
       //这里 我们又让其持有 MainActivity .this的引用
        handler = new TestHandler(this); 

        runnable = new TestRunnable(); 
        handler.postDelayed(runnable, 10 * 1000); 
    } 

//这是由于在 Handler 中持有 Context 对象,而这个 Context 对象是通过 TestHandler 的构造方法传入的,它是一个 MainActivity 对象,也就是说,虽然 TestHandler 作为静态内部类不会持有外部类 MainActivity 的引用,
//但是我们在调用它的构造方法时,自己传入了 MainActivity 的对象,从而 handler 对象持有了 MainActivity 的引用,handler 的生命周期比 MainActivity 的生命周期长,因此会造成内存泄漏,
    private static class TestHandler extends Handler { 
        private Context context; 
        private TestHandler(Context context) { 
            this.context = context; 
        } 
    } 

    private static class TestRunnable implements Runnable { 
        @Override 
        public void run() { 
            Log.d(TAG, "run: "); 
        } 
    } 

    private static final String TAG = "MainActivity"; 
}

解决方案

使用弱引用的方式来引用 Context 来避免内存泄漏,代码如下:

public class MainActivity extends AppCompatActivity { 

    private Handler handler; 

    private Runnable runnable; 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_main); 
     
         //使用 弱引用 的方式 
        handler = new TestHandler(new WeakReference<Context>(this)); 
        runnable = new TestRunnable(); 
        handler.postDelayed(runnable, 10 * 1000); 
    } 

    private static class TestHandler extends Handler { 
        private Context context; 
        private TestHandler(WeakReference<Context> weakContext) { 
            context = weakContext.get(); 
        } 
    } 

    private static class TestRunnable implements Runnable { 
        @Override 
        public void run() { 
            Log.d(TAG, "run: "); 
        } 
    } 

    private static final String TAG = "MainActivity"; 
} 

4.AsyncTask造成内存泄漏

Activity2 中创建了一个匿名类AsyncTask,匿名类和非静态内部类相同,会持有外部类对象,这里也就是activity,因此如果你在Activity里声明且实例化一个匿名的AsyncTask对象,则可能会发生内存泄漏,如果这个线程在Activity销毁后还一直在后台执行,那这个线程会继续持有这个Activity的引用从而不会被GC(垃圾回收机制)回收,直到线程执行完成

public class Activity2 extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_2);

    findViewById( R.id.finish2).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        finish();
      }
    });

 //匿名类和非静态内部类相同,会持有外部类对象
    new AsyncTask<String,Integer,String>(){

      @Override
      protected String doInBackground(String... params) {
        try {
          Thread.sleep( 6000 );
        } catch (InterruptedException e) {
        }
        return "ssss";
      }

      @Override
      protected void onPostExecute(String s) {
        super.onPostExecute(s);
        Log.d( "mmmmmm activity2 " , "" + s ) ;
      }

    }.executeOnExecutor( AsyncTask.THREAD_POOL_EXECUTOR , "" ) ;

  }
}

解决方案

自定义静态AsyncTask类AsyncTask的周期和Activity周期保持一致。也就是在Activity生命周期结束时要将AsyncTask cancel掉

public class AsyncTaskActivity extends AppCompatActivity {

  private static MyTask myTask ;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_asynctask);

    findViewById( R.id.finish).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        finish();
      }
    });

    myTask = new MyTask() ;
    myTask.executeOnExecutor( AsyncTask.THREAD_POOL_EXECUTOR , "") ;

  }

  private static class MyTask extends AsyncTask{

    @Override
    protected Object doInBackground(Object[] params) {
      try {
        //模拟耗时操作
        Thread.sleep( 15000 );
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      return "";
    }
  }

  @Override
  protected void onDestroy() {
    super.onDestroy();

    //取消异步任务
    if ( myTask != null ){
      myTask.cancel(true ) ;
    }
  }
}

5.Timer Tasks 造成内存泄漏

public class TimerActivity extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_2);

    findViewById( R.id.finish2).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        finish();
      }
    });

    //开始定时任务
    timer();
  }

  void timer(){
    new Timer().schedule(new TimerTask() {
      @Override
      public void run() {
        while(true);
      }
    },1000 ); // 1秒后启动一个任务
  }
}

解决方案

在适当的时机进行Cancel。 TimerTask用静态内部类

public class TimerActivity extends AppCompatActivity {

  private TimerTask timerTask ;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_2);

    findViewById( R.id.finish2).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        finish();
      }
    });

    //开始定时任务
    timer();
  }

  void timer(){
    timerTask = new MyTimerTask() ;
    new Timer().schedule( timerTask ,1000 ); // 1秒后启动一个任务
  }
//采用静态内部类
  private static class MyTimerTask extends TimerTask{

    @Override
    public void run() {
      while(true){
        Log.d( "ttttttttt" , "timerTask" ) ;
      }
    }
  }

  @Override
  protected void onDestroy() {
    super.onDestroy();

    //取消定时任务
    if ( timerTask != null ){
      timerTask.cancel() ;
    }
  }
}

6.其他内存泄漏情况

其他一些情况参考 http://blog.csdn.net/qq_32969313/article/details/51669211

还有一些其他的会导致内存泄漏的情况,比如 BraodcastReceiver 未取消注册,InputStream 未关闭等,这类内存泄漏非常简单,只要在平时写代码时多多注意即可避免。

在 MVP 的架构中,通常 Presenter 要同时持有 View 和 Model 的引用,如果在 Activity 退出的时候,Presenter 正在进行一个耗时操作,那么 Presenter 的生命周期会比 Activity 长,导致 Activity 无法回收,造成内存泄漏:

在 onDestory 方法中把 presenter 中的 view 对象置为空就可以了:

@Override protected void onDestroy() { 
    super.onDestroy(); 
    presenter.detachView(); 
} 
public void detachView() { 
    view = null; 
}

也就是在退出 Activity 的时候,让 Presenter 不再持有 Activity 的引用,避免了内存泄漏。

相关文章

  • 内存泄漏详解

    造成 Android 内存泄漏的最根本原因就是生命周期较长的对象持有生命周期较短的对象的引用 通常内存泄漏 有以下...

  • Swift 中的weakSelf

    Swift内存泄漏详解([weak self]使用场景)https://www.jianshu.com/p/cb4...

  • 【Android测试】内存泄漏检测 LeakCanary

    什么是内存泄漏和内存溢出?内存泄漏有什么危害?LeakCanary检测内存泄漏? 内存泄漏(Memory Leak...

  • Android内存泄漏详解

    一、Android内存泄漏的本质原因 我们经常看到的说法是,安卓内存泄漏是因为长生命周期的对象持有了短生命周期的引...

  • 为何每次用完ThreadLocal都要调用remove()?

    什么是内存泄漏 Key 的泄漏 Value 的泄漏 如何避免内存泄露 什么是内存泄漏 内存泄漏指的是,当某一个对象...

  • Android如何打造高质量的应用?( 三)

    内存泄漏内存泄漏简单来说就是没有回收不再使用的内存,排查和解决内存泄漏也是内存优化无法避开的工作之一。很多内存泄漏...

  • Android内存泄漏场景及解决方法

    本文包括以下内容: 内存泄漏原理 Android内存泄漏发生的情况 检测内存泄漏的工具、方法 如何避免内存泄漏 更...

  • 内存泄漏

    什么是内存泄漏引起内存泄漏的原因野指针,空指针,僵尸对象 1.什么是内存泄漏 内存泄漏(Memory Leak)是...

  • 性能优化——内存泄漏(3)代码分析篇

    内存泄漏系列文章:性能优化——内存泄漏(1)入门篇性能优化——内存泄漏(2)工具分析篇性能优化——内存泄漏(3)代...

  • 性能优化——内存泄漏(2)工具分析篇

    内存泄漏系列文章:性能优化——内存泄漏(1)入门篇性能优化——内存泄漏(2)工具分析篇性能优化——内存泄漏(3)代...

网友评论

      本文标题:内存泄漏详解

      本文链接:https://www.haomeiwen.com/subject/vvlxvxtx.html