解决内存泄漏更加清楚的认识到Java匿名类与外部类的关系
1.事件起因
2.代码是怎么样让Volley 的缓存对象持有了mView对象呢?
public CCHttpRequest(final String url, final Map<String, String> params, final CCApiCallback callback) { mRequest = new HttpStringRequest(HttpGsonRequest.Method.POST, url) { @Override protected Map<String, String> getParams() { return params; } @Override protected void onResponse(String s) {
<pre name="code" class="java"> <span style="white-space:pre"> </span>if (null != callback) { callback.onResponse(data.toString(), hasServerTime, serverTime); }} @Override protected void onErrorResponse(Exception e) { if (null != callback) { //系统错误返回-1 callback.onError(createErrorMessage(-1, e.getMessage())); } } }; } 被Volley缓存持有的对象是new HttpStringRequest 这个匿名类对象的实例,为什么方法中的参数final CCApiCallback callback这个参数会被新创建出来的匿名内部内持有呢?
3.一个简单的例子解释java匿名类与外部类的关系
书写一个简单的Hello.java文件,里面包括了一个匿名类与一个内部类Demo
public class Hello{ private String mName="37785612"; class Demo{ public void show(){ } } public void showDemo(final String s){ new Demo(){ public void show(){ System.out.println("s="+s); System.out.println("name="+mName); } }.show(); } }执行javac Hello.java编译完成后,会在同一目录下生成如下几个class文件,Hello.class,Hello$1.class,Hello$Demo.class。Hello.class就是我们源文件Hello的类文件,Hello$1.class是在showDemo()方法里面new Demo()那个匿名类的类文件,Hello$Demo.class是内部类Demo的类文件,我们这里主要分析Hello.class与Hello$1.class.
{ public Hello(); Code: Stack=2, Locals=1, Args_size=1 0: aload_0 1: invokespecial #2; //Method java/lang/Object."<init>":()V 4: aload_0 5: ldc #3; //String 37785612 7: putfield #1; //Field mName:Ljava/lang/String; 10: return LineNumberTable: line 1: 0 line 2: 4 line 3: 10 public void showDemo(java.lang.String); Code: Stack=4, Locals=2, Args_size=2 0: new #4; //class Hello$1 3: dup 4: aload_0 5: aload_1 6: invokespecial #5; //Method Hello$1."<init>":(LHello;Ljava/lang/String;)V 9: invokevirtual #6; //Method Hello$1.show:()V 12: return LineNumberTable: line 8: 0 line 14: 12 static java.lang.String access$000(Hello); Code: Stack=1, Locals=1, Args_size=1 0: aload_0 1: getfield #1; //Field mName:Ljava/lang/String; 4: areturn LineNumberTable: line 1: 0 }可以看到这里有一个方法access$000(Hello)是我们在源文件中没有出现的,而编译后会多了这个方法,它其实都是返回变量mName的值,后面会说到这个方法会被怎么用
继续执行命令javap -v Hello$1,汇编出来的部分代码如下
{ final java.lang.String val$s; final Hello this$0; Hello$1(Hello, java.lang.String); Code: Stack=2, Locals=3, Args_size=3 0: aload_0 1: aload_1 2: putfield #1; //Field this$0:LHello; 5: aload_0 6: aload_2 7: putfield #2; //Field val$s:Ljava/lang/String; 10: aload_0 11: aload_1 12: invokespecial #3; //Method Hello$Demo."<init>":(LHello;)V 15: return LineNumberTable: line 8: 0 public void show(); Code: Stack=3, Locals=1, Args_size=1 0: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream; 3: new #5; //class java/lang/StringBuilder 6: dup 7: invokespecial #6; //Method java/lang/StringBuilder."<init>":()V 10: ldc #7; //String s= 12: invokevirtual #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 15: aload_0 16: getfield #2; //Field val$s:Ljava/lang/String; 19: invokevirtual #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 22: invokevirtual #9; //Method java/lang/StringBuilder.toString:()Ljava/lang/String; 25: invokevirtual #10; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 28: getstatic #4; //Field java/lang/System.out:Ljava/io/PrintStream; 31: new #5; //class java/lang/StringBuilder 34: dup 35: invokespecial #6; //Method java/lang/StringBuilder."<init>":()V 38: ldc #11; //String name= 40: invokevirtual #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 43: aload_0 44: getfield #1; //Field this$0:LHello; 47: invokestatic #12; //Method Hello.access$000:(LHello;)Ljava/lang/String; 50: invokevirtual #8; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 53: invokevirtual #9; //Method java/lang/StringBuilder.toString:()Ljava/lang/String; 56: invokevirtual #10; //Method java/io/PrintStream.println:(Ljava/lang/String;)V 59: return LineNumberTable: line 10: 0 line 11: 28 line 12: 59 }
我们可以看到这个匿名类多了两个成员变量final java.lang.String val$s与final Hello this$0;在看下这个匿名类的构造函数Hello$1(Hello, java.lang.String);刚好是对两个成员变量进行赋值。this$0指向了外部类对象的引用,val$s指向了方法showDemo(final String s)的参数s所指向内存的引用。在看看匿名类是怎么访问外部类的成员变量呢?看下这几行汇编代码:
43: aload_0 44: getfield #1; //Field this$0:LHello; 47: invokestatic #12; //Method Hello.access$000:(LHello;)Ljava/lang/String;43,调用匿名内的this对象,44,取得匿名类的this$0成员变量,就是(Hello对象) 47 调用Hello的静态方法static java.lang.String access$000(Hello);获取成员mName的值
4.找出真正原因
从上面关于匿名类与外部类的关系理清之后,我们能够发现,我代码中的callback持有了一个外部对象,层层回退,最下面一个callback对象持有了一个外部引用,而刚好这个外部对象又持有了一个mListener对象,而mListener内部类对象又持有了一个外部对象,这个外部对象又持有了mView,导致程序退出时由于Volley的缓存不释放,mView对象不会被垃圾回收,从而产生导致内存泄漏。郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。