内存泄漏探讨中(Android篇)

    |     2016年3月3日   |   Android经验   |     0 条评论   |    1469

内存泄漏:对象已经不用了,却被别的对象还握着引用,内存收不回去。慢慢堆积最终 OOM。写代码时就要避开这类写法,提高健壮性。

640


一、静态变量

Java 里静态变量从类加载活到类卸载;Android 里就是进程活着它就在。进程不杀,静态变量不会被回收。若它强引用了某个 Activity 的对象,Activity 即使走了 onDestroy 也释放不了(不要把执行 onDestroy 和被回收划等号)。

对策:1. 找生命周期差不多的替代对象;2. 找不到就把强引用改弱引用。典型:单例拿了 Activity 的 Context。

public class IMManager {
  private Context context;
  private static IMManager mInstance;

  public static IMManager getInstance(Context context) {
    if (mInstance == null) {
      synchronized (IMManager.class) {
        if (mInstance == null)
          mInstance = new IMManager(context);
      }
    }
    return mInstance;
  }

  private IMManager(Context context) {
    this.context = context;
  }
}

getInstance 若传入 Activity 的 context,单例不释放,Activity 也不释放。应传入 Application 的 context,生命周期与单例相当:

public class IMManager {
  private Context context;
  private static IMManager mInstance;

  public static IMManager getInstance(Context context) {
    if (mInstance == null) {
      synchronized (IMManager.class) {
        if (mInstance == null)
          //将传入的context转换成Application的context
          mInstance = new IMManager(context.getApplicationContext());
      }
    }
    return mInstance;
  }

  private IMManager(Context context) {
    this.context = context;
  }
}

二、非静态内部类

非静态内部类实例会引用外围实例。若它做了耗时操作,外围对象收不掉。对策:1. 改成静态内部类;2. 强引用 Activity 属性则改弱引用;3. 业务允许时在 onDestroy 结束耗时任务。

内部线程

public class LeakAty extends Activity {

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

  public void test() {
    //匿名内部类会引用其外围实例LeakAty.this,所以会导致内存泄漏
    new Thread(new Runnable() {
      @Override
      public void run() {
        while (true) {
          try {
            Thread.sleep(1000);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      }
    }).start();
  }
}

把方法改成 static,匿名内部类不再持有 Activity:

public class LeakAty extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.aty_leak);
    test();
  }
  //加上static,变成静态匿名内部类
  public static void test() {
    new Thread(new Runnable() {
      @Override
      public void run() {
        while (true) {
          try {
            Thread.sleep(1000);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      }
    }).start();
  }
}

Handler

public class LeakAty extends Activity {

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

  private Handler mHandler = new Handler() {
    public void handleMessage(android.os.Message msg) {
      switch (msg.what) {
      case 0:
        // 刷新数据
        break;
      default:
        break;
      }
    };
  };

  private void fetchData() {
    //获取数据
    mHandler.sendEmptyMessage(0);
  }
}

匿名 Handler 会引用 LeakAty.this;Activity 退出时若还有消息要处理,Activity 就不会被回收。正确写法:静态内部类 + 弱引用 + onDestroy 清消息。

public class LeakAty extends Activity {
  private TextView tvResult;
  private MyHandler handler;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.aty_leak);
    tvResult = (TextView) findViewById(R.id.tvResult);
    handler = new MyHandler(this);
    fetchData();
  }
  //第一步,将Handler改成静态内部类。
  private static class MyHandler extends Handler {
    //第二步,将需要引用Activity的地方,改成弱引用。
    private WeakReference<LeakAty> atyInstance;
    public MyHandler(LeakAty aty) {
      this.atyInstance = new WeakReference<LeakAty>(aty);
    }

    @Override
    public void handleMessage(Message msg) {
      super.handleMessage(msg);
      LeakAty aty = atyInstance == null ? null : atyInstance.get();
      //如果Activity被释放回收了,则不处理这些消息
      if (aty == null || aty.isFinishing()) {
        return;
      }
      aty.tvResult.setText("fetch data success");
    }
  }

  private void fetchData() {
    handler.sendEmptyMessage(0);
  }

  @Override
  protected void onDestroy() {
    //第三步,在Activity退出的时候移除回调
    super.onDestroy();
    handler.removeCallbacksAndMessages(null);
  }
}

三、资源未关闭

用了 BroadcastReceiver、File、Cursor、Bitmap 等,不用时要及时释放,否则泄漏。


四、Adapter 没用 convertView

ListView 的 BaseAdapter.getView(int position, View convertView, ViewGroup parent) 给每一项提供 View。一开始按屏幕实例化一批并缓存;向上滚时,最上面的 item View 被回收,拿去构造新出现的最下面一项。convertView 就是缓存下来的那块(初始化没有缓存时为 null)。每次 getView 都 new 一个 View,既浪费时间,也制造内存垃圾,回收跟不上就迫使 VM 再给进程加内存。


五、不良代码造成内存压力

有些代码不一定泄漏,但对没用的内存释放不及时,或频繁申请新对象,同样会把 VM 压高。Bitmap 尤其要注意:

  • 及时销毁:系统最终会回收 Bitmap,但占用太大容易超 Java 堆。用完 recycle,不能保证立刻释放,但会提示虚拟机「这张图可以放了」
  • 设采样率:显示区域很小就不必加载整图。inSampleSize = 2 时宽高各一半,像素约为原来的 1/4
private ImageView preview;
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;//图片宽高都为原来的二分之一,即图片为原来的四分之一
Bitmap bitmap = BitmapFactory.decodeStream(cr.openInputStream(uri), null, options);
preview.setImageBitmap(bitmap);

六、清单

场景 做法
静态单例 用 Application Context,或弱引用
非静态内部类 / 线程 / Handler 改静态 + 弱引用,onDestroy 停任务、清消息
Cursor / 文件 / 广播 / Bitmap 谁创建谁关闭,Activity 销毁时注销
ListView Adapter 复用 convertView
大图 recycle + inSampleSize
  • 对 Activity 等组件的引用应控制在其生命周期内;不行就 getApplicationContext / getApplication
  • 尽量不要在静态变量或静态内部类里用非静态外部成员(含 context);要用就适时置空,或弱引用
  • 生命周期比 Activity 长的内部类:改静态内部类 + 弱引用外部成员
  • Handler 持有的对象用弱引用;onStop / onDestroy 取消 Message 和 Runnable
  • 不用的对象显式赋 null;Bitmap 先 recycle 再 null;数组 clear 后 null;谁创建谁释放
  • BroadcastReceiver、ContentObserver、File、Cursor、Stream、Bitmap 在销毁时关闭或注销
  • 对单例、静态对象、全局集合的生命周期保持敏感

一句话总结:泄漏几乎都是「短命对象被长命引用抓住」:单例用 Application、内部类改静态加弱引用、资源用完就关。

转载请注明来源:内存泄漏探讨中(Android篇)
本文链接地址:https://ai.zhousir.top/?p=1358
回复 取消