”弱引用“”来优化使用“内部类”造成的内存溢出

来源:互联网 发布:行政审批流程优化方案 编辑:程序博客网 时间:2024/05/17 02:57

什么是内部类?什么是内存泄露?为什么Android的内部类容易引起内存泄露?如何解决?

1.什么是内部类?

 在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。广泛意义上的内部类一般来说包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类。
 
虽然成员内部类可以无条件地访问外部类的成员,而外部类想访问成员内部类的成员却不是这么随心所欲了。在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问:

class Circle {    private double radius = 0;    public Circle(double radius) {        this.radius = radius;        getDrawInstance().drawSahpe();   //必须先创建成员内部类的对象,再进行访问    }    private Draw getDrawInstance() {        return new Draw();    }    class Draw {     //内部类        public void drawSahpe() {            System.out.println(radius);  //外部类的private成员        }    }}

  成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。创建成员内部类对象的一般方式如下:

public class Test {    public static void main(String[] args)  {        //第一种方式:        Outter outter = new Outter();        Outter.Inner inner = outter.new Inner();  //必须通过Outter对象来创建        //第二种方式:        Outter.Inner inner1 = outter.getInnerInstance();    }}class Outter {    private Inner inner = null;    public Outter() {    }    public Inner getInnerInstance() {        if(inner == null)            inner = new Inner();        return inner;    }    class Inner {        public Inner() {        }    }}

  内部类可以拥有private访问权限、protected访问权限、public访问权限及包访问权限。比如上面的例子,如果成员内部类Inner用private修饰,则只能在外部类的内部访问,如果用public修饰,则任何地方都能访问;如果用protected修饰,则只能在同一个包下或者继承外部类的情况下访问;如果是默认访问权限,则只能在同一个包下访问。这一点和外部类有一点不一样,外部类只能被public和包访问两种权限修饰。我个人是这么理解的,由于成员内部类看起来像是外部类的一个成员,所以可以像类的成员一样拥有多种权限修饰。

2.Android中的内部类使用

经常会遇见Android程序中这样使用handler:

public class SomeActivity {    // ......    private Handler mHandler = new Handler(){        @Override        public void handleMessage(Message msg) {            switch(msg.what) {            case 0:                // do something                break;            case 1:                // do something                break;            default:                break;            }        }    };    private void someMethod () {        mHandler.sendEmptyMessage(0);    }}

上述代码中,mHandler字段指向一个匿名Handler类。匿名类是内部类吗?匿名类会持有外部类的对象吗? 答案是:匿名类是内部类,但是是特殊的内部类,如果把匿名类放到一个static方法中,它是不会持有外部类实例的。而在上面的代码中,这个mHandler会持有外部类(SomeActivity)实例的引用,因为它处于一个对象的上下文中,而不是类型上下文中。
什么是”持有外部类实例的引用“?你可以这么理解:

public class InnerClass {    private OuterClass outer;    public InnerClass(OuterClass outer) {        this.outer = outer;    }}

就是说,创建InnerClass对象的时,必须传递进去一个OuterClass对象,赋值给InnerClass的一个字段outer,该字段是OuterClass对象的引用。回忆一下GC原理,如果InnerClass对象没有被标记为垃圾对象,那么outer指向的OuterClass对象会可能被标记为垃圾对象吗?答案是:InnerClass对象与GC Root有引用路径,InnerClass对象又引用了OuterClass对象,那么OuterClass对象到GC Root也是有引用路径的,所以,OuterClass不可能是垃圾对象。

3.为什么发生内存泄露?

由上文可以看出:当mHandler没有被回收时,其外围Activity对象不能被回收。当Activity被用户关闭(finish),而此时mHandler还未被回收,那么Activity对象就不会被回收,造成Activity内存泄露。
问题的关键转入到了这个问题:为什么Activity被finish了,mHandler还不能被回收?
发送消息时,我们使用了这个函数:mHandler.sendEmptyMessage(0)函数。通过查看源码追踪调用关系,发现走到了:
Message对象有个target字段,该字段是Handler类型,引用了当前Handler对象。一句话就是:你通过Handler发往消息队列的Message对象持有了Handler对象的引用。假如Message对象一直在消息队列中未被处理释放掉,你的Handler对象就不会被释放,进而你的Activity也不会被释放。

这种现象很常见,当消息队列中含有大量的Message等待处理,你发的Message需要等几秒才能被处理,而此时你关闭Activity,就会引起内存泄露。如果你经常send一些delay的消息,即使消息队列不繁忙,在delay到达之前关闭Activity也会造成内存泄露。

4.有什么解决方案?

方案#1:在关闭Activity时(finish/onStop等函数中),取消还在排队的Message:mHandler.removeCallbacksAndMessages(null);

方案#2:使用WeakReference截断StrongReference。问题的症结既然是内部类持有外部类对象的引用,那我不用内部类就行了,直接使用静态成员类。但mHandler又需要与Activity对象交互,那就来个WeakReference,指向外部Activity对象。

public class SomeActivity {    private Handler mHandler = new MyHandler(this);    private static class MyHandler extends Handler {        private WeakReference<SomeActivity> ref;        public MyHandler(SomeActivity activity) {            if (activity != null) {                ref = new WeakReference<SomeActivity>(activity);            }        }        @Override        public void handleMessage(Message msg) {            if (ref == null) {                return;            }            SomeActivity v = ref.get();            if (v == null) {                return;            }            // handle message        }    }}

当Activity想关闭销毁时,mHandler对它的弱引用没有影响,该销毁销毁;当mHandler通过WeakReference拿不到Activity对象时,说明Activity已经销毁了,就不用处理了,相当于丢弃了消息。

5.分析工具

eclipse mat
Android studio lint
http://www.jianshu.com/p/c49f778e7acf

0 0
原创粉丝点击