Android 学习笔记--内存泄漏

Author Avatar
ChihoPang 11月 02, 2017
  • 在其它设备中阅读本文章

什么是内存泄漏

由于 Java GC 机制通过对象是否可达(即 Roots 对象与目标对象直接是否存在引用链)判断对象是否可回收,所以,当对象已经不再使用,但仍为可达对象(存在引用链)时,GC 并不会回收这些对象,即发生内存泄漏的情况。

img

GC Roots 对象包括以下几种:

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象
  2. 方法区中类静态属性引用的对象
  3. 方法区中常亮引用的对象
  4. 本地方法栈中 JNI (Native 方法)引用的对象

Android 中常见的内存泄漏

静态变量导致的内存泄漏

public class MainActivity extends Activity{
    private static Context sContext;//①
    private static View sView;//②

    @Override protected void onCreate(Bundle state){
        super.onCreate(state);
        setContentView(R.layout.activity_main);
        sContext=this;
        sView=new View(this);
    }
}

上述代码中,静态变量 1、2 均有可能导致内存泄漏。
原因是静态变量的生命周期长于实例,当 activity 生命周期结束后,由于 sContext 与 sView 均持有 activity 的引用,导致 activity 对象无法释放。

单例模式导致的内存泄漏

由于单例的生命周期与应用生命周期一样长,但若持有某些特定生命周期的对象(如 activity),则会导致内存泄漏。
如下面展示的例子中,若传入 activity 构建 instance,当 activity 生命周期结束后,由于单例类仍持有 activity 的引用,导致 activity 对象无法释放。故应传入 ApplicationContext 作为 构建 instance 的 context。

public class AppManager {
    private static AppManager instance;
    private Context context;
    private AppManager(Context context) {
        this.context = context;
    }
    public static AppManager getInstance(Context context) {
        if (instance != null) {
            instance = new AppManager(context);
        }
        return instance;
    }
}

非静态内部类创建静态实例造成的内存泄漏

public class MainActivity extends AppCompatActivity {
    private static TestResource sResource = null;
    @Override protected void onCreate(Bundle state) {
        super.onCreate(state);
        setContentView(R.layout.activity_main);
        if(mManager == null){
            mManager = new TestResource();
        }
        //...
    }
    class TestResource {
        //...
    }
}

由于静态变量 sResource 生命周期与应用生命周期一样长,但 TestResource 类为非静态内部类,其实例会持有外部引用(activity)且生命周期可能长于 activity,故导致内存泄漏。

Handler造成的内存泄漏

public class SampleActivity extends Activity {
  private final Handler mLeakyHandler = new Handler() {
         @Override public void handleMessage(Message msg) {
           // ...
         }
  }

  @Override protected void onCreate(Bundle state) {
         super.onCreate(state);
         //发送一个消息,并延时 10 分钟执行
         mLeakyHandler.postDelayed(runnable, 1000 * 60 * 10);
         finish();
  }
}

由于此处自定义的 Handler 为匿名内部类,mLeakyHandler 将会持有 activity 的引用,同时,发送消息后,msg.target 将会持有 mLeakyHandler,将发送到消息队列中,而消息队列被 Looper 引用,Looper 的生命周期跟随线程的生命周期(不可确定),故此时可能发生 activity 的内存泄漏。
解决方案
①通过静态内部类实现自定义 Handler,内部可以通过一个弱引用持有 activity 来处理回调操作;
②同时,在 activity.onDestroy() 中,调用 mHandler.removeCallbacksAndMessages(null) 移除消息队列所有消息,防止内存泄漏。
代码实现如下:

public class MainActivity extends AppCompatActivity {
    private MyHandler mHandler = new MyHandler(this);
    private static class MyHandler extends Handler {
        //①静态内部类实现 Handler,并通过弱引用持有 activity
        private WeakReference<Context> reference;
        public MyHandler(Context context) {
            reference = new WeakReference<>(context);
        }
        @Override public void handleMessage(Message msg) {
            MainActivity activity = (MainActivity) reference.get();
            if(activity != null){
                activity.mTextView.setText("");
            }
        }
    }

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

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);//②移除消息
    }
}

线程和 TimerTask 造成的内存泄漏

void spawnThread() {
    new Thread() {
        @Override public void run() {
            while(true);
        }
    }.start();
}
void scheduleTimer() {
    new Timer().schedule(new TimerTask() {
        @Override
        public void run() {
            while(true);
        }
    }, Long.MAX_VALUE >> 1);
}

由于上述代码的 Thread 和 TimerTask 均通过匿名内部类实现,故实例会持有 activity 实例。由于线程和 TimerTask 的生命周期与 activity 的生命周期不同步,所以存在发生内存泄漏的可能。

属性动画导致的内存泄漏

Android 3.0 开始提供的属性动画中,有一类无限循环的动画,如果 onDestroy() 中没有停止动画,则动画会一直播放,View 会被动画持有,从而导致 activity 被持有,发生内存泄漏。
故当我们使用此类动画时,应在 activity.onDestroy() 中调用 animator.cancel();

其他内存泄漏的情况

  • IO操作后,没有关闭文件导致的内存泄露,比如Cursor、FileInputStream、FileOutputStream使用完后没有关闭。
  • 自定义View中使用TypedArray后,没有recycle。
  • Bitmap 没有 recycle,没有降低采样率。
  • ListViewAdapter 没有使用缓存的 convertView

参考资料

【腾讯Bugly干货分享】Android内存泄漏的简单检查与分析方法
云栖社区-Android 内存泄漏总结
Android应用内存泄露分析、改善经验总结
Android性能优化之常见的内存泄漏 - 安卓 - 伯乐在线
《Android 开发艺术探索》第 15 章