4、静态变量泄漏:静态Activity Context导致的内存泄漏场景与修复
静态变量泄漏,这可以说是Android内存泄漏里的“老熟人”了。我入行那会儿,第一个被review打回来的代码,就是因为这个。
说白了,就是你把一个本该被回收的对象,用static关键字给“钉死”在了内存里。Java的垃圾回收器再厉害,也动不了静态变量引用的对象。你想想看,一个Activity本该在退出时被销毁,结果因为某个静态变量还拽着它,它就只能在内存里干瞪眼。
4.1 典型的泄漏场景:静态Context
最常见的场景,就是在一个工具类或者Application类里,定义了一个静态的Context变量。比如这样:
public class Utils {
private static Context sContext;
public static void init(Context context) {
sContext = context;
}
}
然后你在某个Activity里调用了Utils.init(this)。嗯,问题来了。这个this是Activity的实例,它被静态变量sContext持有了。只要App进程活着,这个Activity就永远无法被回收。
我在项目中遇到过类似的情况。一个同事为了图方便,把Activity的Context传给了静态变量,结果那个Activity的onDestroy()从来没被调用过。页面退出了,但内存里的对象还在,甚至还在偷偷执行一些任务。最后导致了一系列奇怪的崩溃。
4.2 为什么会泄漏?
我们来拆解一下泄漏的链条:
- 静态变量属于类,生命周期与App进程一致。
- Activity Context内部持有Activity的引用。
- 静态变量 → Activity Context → Activity → 整个视图层级。
这个链条一旦建立,就是一条“不归路”。GC(垃圾回收)从根节点(GC Roots)开始遍历,静态变量就是根节点之一。只要它还在,整个链条上的对象都不会被标记为垃圾。
你可能会问:“那我用getApplicationContext()不就行了?”没错,这正是修复的关键。
4.3 修复方案:用Application Context代替
修复其实很简单。把静态变量里存的Context,换成Application级别的Context。因为Application的生命周期和进程一样长,不存在泄漏问题。
public class Utils {
private static Context sContext;
public static void init(Context context) {
// 关键:只取Application Context
sContext = context.getApplicationContext();
}
}
或者更干脆一点,在构造时就强制要求传入Application Context:
public class Utils {
private static Context sContext;
public static void init(Application application) {
sContext = application;
}
}
我个人习惯在工具类的init()方法里,直接调用context.getApplicationContext()。这样不管调用方传什么Context进来,最终存的都是安全的Application实例。
instanceof 判断一下。但更好的做法是,从一开始就只接受Application类型的参数。
4.4 其他常见的静态泄漏场景
除了静态Context,还有几个“坑”我经常见到:
| 场景 | 泄漏原因 | 修复方法 |
|---|---|---|
| 静态View | 静态变量持有Activity的某个View | 使用弱引用,或在Activity销毁时置空 |
| 静态内部类 | 静态内部类隐式持有外部类引用 | 改为非静态内部类,或使用弱引用 |
| 单例模式 | 单例对象持有了Activity的引用 | 单例中只使用Application Context |
| 静态集合 | 静态List或Map中存了Activity对象 | 及时移除,或使用WeakHashMap |
我曾经接手过一个项目,里面有个静态的ArrayList<Activity>,用来管理所有打开的Activity。初衷是为了方便“一键关闭所有页面”。结果呢?每个Activity都被这个静态列表拽着,一个都释放不了。页面堆叠多了,内存直接飙到300MB以上。
修复方案其实也简单:要么用WeakReference包装一下,要么在Activity的onDestroy()里主动从列表中移除。我个人更推荐后者,因为弱引用虽然能解决泄漏,但对象被回收的时机不可控,可能带来其他问题。
4.5 避坑指南
这里我总结几条实战经验,希望能帮你少走弯路:
- 静态变量里不要存Activity、Fragment、View。这是铁律。
- 单例对象里如果需要Context,只存Application Context。在
getInstance()方法里强制转换一下。 - 静态集合要小心。如果必须用,记得在合适的时机清理。比如在
onDestroy()里调用remove()。 - 匿名内部类+静态变量。匿名内部类会持有外部类的引用,如果这个匿名内部类被静态变量引用,外部类也会泄漏。
嗯,这里还要提一句。有些同学觉得“我App很小,泄漏一点没关系”。千万别这么想。内存泄漏就像慢性病,初期没症状,等用户手机卡顿、闪退的时候,往往已经积重难返了。我见过太多因为静态泄漏导致OOM(内存溢出)的案例,修复起来其实就几行代码的事,但排查过程可能要花上几天。
所以,写代码的时候多留个心眼。看到static关键字,就问问自己:这个变量真的需要静态吗?它引用的对象生命周期合适吗?养成这个习惯,能帮你避免90%的静态泄漏问题。