Android Context导致的内存泄漏分析(示例代码+分析工具使用)
2016-10-26 12:08
686 查看
Android开发中因为有限的内存,以及防止OOM问题出现,解决内存泄漏问题将是开发者一直持续下去的工作。本文就分析了不当使用(持有)context导致的内存泄漏。
首先从context的本质谈起,context名称上代表了上下文,实质上是Application、Activity或Service的一个引用。因此如果有生命周期较长的对象,比如线程持有了一个context引用,那么在线程结束前,这个context是无法得到释放的,这也意味着context代表的activity、service无法被GC回收,这就发生了内存泄漏。
还记得屏幕旋转,会销毁当前Activity,重新创建一个新的Activity的事吗?我们就以这个操作来做泄漏内存的示例,原理是让老的Activity无法被销毁。
好,运行起来,将屏幕旋转几次,这个代码已经泄漏了老的Activity。
是不是内心有点小怀疑?
我们往下看截图可以证明。
怎么验证,是否发生了内存泄漏呢?
我们可以通过Android Studio提供的Memory Monitor工具来观察Java Heap情况。
打开Android Monitor,如图所示,我们旋转几次屏幕后,点击“Dump Java Heap”按钮
接下来会,弹出一个hprof编辑框,在右边的红圈部分点开“分析界面”
好,在新弹出的分析界面,点击绿色箭头开始分析,下半部分就是分析结果。
注意我红圈标注出来的地方,发现SplashActivity竟然有2个实例!!!!这就证明确实发生了内存泄漏,有一个SplashActivity泄漏了。
首先我们发现,这段代码定义了一个没有初始化的静态Drawable变量。众所周知,静态变量是属于一种跟“类”而非“实例”绑定在一起的对象。是一个被所有实例共享的成员变量,当给它赋值的时候,实际上是赋值给了整个“类对象”。
出于省事的考虑,代码中并没有在每次onCreate中去加载赋值mBackgroudImage,而是检测它如果不为空,就不再赋值。
根据官网的说法,将一个Drawable对象赋给某个View的时候,这个View同时也作为一个callBalk被Drawable对象给引用了。即,Drawable对象持有对应View的引用。这个可以看setBackgroundDrawable()源码,确实是没错的。
事实上,当我们第一次运行起来SplashActivity时,会给mBackgroudImage赋值,当屏幕旋转的时候则不会第二次赋值。因此,mBackgroudImage仍旧持有第一次的TextView的引用。
而TextView的新建需要传入一个context,因此它持有了SplashActivity的一个引用。
所以,相当于静态变量mBackgroudImage间接的、始终持有第一个创建出来SplashActivity没有释放。
所以就发生了内存泄漏!!!
通过本文的分析,让一个生命周期长于Activity的对象持有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引用很容易就发生了内存泄漏,开发者需要特别警惕!
相关文章推荐
- Android 内存泄漏工具使用分析
- Android Audio代码分析1 - AudioTrack使用示例
- Android Audio代码分析1 - AudioTrack使用示例
- [置顶] 代码覆盖工具Jacoco使用示例及源码分析
- Android Audio代码分析1 - AudioTrack使用示例
- 使用代码分析工具SOOT经验总结
- 代码静态分析工具——splint的学习与使用
- 如何使用VSTS工具来分析软件的代码和性能?
- DOS中断跟踪,在纯Dos下使用,分析Dos工作原理的最佳工具,原理详看原代码
- eclipse 中使用内存分析工具MAT分析内存泄漏
- android 内存分析(MAT工具的使用)
- .NET : 使用代码性能分析工具
- 使用FxCop工具分析.NET托管代码
- 代码分析工具findbug简介和使用
- 天天记录 - Android内存分析工具DDMS heap + MAT 安装和使用
- 代码静态分析工具——splint的学习与使用
- 代码分析工具FxCop1.36之一:介绍与使用
- 代码分析工具findbug简介和使用
- 天天记录 - Android内存分析工具DDMS heap + MAT 安装和使用
- 一些常用代码分析工具的使用