Android开发中因为有限的内存,以及防止OOM问题出现,解决内存泄漏问题将是开发者一直持续下去的工作。本文就分析了不当使用(持有)context导致的内存泄漏。
1. 为什么使用Context有可能会导致内存泄漏?
首先从context的本质谈起,context名称上代表了上下文,实质上是Application、Activity或Service的一个引用。因此如果有生命周期较长的对象,比如线程持有了一个context引用,那么在线程结束前,这个context是无法得到释放的,这也意味着context代表的activity、service无法被GC回收,这就发生了内存泄漏。
2. 来个例子
还记得屏幕旋转,会销毁当前Activity,重新创建一个新的Activity的事吗?我们就以这个操作来做泄漏内存的示例,原理是让老的Activity无法被销毁。
private static Drawable mBackgroudImage;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);//持有context
label.setText("演示旋转屏幕导致内存泄漏");
if (mBackgroudImage == null) {
mBackgroudImage = getDrawable(R.drawable.beauty); //这个是随便在网上找的一张图片
}
label.setBackgroundDrawable(mBackgroudImage);
setContentView(label);
}
好,运行起来,将屏幕旋转几次,这个代码已经泄漏了老的Activity。
是不是内心有点小怀疑?
我们往下看截图可以证明。
3. 使用Android Monitor验证内存泄漏情况
怎么验证,是否发生了内存泄漏呢?
我们可以通过Android Studio提供的Memory Monitor工具来观察Java Heap情况。
打开Android Monitor,如图所示,我们旋转几次屏幕后,点击“Dump Java Heap”按钮
接下来会,弹出一个hprof编辑框,在右边的红圈部分点开“分析界面”
好,在新弹出的分析界面,点击绿色箭头开始分析,下半部分就是分析结果。
注意我红圈标注出来的地方,发现SplashActivity竟然有2个实例!!!!这就证明确实发生了内存泄漏,有一个SplashActivity泄漏了。
4. 分析原因。
首先我们发现,这段代码定义了一个没有初始化的静态Drawable变量。众所周知,静态变量是属于一种跟“类”而非“实例”绑定在一起的对象。是一个被所有实例共享的成员变量,当给它赋值的时候,实际上是赋值给了整个“类对象”。
出于省事的考虑,代码中并没有在每次onCreate中去加载赋值mBackgroudImage,而是检测它如果不为空,就不再赋值。
根据官网的说法,将一个Drawable对象赋给某个View的时候,这个View同时也作为一个callBalk被Drawable对象给引用了。即,Drawable对象持有对应View的引用。这个可以看setBackgroundDrawable()源码,确实是没错的。
@Deprecated
public void setBackgroundDrawable(Drawable background) {
//省略其他内容,可以看到确实View
//作为一个callBalk被Drawable对象给引用了
// Set callback last, since the view may still be initializing.
background.setCallback(this);
}
事实上,当我们第一次运行起来SplashActivity时,会给mBackgroudImage赋值,当屏幕旋转的时候则不会第二次赋值。因此,mBackgroudImage仍旧持有第一次的TextView的引用。
而TextView的新建需要传入一个context,因此它持有了SplashActivity的一个引用。
所以,相当于静态变量mBackgroudImage间接的、始终持有第一个创建出来SplashActivity没有释放。
所以就发生了内存泄漏!!!
总结
通过本文的分析,让一个生命周期长于Activity的对象持有context引用很容易就发生了内存泄漏,开发者需要特别警惕!