Android内存泄露详解

首先介绍一下内存泄露的概念:内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。

1.静态变量导致的内存泄露

下面这种情形是一种最简单的内存泄露,将会导致Activity无法正常的销毁。

public class TestActivity extends Activity {
    public static Context sContext;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        sContext=this;
    }
}

我们将Activity始终被一个static对象引用,所以gc不满足回收的条件,Activity将无法被正常销毁。

public class TestActivity extends Activity {
    public static View sView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        sView=new View(this);
    }
}

这两种情况都是一样的,不能讲Acticity对象直接或者间接的被static变量引用。

还有一种更加隐蔽的泄露的情况:

private static Drawable sBackground;  
@Override  
protected void onCreate(Bundle state) {  
    super.onCreate(state);  

    TextView label = new TextView(this);  
    label.setText("Leaks are bad");  

    if (sBackground == null) {  
        sBackground = getDrawable(R.drawable.large_bitmap);  
    }  
    label.setBackgroundDrawable(sBackground);  

    setContentView(label);  
}

上面代码意味着:sBackground(GCRoot)会持有TextView对象,而TextView持有Activity对象。所以导致Activity对象无法被系统回收。

下面看看android4.0为了避免上述问题所做的改进。

先看看android 2.3的Drawable.Java对setCallback的实现:

    public final void setCallback(Callback cb){
        mCallback = cb;
}

再看看android 4.0的Drawable.Java对setCallback的实现:

    public final void setCallback(Callback cb){
        mCallback = new WeakReference<Callback> (cb);
}

在android 2.3中要避免内存泄漏也是可以做到的, 在activity的onDestroy时调用
sBackgroundDrawable.setCallback(null)。

以上2个例子的内存泄漏都是因为Activity的引用的生命周期超越了activity对象的生命周期。也就是常说的Context泄漏,因为activity就是context。

想要避免context相关的内存泄漏,需要注意以下几点:

  1. 不要对activity的context长期引用(一个activity的引用的生存周期应该和activity的生命周期相同)。
  2. 如果可以的话,尽量使用关于application的context来替代和activity相关的context。
  3. 如果一个acitivity的非静态内部类的生命周期不受控制,那么避免使用它;正确的方法是使用一个静态的内部类,并且对它的外部类有一WeakReference,就像在ViewRootImpl中内部类W所做的那样。

2.非静态内部类的静态实例容易造成内存泄漏

非静态内部类在类中创建的过程中会一直持有外部类的引用,所以就会回归到第一种情况。先看一下错误的用法。

public class TestActivity extends Activity {
    public static Test sInstance=null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if(sInstance==null){
            sInstance=new Test();
            //你可以把上面看做 sInstance=new Test(this);
        }
    }
    class Test{
        public void doSomething(){

        }
    }
}

正确的用法:

public class TestActivity extends Activity {
    public static Test sInstance=null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if(sInstance==null){
            sInstance=new Test();
        }
    }
    static class Test{
        public void doSomething(){

        }
    }
}

所以我们在使用static的时候要特别注意,创建内部类的使用尽量用,使用对象引用的时候不是必要情况下,不要瞎使用static,尽量避免错误的发生。

3.Handler引起的内存泄露

大多时候我们使用handler的时候,并没有进行特殊处理,这就为我们的app埋下了内存泄露的隐患。我们先来理解一下为什么会出现这个问题。我们都知道handler有两个小伙伴MessageQueue和Looper,一个是消息队列,handler所有的sendMessage的消息都会存储在这里,Looper是循环体,它会不停地从MessageQueue中将消息发送给handler,最终在handler的handleMessage中进行处理。如果MessageQueue中一直有message没有被处理,那么hander就会一直存在,因为他是一个内部类,它就会自动引用Activity,导致Activity不能够被正常回收。

我们看一下AndroidStudio在我们使用handler时给我们的提示

Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.

大概意思就是:

一旦Handler被声明为内部类,那么可能导致它的外部类不能够被垃圾回收。如果Handler是在其他线程(我们通常成为worker thread)使用Looper或MessageQueue(消息队列),而不是main线程(UI线程),那么就没有这个问题。如果Handler使用Looper或MessageQueue在主线程(main thread),你需要对Handler的声明做如下修改:
声明Handler为static类;在外部类中实例化一个外部类的WeakReference(弱引用)并且在Handler初始化时传入这个对象给你的Handler;将所有引用的外部类成员使用WeakReference对象。

我们的解决方法是:
1.将Handler变为静态内部类。
2.将传递给来的Activity变为WeakReference

public class TestActivity extends Activity {

    private StaticHandler mStaticHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mStaticHandler = new StaticHandler(this);
    }

    static class StaticHandler extends Handler {
        WeakReference<Activity> activity;

        public StaticHandler(Activity activity) {
            this.activity = new WeakReference<Activity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    }

}

4.注册某个对象后未取消注册,对象之间传递引用要特别小心

比如我们在使用观察者模式或者广播的时候,我们会将观察者的应用通过注入的方式传递到被观察者里面,比如registerListener的方式,这样被观察者里面就得到了观察者的引用,我们需要在销毁的时候取消注册,消除引用,避免观察者不能正常的销毁。

5.静态集合中对象没清理造成的内存泄露

像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。
例:

Static Vector v = new Vector(10); 
for (int i = 1; i<100; i++) 
{ 
Object o = new Object(); 
v.add(o); 
o = null; 
}

在这个例子中,循环申请Object 对象,并将所申请的对象放入一个Vector 中,如果仅仅释放引用本身(o=null),那么Vector 仍然引用该对象,所以这个对象对GC 来说是不可回收的。因此,如果对象加入到Vector 后,还必须从Vector 中删除,最简单的方法就是将Vector对象设置为null。

6.各种连接

比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close()方法将其连接关闭,否则是不会自动被GC 回收的。对于Resultset 和Statement 对象可以不进行显式回收,但Connection 一定要显式回收,因为Connection 在任何时候都无法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池,情况就不一样了,除了要显式地关闭连接,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的Statement 对象无法释放,从而引起内存泄漏。这种情况下一般都会在try里面去的连接,在finally里面释放连接。

7.属性动画导致的内存泄露

从Android3.0开始,Google提供了属性动画,属性动画中有一类无限循环的动画,如果在Activity中播放此类动画且没有在onDestroy中去停止动画,那么动画就会被一直播放下去,尽管已经无法在界面上看到动画效果,并且这个时候Activity的View被动画持有,而View又持有了Activity,最终Activity无法释放。
解决的方法在Activity的onDestroy中调用animator.cancel();来停止动画。

8.线程导致的内存泄露

Thread造成内存泄露的愿意在于thread的不可控性,因为不清楚thread什么时候执行完,假如清理Activity
时thread还没有执行完,那么Activity就不能够被销毁,这就出现了内存泄露。其实thread的的解决方法和我们前面解决Handler是一样的。

解决方法:
1.将线程的内部类,改为静态内部类。
2.在线程内部采用弱引用保存Context引用。

public class ThreadAvoidActivity extends Activity {
            public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_main);
                new MyThread(this).start();
            }

            private void dosomthing() {

            }

            private static class MyThread extends Thread {
                WeakReference<ThreadAvoidActivity> mThreadActivityRef;

                public MyThread(ThreadAvoidActivity activity) {
                    mThreadActivityRef = new WeakReference<ThreadAvoidActivity>(
                            activity);
                }

                @Override
                public void run() {
                    super.run();
                    if (mThreadActivityRef == null)
                        return;
                    if (mThreadActivityRef.get() != null)
                        mThreadActivityRef.get().dosomthing();
                    // dosomthing
                }
            }
        }

9.Bitmap没有recycle(),释放不用的bitmap。

10.总结

1.我们用尽量使用Application的Context来替代Activity的Context,因为Application和应用的生命周期相同。

2.资源操作一定要记得关闭,做好善后工作。

3.使用handler,thread要变成静态内部类和传递weakReference。

4.注意static变量不要直接或者间接的引用Activity。

5.静态集合使用要记得最后移除里面的引用。

6.使用属性动画要在onDestroy中关闭动画。

阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014316462/article/details/52086805
文章标签: 内存泄露 android
上一篇java虚拟机的内存区域划分
下一篇面试题之:生产者和消费者问题
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭