背景
匿名内部类会持有外部类的引用,因此有造成内存泄漏的风险;
那么Lambda 表达式是否会造成内存泄漏呢?
匿名内部类 VS Lambda 表达式
我们新建一个类TestInner,其中test方法里面包含一个Lambda表达式,test1方法里面包含一个匿名内部类
public class TestInner {
public void test(){
new Thread(()->{
Log.i("测试","dddd");
}).start();
}
public void test1(){
new Thread(new Runnable() {
@Override
public void run() {
Log.i("测试","dddd1");
}
}).start();
}
}
我们将其编译成apk,然后查看编译后的产物
匿名内部类
首先来看匿名内部类。
我们发现test1方法编译产物如下:
.method public test1()V
.registers 3
.line 14
new-instance v0, Ljava/lang/Thread;
new-instance v1, Lcom/example/jnihelper/TestInner$1;
invoke-direct {v1, p0}, Lcom/example/jnihelper/TestInner$1;-><init>(Lcom/example/jnihelper/TestInner;)V
invoke-direct {v0, v1}, Ljava/lang/Thread;-><init>(Ljava/lang/Runnable;)V
.line 19
invoke-virtual {v0}, Ljava/lang/Thread;->start()V
.line 20
return-void
.end method
从中可以发现,test1方法里面调用了TestInner$1的init方法,接着我们可以从编译产物当中找到TestInner$1
接着我们查看TestInner$1的内容,init方法会将TestInner以参数的形式传入
# direct methods
.method constructor <init>(Lcom/example/jnihelper/TestInner;)V
.registers 2
.param p1, "this$0" # Lcom/example/jnihelper/TestInner;
.line 14
iput-object p1, p0, Lcom/example/jnihelper/TestInner$1;->this$0:Lcom/example/jnihelper/TestInner;
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
# virtual methods
.method public run()V
.registers 3
.line 17
const-string v0, "\u6d4b\u8bd5"
const-string v1, "dddd1"
invoke-static {v0, v1}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I
.line 18
return-void
.end method
所以匿名内部类会持有外部类的引用,因此会有内存泄漏的风险。
那么Lambda 表达式呢?
Lambda 表达式
我们先来看下含有Lambda表达式的test方法的编译产物
.method static synthetic Lambda表达式()V
.registers 2
.line 9
const-string v0, "\u6d4b\u8bd5"
const-string v1, "dddd"
invoke-static {v0, v1}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I
.line 10
return-void
.end method
# virtual methods
.method public test()V
.registers 3
.line 8
new-instance v0, Ljava/lang/Thread;
sget-object v1, Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;->INSTANCE:Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;
invoke-direct {v0, v1}, Ljava/lang/Thread;-><init>(Ljava/lang/Runnable;)V
.line 10
invoke-virtual {v0}, Ljava/lang/Thread;->start()V
.line 11
return-void
.end method
我们可以看到test方法里面调用了com/example/jnihelper/TestInner$$ExternalSyntheticLambda0
打开TestInner$$ExternalSyntheticLambda0,我们发现最后执行的run方法
会调用TestInner的lambda$test$0方法,而该方法是一个静态方法。
# direct methods
.method static synthetic constructor <clinit>()V
.registers 1
new-instance v0, Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;
invoke-direct {v0}, Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;-><init>()V
sput-object v0, Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;->INSTANCE:Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;
return-void
.end method
.method private synthetic constructor <init>()V
.registers 1
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
return-void
.end method
# virtual methods
.method public final run()V
.registers 1
invoke-static {}, Lcom/example/jnihelper/TestInner;->lambda$test$0()V
return-void
.end method
可以看出此时Lambda表达式并未持有外部类的引用,因而没有造成内存泄漏。
然而真的是这样吗?
我们再回过头来看下TestInner的Lambda 表达式实现,可以看出,内部确实没有用到外部类。
public void test(){
new Thread(()->{
Log.i("测试","dddd");
}).start();
}
那么,我们如果手动引用呢?
public class TestInner {
private String helloInner = "helloIIIII";
public void test() {
new Thread(() -> {
Log.i("测试", "dddd");
Log.i("测试", TestInner.this.helloInner);//手动显示引用
}).start();
}
...
}
编译之后查看其编译产物
# virtual methods
.method public synthetic lambda$test$0$com-example-jnihelper-TestInner()V
.registers 3
.line 11
const-string v0, "\u6d4b\u8bd5"
const-string v1, "dddd"
invoke-static {v0, v1}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I
.line 12
iget-object v1, p0, Lcom/example/jnihelper/TestInner;->helloInner:Ljava/lang/String;
invoke-static {v0, v1}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I
.line 13
return-void
.end method
.method public test()V
.registers 3
.line 10
new-instance v0, Ljava/lang/Thread;
new-instance v1, Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;
invoke-direct {v1, p0}, Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;-><init>(Lcom/example/jnihelper/TestInner;)V
invoke-direct {v0, v1}, Ljava/lang/Thread;-><init>(Ljava/lang/Runnable;)V
.line 13
invoke-virtual {v0}, Ljava/lang/Thread;->start()V
.line 14
return-void
.end method
我们发现对应的实现方法lambda$test 0 0 0com-example-jnihelper-TestInner已经变成非静态方法了,
而在test方法里面,调用了TestInner$$ExternalSyntheticLambda0的init方法,并将外部类当作参数传入,
我们再来看TestInner$$ExternalSyntheticLambda0的实现,可以发现TestInner会在init方法中当作参入传入,并当作其成员变量。
.class public final synthetic Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;
.super Ljava/lang/Object;
# interfaces
.implements Ljava/lang/Runnable;
# instance fields
.field public final synthetic f$0:Lcom/example/jnihelper/TestInner;
# direct methods
.method public synthetic constructor <init>(Lcom/example/jnihelper/TestInner;)V
.registers 2
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
iput-object p1, p0, Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;->f$0:Lcom/example/jnihelper/TestInner;
return-void
.end method
# virtual methods
.method public final run()V
.registers 2
iget-object v0, p0, Lcom/example/jnihelper/TestInner$$ExternalSyntheticLambda0;->f$0:Lcom/example/jnihelper/TestInner;
invoke-virtual {v0}, Lcom/example/jnihelper/TestInner;->lambda$test$0$com-example-jnihelper-TestInner()V
return-void
.end method
所以,可以看出,如果在Lambda 表达式中需显示的调用外部类,才会持有外部类的引用。
结论
匿名内部类会持有外部类的引用,有造成内存泄漏的风险;
Lambda 表达式,若内部没有引用外部类,则不会持有外部类;若内部引用到了外部类,则会持有外部类。