Android Context导致的内存泄漏分析(示例代码+分析工具使用)

≯℡__Kan透↙ 提交于 2019-12-08 11:14:01

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引用很容易就发生了内存泄漏,开发者需要特别警惕!

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!