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必须手动销毁,流等对象必须手动关闭等。