“内存泄漏”就是一个对象已经不需要再使用了,但是因为其它的对象持有该对象的引用,导致它的内存不能被回收。“内存泄漏”的慢慢积累,最终会导致OOM “内存溢出”的发生,千里之堤,毁于蚁穴。所以在写代码的过程中,应该要注意规避会导致“内存泄漏”的代码写法,提高软件的健壮性。
一、常见导致“内存泄漏”的代码写法及解决方案
1.静态变量引起的内存泄漏
在java中静态变量的生命周期是在类加载时开始,类卸载时结束。换句话说,在android中其生命周期是在进程启动时开始,进程死亡时结束。所以在程序的运行期间,如果进程没有被杀死,静态变量就会一直存在,不会被回收掉。如果静态变量强引用了某个Activity中变量,那么这个Activity就同样也不会被释放,即便是该Activity执行了onDestroy(不要将执行onDestroy和被回收划等号)。
这类问题的解决方案为:1.寻找与该静态变量生命周期差不多的替代对象。2.若找不到,将强引用方式改成弱引用。
比较典型的例子如下:
单例引起的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时,如果传入的context是Activity的context。只要这个单例没有被释放,这个Activity也不会被释放。
解决方案
传入Application的context,因为Application的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; } }
2、非静态内部类引起的内存泄漏
在java中,创建一个非静态的内部类实例,就会引用它的外围实例。如果这个非静态内部类实例做了一些耗时的操作,就会造成外围对象不会被回收,从而导致内存泄漏。
这类问题的解决方案为:1.将内部类变成静态内部类 2.如果有强引用Activity中的属性,则将该属性的引用方式改为弱引用。3.在业务允许的情况下,当Activity执行onDestory时,结束这些耗时任务。
内部线程造成的内存泄漏
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(); } }
解决方案
将非静态匿名内部类修改为静态匿名内部类
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); } }
mHandler 为匿名内部类实例,会引用外围对象LeakAty.this,如果该Handler在Activity退出时依然还有消息需要处理,那么这个Activity就不会被回收。
解决方案
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); } }
3、资源未关闭引起的内存泄漏
当使用了BraodcastReceiver、File、Cursor、Bitmap等资源时,当不需要使用时,需要及时释放掉,若没有释放,则会引起内存泄漏。
4、构造Adapter时,没有使用缓存的 convertView
以构造ListView的BaseAdapter为例,在BaseAdapter中提共了方法:
public View getView(intposition, View convertView, ViewGroup parent)
来向ListView提供每一个item所需要的view对象。初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的view对象,同时ListView会将这些view对象缓存起来。当向上滚动ListView时,原先位于最上面的list item的view对象会被回收,然后被用来构造新出现的最下面的list item。这个构造过程就是由getView()方法完成的,getView()的第二个形参 View convertView就是被缓存起来的list item的view对象(初始化时缓存中没有view对象则convertView是null)。
由此可以看出,如果我们不去使用convertView,而是每次都在getView()中重新实例化一个View对象的话,即浪费时间,也造成内存垃圾,给垃圾回收增加压力,如果垃圾回收来不及的话,虚拟机将不得不给该应用进程分配更多的内存,造成不必要的内存开支。
5、一些不良代码成内存压力
有些代码并不造成内存泄露,但是它们或是对没使用的内存没进行有效及时的释放,或是没有有效的利用已有的对象而是频繁的申请新内存,对内存的回收和分配造成很大影响的,容易迫使虚拟机不得不给该应用进程分配更多的内存,增加vm的负担,造成不必要的内存开支。
如Bitmap使用不当
第一、及时的销毁。
虽然,系统能够确认Bitmap分配的内存最终会被销毁,但是由于它占用的内存过多,所以很可能会超过Java堆的限制。因此,在用完Bitmap时,要及时的recycle掉。recycle并不能确定立即就会将Bitmap释放掉,但是会给虚拟机一个暗示:“该图片可以释放了”。
第二、设置一定的采样率。
有时候,我们要显示的区域很小,没有必要将整个图片都加载出来,而只需要记载一个缩小过的图片,这时候可以设置一定的采样率,那么就可以大大减小占用的内存。如下面的代码:
private ImageView preview; BitmapFactory.Options options = newBitmapFactory.Options(); options.inSampleSize = 2;//图片宽高都为原来的二分之一,即图片为原来的四分之一 Bitmap bitmap =BitmapFactory.decodeStream(cr.openInputStream(uri), null, options); preview.setImageBitmap(bitmap);
总结
- 对 Activity 等组件的引用应该控制在 Activity 的生命周期之内; 如果不能就考虑使用 getApplicationContext 或者 getApplication,以避免 Activity 被外部长生命周期的对象引用而泄露。
- 尽量不要在静态变量或者静态内部类中使用非静态外部成员变量(包括context ),即使要使用,也要考虑适时把外部成员变量置空;也可以在内部类中使用弱引用来引用外部类的变量。
- 对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:
- 将内部类改为静态内部类
- 静态内部类中使用弱引用来引用外部类的成员变量
- Handler 的持有的引用对象最好使用弱引用,资源释放时也可以清空 Handler 里面的消息。比如在 Activity onStop 或者 onDestroy 的时候,取消掉该 Handler 对象的 Message和 Runnable.
- 在 Java 的实现过程中,也要考虑其对象释放,最好的方法是在不使用某对象时,显式地将此对象赋值为 null,比如使用完Bitmap 后先调用 recycle(),再赋为null,清空对图片等资源有直接引用或者间接引用的数组(使用 array.clear() ; array = null)等,最好遵循谁创建谁释放的原则。
- 正确关闭资源,对于使用了BraodcastReceiver,ContentObserver,File,游标 Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销。
- 保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。