关于Andorid内存泄漏

OOM问题会经常困扰我们,尤其是维护一个用户数量基数大的app的时候,我们的bug统计数据前几位,基本上会被OOM问题给占据。通过结果反向分析问题,和在写代码的时候注意内存泄漏从而避免OOM,本质上都需要对内存泄漏的产生,以及几种常见的方式需要有着清晰的认识。

内存泄漏的根本原因:

长生命周期的对象 持有 短生命周期 的强/弱引用,导致本应该被回收的短生命周期的对象无法被正常回收。

内存泄漏的情况

单例模式

由于单例模式的静态特性,使得它的生命周期和我们的应用一样长,一不小心让单例无限制的持有 Activity 的强引用就会导致内存泄漏。

解决办法

public class BaseApplication extends Application{
    private static ApplicationContext sContext;
    @Override
    public void onCreate(){
        super.onCreate();
        //全局的context,不再使用activity的引用
        sContext = getApplicationContext();
    }
    public static Context getApplicationContext(){
        return sContext;
    }
}

handler使用

由于 Handler 属于 TLS(Thread Local Storage)变量,导致它的生命周期和 Activity 不一致。因此通过 Handler 来更新 UI 一般很难保证跟 View 或者 Activity 的生命周期一致,故很容易导致无法正确释放。

解决办法

  • 弱引用+ 静态内部类。首先第一个关键是,静态内部类是不会引用外部对象的,但是我们依然需要外部的activity 处理事务,所以我们持有activity的弱引用来处理消息。弱引用的好处是当gc运行的时候,也就是退出的时候,不管内存够不够,都会回收弱引用。
  • activity结束的时候,移除message。为啥这个也可以防止内存泄漏?回到handler内存泄漏的本质,其实就是messagequeue 里面的message持有了handler的句柄,handler持有了外部的activity,所以message如果一直在,这个activity就没有办法回收,导致内存泄漏。移除了message,源头解决了内存泄漏。
    public class MyHandler extends android.os.Handler {

        private WeakReference<Activity> mContextWeakReference;

        public MyHandler(Activity activity) {
            mContextWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Activity activity = mContextWeakReference.get();
            if (activity == null || activity.isDestroyed() || activity.isFinishing()) {
                removeCallbacksAndMessages(null);
            }
            //todo logic things
        }
    }

引生一下:(远离非静态内部类和匿名类)

非静态内部类为什么会容易引发内存泄漏?

当我们反编译app的时候,会看到非静态内部类的格式为 外部类$非静态内部类

也就说说非静态内部类会持有外部类的引用,称为隐式引用。这个非静态内部类如果没有在外部类回收的时候被回收,也就是会持有外部类的引用导致外部类无法被回收。也就是内存泄漏。

非静态内部类中创建了一个静态实例,导致该实例的生命周期和应用ClassLoader级别,又因为该静态实例又会隐式持有其外部类的引用,所以导致其外部类无法正常释放,出现了泄漏问题。

使用系统服务引发的内存泄漏

遇到的内存泄漏问题是因为在 Activity 中调用了 getPackageManger 方法获取 PMS ,该方法调用的是 ContextImpl,此时如果ContextImpl 中 PackageManager 为 null,就会创建一个 PackageManger(ContextImpl 会将自己传递进去,而 ContextImpl 的 mOuterContext 为 Activity),创建 PackageManager 实际上会创建 PackageManagerService(简称 PMS),而 PMS 的构造方法中会创建一个 UserManger(UserManger 初始化之后会持有 ContextImpl 的强引用)。 只要 PMS 的 class 未被销毁,那么就会一直引用着 UserManger ,进而导致其关联到的资源无法正常释放。

解决办法

将getPackageManager()改为 getApplication()#getPackageManager() 。这样引用的就是 Application Context,而非 Activity 了。

WebView 引发的内存泄漏

对象的注册与反注册没有成对出现

譬如注册广播接收器、注册观察者(典型的譬如数据库的监听)等。

创建与关闭没有成对出现造成的泄露

譬如Cursor资源必须手动关闭,WebView必须手动销毁,流等对象必须手动关闭等。