Android中内存泄露的常见场景以及优化

【概念】Java中,当一个不再需要被使用的对象仍被其他对象所引用时,会造成该对象无法被gc及时回收,所占用的内存空间无法释放,从而导致内存单元的浪费。

 

Android开过程中,一些不合理的开发方式会导致app存在内存泄露的情况,导致app性能下降,严重时会产生crash。下面介绍Android几种常见的内存泄露场景,以及优化方案。

 

单例导致的内存泄露

【问题】单例模式在Android开发过程中会经常用到,但是对于单例的使用不当可能会造成内存泄露。因为单例的静态特性,使得它的生命周期与application是一致的,如果单例对象持有了不再需要使用的对象的引用,那么在整个application生命周期中这个对象都不会被释放,从而造成内存泄露。

public class AppSettings {
private static AppSettings sInstance; 
private Context mContext;

private AppSettings(Context context) { 
  this.mContext = context; } public static AppSettings getInstance(Context context) { if (sInstance == null) { sInstance = new AppSettings(context); } return sInstance; } }

例如以上代码中,单例的getInstance方法传入了context的引用,该context指向的对象一般为Activity、Service等上下文,这时就会导致在整个application的生命周期内,该单例所持有的context无法释放,进一步导致Activity/Service内部资源以及视图无法被释放,严重影响app运行过程中的性能表现。

 

【解决】为了避免这样的单例造成的内存泄露,可以改成

private AppSettings(Context context) {
     this.mContext = context.getApplicationContext();
}

全局context对应的本身就是应用程序的上下文,和单例的生命周期保持一致,这样就避免了内存泄露。

 

静态变量导致的内存泄露 

【问题】静态变量存储在方法区(Method Area),它的生命周期从类加载开始,一直持续到进程结束。一旦静态变量初始化,那么它所持有的引用就只有等到进程结束才会释放。

 

比如,在Activity中为了避免重复创建Info,将sInfo声明为静态变量:

public class MainActivity extends AppCompatActivity {
  private static Info sInfo;
  @Override   
protected void onCreate(Bundle savedInstanceState) {     super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    if (sInfo != null) {       sInfo = new Info(this);
    }   }
}

sInfo作为Activity的静态成员变量,持有Activity实例的引用,同时生命周期比Activity要长,在Activity退出时sInfo仍然引用了Activity,导致Activity无法及时释放,造成内存泄露。

 

【解决】在开发过程中,静态成员变量的生命周期很容易与其使用方生命周期不一致,为避免泄露,可尽量减少静态变量的使用,或者提供api回调,在使用方生命周期结束时将静态变量的引用指向null。

 

非静态内部类导致的内存泄露

【问题】非静态内部类,也就是匿名内部类,默认会持有其外部类的引用,当非静态内部类的生命周期长于外部类时,也会造成内存泄露。

 

Android开发过程中,非静态内部类的一个典型使用场景就是Handler,例如:

public class MainActivity extends AppCompatActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.activity_main);
    start();   }
  
private void start() {     Message msg = Message.obtain();
    msg.what = 1;
    mHandler.sendMessage(msg);   }   
private Handler mHandler = new Handler() {
    @Override     
public void handleMessage(Message msg) {
      if (msg.what == 1) {       // do something
      
}
    }
  }}; }

通过Handler的消息机制我们可以知道,Handler的引用会作为成员变量保存在msg中,也就是msg持有对Hanlder的引用,而Handler作为匿名内部类持有外部类Activity的引用,也就相当于发出的msg间接持有了Activity的引用。msg对象被发送到MessageQueue中,等待Looper的轮询处理,若Activity退出时,MessageQueue中仍存在未处理的msg,那么此时Activity就无法及时被释放,从而造成内存泄露。

 

【解决】通常在Android开发过程中,如果要使用内部类,那么可以采取静态内部类+弱引用的方式。

public class MainActivity extends AppCompatActivity {
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mHandler = new MyHandler(this); start();
}
private void start() {   Message msg = Message.obtain(); msg.what = 1; mHandler.sendMessage(msg); }
private static class MyHandler extends Handler {   private WeakReference<MainActivity> activityWeakReference;   public MyHandler(MainActivity activity) {
    activityWeakReference = new WeakReference<>(activity);   }
  @Override   
public void handleMessage(Message msg) {     MainActivity activity = activityWeakReference.get();
    if (activity != null) {       if (msg.what == 1) {
        // do something       }
    }   }
 } }

Handler通过弱引用的方式持有Activity的引用,这样在GC进行回收时,Activity占用的内存可以及时释放,从而避免了内存泄露。

当然,这样的做法是依赖GC的回收时机的,在Activity退出到GC到达之间的这段时间内,还是会有不必要的内存占用,所以最完善的做法是在Activity销毁时,将Handler中的msg全部都移除掉,不再进行处理:

@Overrideprotected void onDestroy() {
     super.onDestroy(); 
    mHandler.removeCallbacksAndMessages(null);
}

匿名内部类的使用还有Thread,回调接口Listener等等,可使用类似的方法进行代码优化,避免不规范的内存使用。

 

未取消注册或回调造成的内存泄露

【问题】比如在Activity中动态注册广播监听,如果在Activity销毁时未进行unregister,那么这么广播会一直存在于系统中,并且持有Activity的引用,从而造成内存泄露。

【解决】注册与取消注册一定要成对使用。

public class MainActivity extends AppCompatActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); this.registerReceiver(mReceiver, new IntentFilter());
  }
  
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
    @Override     
public void onReceive(Context context, Intent intent) {
      // 接收到广播需要做的逻辑     }
  };
  @Override   
protected void onDestroy() {     super.onDestroy();     this.unregisterReceiver(mReceiver);
  } }

在观察者模式的使用中,同样需要注意注册与取消注册的问题,比如RxJava+Retrofit的Observeble使用。

 

Timer和TimerTask造成的内存泄露

【问题】Timer和TimerTask一般会用于一些与时间相关的任务,比如循环滚动的ViewPager、倒计时相关的业务逻辑、轮询请求接口实现实时刷新等。如果Activity在销毁时,其成员变量Timer/TimerTask中的计时任务并没有完成,仍然持有Activity的引用,这时也会造成内存泄露。

【解决】在Activity#onDestroy()中将所有Timer/TimerTask成员进行cancel,可有效避免内存泄露。

 

集合保存的对象未及时清除导致的内存泄露

【问题】同样的生命周期不一致的问题,如果Collection/Map中保存的item不再需要使用,而集合仍然持有它们的引用,这时也会造成内存不合理占用。

【解决】这个问题需要关注的就是Collection/Map的生命周期,使用方在对其保存的items不再使用的时候需要及时clear/remove掉。

 

资源未及时关闭导致的内存泄露

【问题】文件存取的I/O操作、Stream流操作,数据库读取的Cursor操作,bitmap的读写操作,TypedArray的attr属性读取操作等,未及时进行关闭或者释放,也会造成内存泄露。

【解决】在业务操作结束的时候,及时释放相关的资源,避免占用内存空间,这些操作可大可小,很容易长时间累积之后OOM,需特别注意。

 

属性动画造成的内存泄露

【问题】与Timer类似,属性动画也是一个耗时任务,在Activity销毁时如果还有执行中的属性动画,同样会造成内存泄露。

【解决】在Activity销毁时对Activity中的属性动画执行canel操作。

@Overrideprotected void onDestroy() { 
    super.onDestroy(); 
    mAnimator.cancel();
}

 

WebView造成的内存泄露

【问题1】WebView在加载页面时,会长期占用内存而不能被释放,因为WebView的网络请求由内核实现,所以app端无法对其进行控制。

【解决】同大多数操作一样,在Activity销毁时调用WebView#destroy()。

【问题2】在Android 5.1版本以后,WebView中添加的Callback会持有Activity的引用,从而造成即使调用了destroy()方法,也无法释放WebView占用的内存。

【解决】在销毁WebView之前,先把WebView从其父容器之中移除。具体分析见:http://blog.csdn.net/xygy8860/article/details/53334476

@Overrideprotected void onDestroy() { 
    super.onDestroy();
    // 先从父控件中移除 WebView             
    mWebViewContainer.removeView(mWebView);     
    mWebView.stopLoading();     
    mWebView.getSettings().setJavaScriptEnabled(false); 
    mWebView.clearHistory();
    mWebView.removeAllViews();
    mWebView.destroy(); 
}

 

总结

内存泄露在 Android 内存优化是一个比较重要的一个方面,很多时候程序中发生了内存泄露我们 不一定就能注意到,所有在编码的过程要养成良好的习惯。总结下来只要做到以下这几点就能避 免大多数情况的内存泄漏:

1. 单例的构造尽量不要依赖具体的Activity,两边的生命周期不一致。

2. 减少静态引用的使用,必要时在使用方生命周期结束回调中将静态变量置空。

3. 使用静态内部类+弱引用的方式代替非静态内部类。

4. 注册/取消注册广播或者观察者需要成对出现,并且对应使用方的生命周期。

5. Timer/TimerTask、ObjectAnimator及时取消。

6. I/O stream、File、bitmap、TypedArray及时关闭或者回收。

7. Activity销毁时对WebView进行完整的销毁。

 

posted @ 2019-11-11 11:09  白发迷途。  阅读(621)  评论(0)    收藏  举报