Android内存泄漏的场景 (1)
场景一:匿名内部类、非静态内部类
隐式持有外部类的引用
非静态内部类示例
1 public class Test { 2 private int testValue = 1; 3 4 public class Outer { 5 private int outerValue = 2; 6 7 public class Inner { 8 private int innerValue = 3; 9 10 public void print() { 11 System.out.println("testValue=" + testValue + ", outerValue=" + outerValue 12 + ", innerValue=" + innerValue); 13 } 14 } 15 } 16 17 public static void main(String[] args) { 18 Test test = new Test(); 19 Outer outer = test.new Outer(); 20 Outer.Inner inner = outer.new Inner(); 21 inner.print(); 22 } 23 }
如19-20行所示,在外部类外实例化非静态内部类时,需要通过外部类的实例.new进行构造。
使用 javac Test.java进行编译,生成Test$Outer$Inner.class、Test$Outer.class、Test.class几个class文件。使用javap -p class_file_name查看,结果如下:
» ~/Programming/java javap -p "Test.class" Compiled from "Test.java" public class Test { private int testValue; public Test(); public static void main(java.lang.String[]); static int access$000(Test); } » ~/Programming/java javap -p "Test\$Outer.class" Compiled from "Test.java" public class Test$Outer { private int outerValue; final Test this$0; public Test$Outer(Test); static int access$100(Test$Outer); } » ~/Programming/java javap -p "Test\$Outer\$Inner.class" Compiled from "Test.java" public class Test$Outer$Inner { private int innerValue; final Test$Outer this$1; public Test$Outer$Inner(Test$Outer); public void print(); }
从生成的class文件可以看出,内部类的构造函数被自动加上以外部类对象为参数,并且内部类有一个成员指向一个外部类对象(Test$Outer的this$0,Test$Outer$Inner的this$1)。
外部类和内部类之间可以相互访问对方的私有属性,而编译之后每个类都是单独的class文件,因此编译器会自动生成相应的包访问权限的静态方法(只有在需要的时候才会生成),来支持这种私有属性的访问(Test中为testValue生成的access$000静态方法,Test$Outer中为outerValue生成的access$100静态方法)。
匿名内部类示例
1 public class AnonymousTest { 2 private int testValue = 1; 3 4 public interface IAnonymous { 5 void print(); 6 } 7 8 private final IAnonymous anonymous = new IAnonymous() { 9 private int innerValue = 2; 10 11 public void print() { 12 System.out.println("testValue=" + testValue + ", innerValue=" + innerValue); 13 } 14 }; 15 16 public static void main(String[] args) { 17 AnonymousTest test = new AnonymousTest(); 18 test.anonymous.print(); 19 } 20 }
使用 javac AnonymousTest.java进行编译,生成AnonymousTest$1.class、AnonymousTest$IAnonymous.class、AnonymousTest.class几个class文件。使用javap -p class_file_name查看,结果如下:
» ~/Programming/java javap -p AnonymousTest.class Compiled from "AnonymousTest.java" public class AnonymousTest { private int testValue; private final AnonymousTest$IAnonymous anonymous; public AnonymousTest(); public static void main(java.lang.String[]); static int access$000(AnonymousTest); } » ~/Programming/java javap -p "AnonymousTest\$IAnonymous.class" Compiled from "AnonymousTest.java" public interface AnonymousTest$IAnonymous { public abstract void print(); } » ~/Programming/java javap -p "AnonymousTest\$1.class" Compiled from "AnonymousTest.java" class AnonymousTest$1 implements AnonymousTest$IAnonymous { private int innerValue; final AnonymousTest this$0; AnonymousTest$1(AnonymousTest); public void print(); }
从生成的class文件可以看出,匿名内部类与非静态内部类一样,同样持有外部类的引用。不同之处在于,匿名内部类的名字是外部类$数字。匿名内部类可以定义成员变量和成员函数,但是只能在本类中访问,也无法通过JAVA代码直接构造匿名内部类的新的实例。
结论
由于非静态内部类和匿名内部类的实例会隐式持有外部类实例的引用,因此,若其生命周期比外部类实例长,会导致外部类实例无法回收而产生泄漏。因此,在使用要格外注意。