没有魔法,也没有银弹
本文内容均为虚构,人名由ChatGPT给出。如有雷同。。。那肯定是你YY出来的
毕业季
烈日炎炎的17年夏天,21岁的林宇轩怀着激动的心情拖着行李箱,踏上了通往南方的旅程,迎来了他人生中的第一次飞行。经过了艰苦的军训和实习,到了九月,他终于坐在自己的工位前,打开了项目的代码,错综复杂的EventBus让他感到有些目不暇接,数千行的RecyclerView更是让他防不胜防。至于测试?测试忙着呢
在大大的花园(雾)里面挖了两个月以后,一张组长分下来的LeakCanary问题单落到了宇轩的头上,他一看,哦,源赖式佐田,有两个Activity leak了,但什么是leak呢?一头雾水的宇轩某度了一下午,什么静态内部类,弱引用之类的词语在网上比比皆是,但这又是什么意思呢?似懂非懂的他也来不及再学,晚上忙着封板,着急关问题单,谁有功夫让你学这个,在他照猫画虎地把网上的解决方案copy借鉴到项目中后,果然神奇地修好了这个问题,一看表已经11点半了,来不及多想,背着书包赶快回宿舍睡觉,只是迷迷糊糊中总觉得还是怪怪的,这弱引用这么神奇,以后new对象的时候都用它不就好了?带着疑惑,他最终还是进入了梦乡,明天还得早早起床上班呢
问题到底是不会自己消失,在后来的开发中,伴随着十分的纠结,宇轩写了一大堆的WeakReference,搜了搜项目里面好像也挺多这种写法的,但是到底为什么会有leak?WeakReference又为什么能解决呢?难道有什么黑魔法?不行,他觉得还是得搞明白这个问题,在一个大周末,经过多番的请教和搜索后,他好像在某个犄角旮旯的一句话找到了答案:
Longer lifecycle components reference shorter lifecycle components
回看当时leak的代码,虽然很长,说起来倒也并不复杂,只是一个AsyncTask下载图片而已
public class UserActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
...
new DownloadTask().execute("/image");
}
private class DownloadTask extends AsyncTask<String, Integer, String>{
@Override
protected String doInBackground(String... params) {
return filePath;
}
@Override
protected void onPostExecute(String filePath) {
updateUI(filePath);
}
}
private void updateUI(String path){
}
}
这里面谁是longer,谁又是shorter呢?既然leak的是Activity,想必它就是那个本该被释放而没被释放的shorter了,又既然LeakCanary的trace和网上的信息里都有AsyncTask,那它就该是那个罪魁祸首longer了,好了,现在问题就缩小为longer的Task什么时候reference了shorter的Activity,它又为什么要reference呢?
项目代码回想不起来,自己写了个demo也没找见犯罪现场,看来源代码里是找不到答案了,回想起以前混酷安时候玩过的一些重打包之类的操作,宇轩决定打开Jadx反编译一下,看看会不会有什么发现
.class Lcom/yuxuan/leak/UserActivity$DownloadTask;
.super Landroid/os/AsyncTask;
.source "UserActivity.java"
# instance fields
.field final synthetic this$0:Lcom/yuxuan/leak/UserActivity;
# direct methods
.method constructor <init>(Lcom/yuxuan/leak/UserActivity;)V
.registers 2
.param p1, "this$0" # Lcom/yuxuan/leak/UserActivity;
.line 18
iput-object p1, p0, Lcom/yuxuan/leak/UserActivity$DownloadTask;->this$0:Lcom/yuxuan/leak/UserActivity;
invoke-direct {p0}, Landroid/os/AsyncTask;-><init>()V
return-void
.end method
.method protected onPostExecute(Ljava/lang/String;)V
.registers 3
.param p1, "filePath" # Ljava/lang/String;
.line 26
iget-object v0, p0, Lcom/yuxuan/leak/UserActivity$DownloadTask;->this$0:Lcom/yuxuan/leak/UserActivity;
invoke-static {v0, p1}, Lcom/yuxuan/leak/UserActivity;->access$000(Lcom/yuxuan/leak/UserActivity;Ljava/lang/String;)V
.line 27
return-void
.end method
对着文档一顿翻译后,宇轩得出了这个结论:
在DownloadTask的构造方法中,传入了UserActivity的对象this$0,在onPostExecute方法中,原本该调用的是来自MainActivity的成员方法updateUI,编译后却变成了静态方法access$000,那么这个access$000又在做什么呢?
.method static synthetic access$000(Lcom/yuxuan/leak/UserActivity;Ljava/lang/String;)V
.registers 2
.param p0, "x0" # Lcom/yuxuan/leak/UserActivity;
.param p1, "x1" # Ljava/lang/String;
.line 9
invoke-direct {p0, p1}, Lcom/yuxuan/leak/UserActivity;->updateUI(Ljava/lang/String;)V
return-void
.end method
它拿着UserActivity的对象x0以及updateUI方法原本的入参x1,去调用了一次UserActivity的成员方法updateUI,本质上只是个代理,将这段Smail手动反编译回Java后,原始的代码就变成了这样
public class UserActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
...
new DownloadTask(UserActivity.this).execute("/image");
}
class DownloadTask extends AsyncTask<String, Integer, String> {
private UserActivity this$0;
public DownloadTask(UserActivity this$0) {
this.this$0 = this$0;
}
@Override
protected String doInBackground(String... params) {
return null;
}
@Override
protected void onPostExecute(String filePath) {
UserActivity.access$000(this$0, filePath);
}
}
public static void access$000(UserActivity x0, String x1) {
x0.updateUI(x1);
}
private void updateUI(String path) {
//
}
}
看起来编译器做了一些很多余的事情,本来直接访问了updateUI,非要绕这么个大圈子,悄摸摸的弄出了个引用搞出来了leak。。。正当宇轩准备口吐芬芳之时,又看了看Smail,感觉有点不对劲,问题好像出在这里
public static void access$000(UserActivity x0, String x1) {
x0.updateUI(x1);
}
private void updateUI(String path) {
//
}
仔细观察这段代码,它的作用看起来是将private方法转调为了public,难道是private方法不能调用,需要转调一次吗?再回过头去看这个内部类的定义
.class Lcom/yuxuan/leak/UserActivity$DownloadTask;
.super Landroid/os/AsyncTask;
.source "UserActivity.java"
UserActivity的private成员只能在类内访问,DownloadTask作为UserActivity的内部类,实际上被编译成了一个单独的类UserActivity$DownloadTask,在UserActivity$DownloadTask中要访问UserActivity的private方法,就会违反private成员不能在类外访问的原则,但Java又允许非静态内部类访问外部类的private成员,那就搞个public的代理出来让它访问,岂不美哉①(All problems in computer science can be solved by another level of indirection)
看来是编译器在这里的操作让内部类持有了外部类的引用,用来实现内部类对外部类private成员的访问,那么UserActivity$DownloadTask便持有了UserActivity,子线程的GC Root对Activity便可达,又因为AsyncTask的job不一定能在Activity#onDestroy前完成,在没完成的时候,自然就发生了leak。那么解决这种内部类问题的思路也很明确了
1:在shorter的对象销毁时,关掉longer中还能够持有shorter的job,清除掉引用(AsyncTask#cancel, Handler#removeCallbacksAndMessages)
2:如果不能保证及时关掉/清除,就让longer与shorter保持可被清除的引用关系,保证shorter被GC,当然,该释放的资源还得释放(Static Inner Class+WeakReference就是这么来的)
想明白了这些,宇轩总算能睡个踏实觉了。周一起了个大早去把自己之前写的各种莫名其妙的WeakReference清理掉,看着顺眼多了。
鸟枪换炮
一年后,作为一种取代匿名内部类的新语法,Lambda表达式正风靡于Android。可不是嘛,原来要写这么长的代码
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
现在只要这么点了
button.setOnClickListener(v -> {});
宇轩在自己的电脑上试了试,看着确实是挺爽的,配合上RxJava,原来那一大坨AsyncTask可以被写的很短,整体上优雅了许多。宇轩将引入Lambda的想法告诉了组长,组长倒也不是个保守的人,同意了宇轩的提议(那还得感谢公司不考核代码量),不过因为这个东西不太容易看懂,倒也不强制所有人使用。旋即宇轩便在gradle里加入了这两行代码,并引入了RxJava
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
美滋滋地用了一段时间后,宇轩又接到了例行的leak问题单(因为之前多次解决过leak,现在这种问题测试基本都会直接提给他),trace指向了一个Fragment,这次倒没那么容易看懂,不过这熟悉的this$0倒是让他虎躯一震:我也没写内部类啊,这里面就Lambda和RxJava
private void loadAvatar() {
Observable.<String>create(emitter -> {
emitter.onNext(loadImage());
emitter.onComplete();
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::updateUI, e -> {
});
}
private String loadImage() throws InterruptedException {
return null;
}
private void updateUI(String path) {
}
追查下去,trace中SyntheticLambda的字样让他产生了一丝警觉,难道是你这浓眉大眼的Lambda搞的鬼?网上不是说Lambda用了一个魔法invokeDynamic,不会内存泄露么?
时间有限,在loadImage中把没结束的job处理掉后,问题单可以关了。但是宇轩总觉得这事没那么简单,在又一个大周末来临之际,简单构建了一个demo,看看你这Lambda有些什么花活③
private void simpleLambda() {
Runnable runnable = () -> updateUI();
runnable.run();
}
private void updateUI() {
}
有了上次的经验,宇轩驾轻就熟地进行了反编译,看看这次的UserActivity又有了些什么新变化
###### Class com.yuxuan.leak.UserActivity (com.yuxuan.leak.UserActivity)
.class public Lcom/yuxuan/leak/UserActivity;
.super Landroidx/appcompat/app/AppCompatActivity;
.source "UserActivity.java"
.method private simpleLambda()V
.registers 2
.line 19
new-instance v0, Lcom/yuxuan/leak/UserActivity$$ExternalSyntheticLambda0;
invoke-direct {v0, p0}, Lcom/yuxuan/leak/UserActivity$$ExternalSyntheticLambda0;-><init>(Lcom/yuxuan/leak/UserActivity;)V
.line 20
.local v0, "runnable":Ljava/lang/Runnable;
invoke-interface {v0}, Ljava/lang/Runnable;->run()V
.line 21
return-void
.end method
# virtual methods
.method synthetic lambda$simpleLambda$0$com-yuxuan-leak-UserActivity()V
.registers 1
.line 19
invoke-direct {p0}, Lcom/yuxuan/leak/UserActivity;->updateUI()V
return-void
.end method
果然有猫腻,simpleLambda方法构造了一个类型为Runnable的UserActivity$$ExternalSyntheticLambda0的对象,并将UserActivity的this传入了其构造器中,最终调用了该对象的run方法,那么打开这个UserActivity$$ExternalSyntheticLambda0就能得到答案了
###### Class com.yuxuan.leak.UserActivity$$ExternalSyntheticLambda0 (com.yuxuan.leak.UserActivity$$ExternalSyntheticLambda0)
.class public final synthetic Lcom/yuxuan/leak/UserActivity$$ExternalSyntheticLambda0;
.super Ljava/lang/Object;
.source "D8$$SyntheticClass"
# interfaces
.implements Ljava/lang/Runnable;
# instance fields
.field public final synthetic f$0:Lcom/yuxuan/leak/UserActivity;
# direct methods
.method public synthetic constructor <init>(Lcom/yuxuan/leak/UserActivity;)V
.registers 2
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
iput-object p1, p0, Lcom/yuxuan/leak/UserActivity$$ExternalSyntheticLambda0;->f$0:Lcom/yuxuan/leak/UserActivity;
return-void
.end method
# virtual methods
.method public final run()V
.registers 2
iget-object v0, p0, Lcom/yuxuan/leak/UserActivity$$ExternalSyntheticLambda0;->f$0:Lcom/yuxuan/leak/UserActivity;
invoke-virtual {v0}, Lcom/yuxuan/leak/UserActivity;->lambda$simpleLambda$0$com-yuxuan-leak-UserActivity()V
return-void
.end method
UserActivity$$ExternalSyntheticLambda0是一个继承自Runnable的类,其构造器中传入了UserActivity的对象f$0,run方法调用了UserActivity的lambda$simpleLambda$0$com-yuxuan-leak-UserActivity方法,该方法的实现为
.method synthetic lambda$simpleLambda$0$com-yuxuan-leak-UserActivity()V
.registers 1
.line 19
invoke-direct {p0}, Lcom/yuxuan/leak/UserActivity;->updateUI()V
return-void
.end method
就是调用了一下updateUI罢了
将Smali手动反编译回Java,就变成了这样
public class UserActivity extends Activity {
private void simpleLambda() {
Runnable runnable = new UserActivity$$ExternalSyntheticLambda0(this);
runnable.run();
}
public void lambda$simpleLambda$0$com-yuxuan-leak-UserActivity(){
updateUI();
}
private void updateUI() {
}
}
public class UserActivity$$ExternalSyntheticLambda0 implements Runnable {
private final UserActivity f$0;
public UserActivity$$ExternalSyntheticLambda0(UserActivity f$0) {
this.f$0 = f$0;
}
@Override
public void run() {
f$0.lambda$simpleLambda$0$com-yuxuan-leak-UserActivity();
}
}
生成了一个新的类并构造出来,在UserActivity中新增了一个public方法用来转调private方法,虽与内部类不完全相同,但却异曲同工。既然有了对shorter的引用,那么longer未将其及时释放之时当然会leak,哪里有什么魔法。自然,解决Lambda中leak问题的思路,与之前别无二致。④⑤
军备竞赛
2020年前后,Android与iOS开发都迎来的自己的又一春,Kotlin+Jetpack Compose及Swift+SwiftUI组合的风头一时无两。此时宇轩也已工作三年,成为了一个开发小组的组长,爱追潮流的他也决定去研究研究,评估一下能否引入项目中,看到Kotlin中遍地开花的高阶函数和Lambda,一时兴起,决定如法炮制地将其扒开看看
class KotlinUserActivity : AppCompatActivity() {
private fun simpleLambda() {
Runnable { updateUI() }.run()
}
private fun updateUI() {
}
}
KotlinUserActivity的bytecode为
###### Class com.yuxuan.leak.KotlinUserActivity (com.yuxuan.leak.KotlinUserActivity)
.class public final Lcom/yuxuan/leak/KotlinUserActivity;
.super Landroidx/appcompat/app/AppCompatActivity;
.source "KotlinUserActivity.kt"
.method private final simpleLambda()V
.registers 2
.line 14
new-instance v0, Lcom/yuxuan/leak/KotlinUserActivity$$ExternalSyntheticLambda0;
invoke-direct {v0, p0}, Lcom/yuxuan/leak/KotlinUserActivity$$ExternalSyntheticLambda0;-><init>(Lcom/yuxuan/leak/KotlinUserActivity;)V
invoke-interface {v0}, Ljava/lang/Runnable;->run()V
.line 15
return-void
.end method
从simpleLambda入手,它构造了一个类型为Runnable的KotlinUserActivity$$ExternalSyntheticLambda0的对象,并在其构造器中传入了KotlinUserActivity的this,调用了run方法,打开KotlinUserActivity$$ExternalSyntheticLambda0看看
###### Class com.yuxuan.leak.KotlinUserActivity$$ExternalSyntheticLambda0 (com.yuxuan.leak.KotlinUserActivity$$ExternalSyntheticLambda0)
.class public final synthetic Lcom/yuxuan/leak/KotlinUserActivity$$ExternalSyntheticLambda0;
.super Ljava/lang/Object;
.source "D8$$SyntheticClass"
# interfaces
.implements Ljava/lang/Runnable;
# instance fields
.field public final synthetic f$0:Lcom/yuxuan/leak/KotlinUserActivity;
# direct methods
.method public synthetic constructor <init>(Lcom/yuxuan/leak/KotlinUserActivity;)V
.registers 2
invoke-direct {p0}, Ljava/lang/Object;-><init>()V
iput-object p1, p0, Lcom/yuxuan/leak/KotlinUserActivity$$ExternalSyntheticLambda0;->f$0:Lcom/yuxuan/leak/KotlinUserActivity;
return-void
.end method
# virtual methods
.method public final run()V
.registers 2
iget-object v0, p0, Lcom/yuxuan/leak/KotlinUserActivity$$ExternalSyntheticLambda0;->f$0:Lcom/yuxuan/leak/KotlinUserActivity;
invoke-static {v0}, Lcom/yuxuan/leak/KotlinUserActivity;->$r8$lambda$-3GZDt_QPwKG2FgBRX9E3cVb5Uc(Lcom/yuxuan/leak/KotlinUserActivity;)V
return-void
.end method
构造器中保存了KotlinUserActivity的对象f$0,在run方法中调用了KotlinUserActivity的静态方法$r8$lambda$-3GZDt_QPwKG2FgBRX9E3cVb5Uc
.method public static synthetic $r8$lambda$-3GZDt_QPwKG2FgBRX9E3cVb5Uc(Lcom/yuxuan/leak/KotlinUserActivity;)V
.registers 1
invoke-static {p0}, Lcom/yuxuan/leak/KotlinUserActivity;->simpleLambda$lambda$0(Lcom/yuxuan/leak/KotlinUserActivity;)V
return-void
.end method
调用了静态方法simpleLambda$lambda$0
.method private static final simpleLambda$lambda$0(Lcom/yuxuan/leak/KotlinUserActivity;)V
.registers 2
.param p0, "this$0" # Lcom/yuxuan/leak/KotlinUserActivity;
const-string v0, "this$0"
invoke-static {p0, v0}, Lkotlin/jvm/internal/Intrinsics;->checkNotNullParameter(Ljava/lang/Object;Ljava/lang/String;)V
.line 14
invoke-direct {p0}, Lcom/yuxuan/leak/KotlinUserActivity;->updateUI()V
return-void
.end method
调用了Kotlin的null-safe方法checkNotNullParameter,忽略,最终使用KotlinUserActivity的对象this调用了updateUI()方法,倒是与Java的内部类有些相似了
将Smali手动反编译回Kotlin,就变成了这样
class KotlinUserActivity : AppCompatActivity() {
companion object {
fun `$r8$lambda$-3GZDt_QPwKG2FgBRX9E3cVb5Uc`(`this$0`: KotlinUserActivity) {
`simpleLambda$lambda$0`(`this$0`);
}
private fun `simpleLambda$lambda$0`(`this$0`: KotlinUserActivity) {
Intrinsics.checkNotNullParameter(`this$0`)
`this$0`.updateUI()
}
}
private fun simpleLambda() {
`KotlinUserActivity$$ExternalSyntheticLambda0`(this).run()
}
private fun updateUI() {
}
}
class `KotlinUserActivity$$ExternalSyntheticLambda0`(val `f$0`: KotlinUserActivity) : Runnable {
override fun run() {
KotlinUserActivity.`$r8$lambda$-3GZDt_QPwKG2FgBRX9E3cVb5Uc`(`f$0``)
}
}
除了Lambda之外,协程也是Kotlin带来的一项重要特性,回想起Android的异步请求,从最早的AsyncTask到RxJava,再到现在被称为黑魔法的协程,代码是越来越少,看起来也越来越像魔法,那么所谓的黑魔法,真的有那么神奇吗?协程会不会导致leak呢?想到这里,宇轩捣鼓出了这样一段demo
class KotlinUserActivity : AppCompatActivity() {
private fun simpleCoroutine() {
GlobalScope.launch {
val path = withContext(Dispatchers.IO) {
loadImage()
}
withContext(Dispatchers.Main) {
updateUI(path)
}
}
}
private fun loadImage(): String = ""
private fun updateUI(path: String) {
}
}
反编译来看看
.method private final simpleCoroutine()V
.registers 8
.line 22
sget-object v0, Lkotlinx/coroutines/GlobalScope;->INSTANCE:Lkotlinx/coroutines/GlobalScope;
move-object v1, v0
check-cast v1, Lkotlinx/coroutines/CoroutineScope;
const/4 v2, 0x0
const/4 v3, 0x0
new-instance v0, Lcom/yuxuan/leak/KotlinUserActivity$simpleCoroutine$1;
const/4 v4, 0x0
invoke-direct {v0, p0, v4}, Lcom/yuxuan/leak/KotlinUserActivity$simpleCoroutine$1;-><init>(Lcom/yuxuan/leak/KotlinUserActivity;Lkotlin/coroutines/Continuation;)V
move-object v4, v0
check-cast v4, Lkotlin/jvm/functions/Function2;
const/4 v5, 0x3
const/4 v6, 0x0
invoke-static/range {v1 .. v6}, Lkotlinx/coroutines/BuildersKt;->launch$default(Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/Job;
.line 30
return-void
.end method
构造了KotlinUserActivity$simpleCoroutine$1的对象
.class final Lcom/yuxuan/leak/KotlinUserActivity$simpleCoroutine$1;
.super Lkotlin/coroutines/jvm/internal/SuspendLambda;
.source "KotlinUserActivity.kt"
.class final Lcom/yuxuan/leak/KotlinUserActivity$simpleCoroutine$1$1;
.super Lkotlin/coroutines/jvm/internal/SuspendLambda;
.source "KotlinUserActivity.kt"
.method constructor <init>(Lcom/yuxuan/leak/KotlinUserActivity;Ljava/lang/String;Lkotlin/coroutines/Continuation;)V
.registers 5
.annotation system Ldalvik/annotation/Signature;
value = {
"(",
"Lcom/yuxuan/leak/KotlinUserActivity;",
"Ljava/lang/String;",
"Lkotlin/coroutines/Continuation<",
"-",
"Lcom/yuxuan/leak/KotlinUserActivity$simpleCoroutine$1$1;",
">;)V"
}
.end annotation
iput-object p1, p0, Lcom/yuxuan/leak/KotlinUserActivity$simpleCoroutine$1$1;->this$0:Lcom/yuxuan/leak/KotlinUserActivity;
iput-object p2, p0, Lcom/yuxuan/leak/KotlinUserActivity$simpleCoroutine$1$1;->$path:Ljava/lang/String;
const/4 v0, 0x2
invoke-direct {p0, v0, p3}, Lkotlin/coroutines/jvm/internal/SuspendLambda;-><init>(ILkotlin/coroutines/Continuation;)V
return-void
.end method
.method public final invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;
.registers 5
invoke-static {}, Lkotlin/coroutines/intrinsics/IntrinsicsKt;->getCOROUTINE_SUSPENDED()Ljava/lang/Object;
.line 26
iget v0, p0, Lcom/yuxuan/leak/KotlinUserActivity$simpleCoroutine$1$1;->label:I
packed-switch v0, :pswitch_data_1e
new-instance p1, Ljava/lang/IllegalStateException;
const-string v0, "call to 'resume' before 'invoke' with coroutine"
invoke-direct {p1, v0}, Ljava/lang/IllegalStateException;-><init>(Ljava/lang/String;)V
throw p1
:pswitch_10
invoke-static {p1}, Lkotlin/ResultKt;->throwOnFailure(Ljava/lang/Object;)V
move-object v0, p0
.line 27
.local v0, "this":Lcom/yuxuan/leak/KotlinUserActivity$simpleCoroutine$1$1;
.local p1, "$result":Ljava/lang/Object;
iget-object v1, v0, Lcom/yuxuan/leak/KotlinUserActivity$simpleCoroutine$1$1;->this$0:Lcom/yuxuan/leak/KotlinUserActivity;
iget-object v2, v0, Lcom/yuxuan/leak/KotlinUserActivity$simpleCoroutine$1$1;->$path:Ljava/lang/String;
invoke-static {v1, v2}, Lcom/yuxuan/leak/KotlinUserActivity;->access$updateUI(Lcom/yuxuan/leak/KotlinUserActivity;Ljava/lang/String;)V
.line 28
sget-object v1, Lkotlin/Unit;->INSTANCE:Lkotlin/Unit;
return-object v1
:pswitch_data_1e
.packed-switch 0x0
:pswitch_10
.end packed-switch
.end method
协程框架调度线程后⑥,生成KotlinUserActivity$simpleCoroutine$1$1,构造传入KotlinUserActivity的对象this$0,调用KotlinUserActivity的public static方法access$updateUI
.method public static final synthetic access$updateUI(Lcom/yuxuan/leak/KotlinUserActivity;Ljava/lang/String;)V
.registers 2
.param p0, "$this" # Lcom/yuxuan/leak/KotlinUserActivity;
.param p1, "path" # Ljava/lang/String;
.line 12
invoke-direct {p0, p1}, Lcom/yuxuan/leak/KotlinUserActivity;->updateUI(Ljava/lang/String;)V
return-void
.end method
传入KotlinUserActivity的对象,调用updateUI,得,还得是$this
将Smali手动反编译回Kotlin看看
class KotlinUserActivity : AppCompatActivity() {
companion object {
fun `access$updateUI`(path: String, `$this`: KotlinUserActivity) {
`$this`.updateUI(path)
}
}
private fun simpleCoroutine() {
BuildersKt.`launch$default`(
GlobalScope,
null,
null,
`KotlinUserActivity$simpleCoroutine$1`(this, null),
3,
null
);
}
private fun loadImage(): String = ""
private fun updateUI(path: String) {
}
}
class `KotlinUserActivity$simpleCoroutine$1`(
val `this$0`: KotlinUserActivity,
val continuation: Continuation<`KotlinUserActivity$simpleCoroutine$1`>
) : SuspendLambda, Function2<CoroutineScope, Continuation<Unit>, Any> {
fun invokeSuspend(any: Any): Any {
//略去协程调度,直接进入invokeSuspend
`KotlinUserActivity$simpleCoroutine$1$1`(`this$0`, path, null).invokeSuspend()
}
}
class `KotlinUserActivity$simpleCoroutine$1$1`(
val `this$0`: KotlinUserActivity,
val path: String,
val continuation: Continuation<`KotlinUserActivity$simpleCoroutine$1$1`>
) : SuspendLambda, Function2<CoroutineScope, Continuation<Unit>, Any> {
fun invokeSuspend(any: Any): Any {
//略去协程调度,直接进入invokeSuspend
IntrinsicsKt.getCOROUTINE_SUSPENDED()
KotlinUserActivity.`access$updateUI`(path, `this$0`)
}
}
此时,宇轩已经明白,所谓leak,与内部类,Lambda,协程并没什么关系,那些都只是表象,背后也并没有什么魔法,你要调用一个类的成员方法,你就得拿着它的对象,当这个对象要被GC之时,有GC Root拿着不放,那可不就leak了⑦,无需纠结于具体的实现,只需谨记
Longer lifecycle components reference shorter lifecycle components
①参考资料:
https://docs.oracle.com/javase/specs/jls/se7/html/jls-6.html#jls-6.6
https://jcp.org/aboutJava/communityprocess/maintenance/JLS/innerclasses.pdf
②本篇扩展思考:
成员内部类会持有外部类引用,那么匿名内部类呢?
持有外部类引用是为了访问外部类private成员,那要是不访问呢?
③声明
篇幅有限,且本文重点不在RxJava的内部实现,分析会引入干扰,故而重新构建无RxJava的demo版本
④补充
Android上Lambda的情况较为复杂,请注意ART并不是标准的JVM,早期在无官方支持时多使用RetroLambda来进行desugar。Android N之后Android SDK实现了部分Java8特性,对minSDK在24以下的Java7版本进行了desugar,但在N之后,也并未采用与JDK相同的实现,依然是desugar处理。网上传的到处都是的bytecodeinvokeDynamic在Davlik bytecode中并不存在,Android O之后Davlik bytecode提供了invoke-polymorphic和invoke-custom,但他们并未被应用到Lambda中(截止2023.8)
⑤本篇扩展思考:
持有外部类引用是为了访问外部类private成员,那要是不访问呢?(本问题与②中并不重复,请自行验证)
⑥注意
篇幅有限,且本文重点不在协程的内部实现,故而此处将其调度忽略,直接跳转到调度结束点
⑦声明
本文中讨论的leak主要应用于Android中常见的组件被持有未释放情况(大部分情况下是子线程处理业务后更新UI),至于资源未关闭等问题不在讨论范围中,但也请加以关注