没有魔法,也没有银弹
本文内容均为虚拟,人名由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),至于资源未关闭等问题不在评论规模中,但也请加以重视