没有魔法,也没有银弹

本文内容均为虚拟,人名由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办法结构了一个类型为RunnableUserActivity$$ExternalSyntheticLambda0的目标,并将UserActivitythis传入了其结构器中,终究调用了该目标的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$0run办法调用了UserActivitylambda$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入手,它结构了一个类型为RunnableKotlinUserActivity$$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的异步恳求,从最早的AsyncTaskRxJava,再到现在被称为黑魔法的协程,代码是越来越少,看起来也越来越像魔法,那么所谓的黑魔法,真的有那么奇特吗?协程会不会导致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-polymorphicinvoke-custom,但他们并未被应用到Lambda中(截止2023.8)

⑤本篇扩展考虑:

持有外部类引证是为了拜访外部类private成员,那要是不拜访呢?(本问题与②中并不重复,请自行验证)

⑥留意

篇幅有限,且本文重点不在协程的内部完结,故而此处将其调度疏忽,直接跳转到调度完毕点

⑦声明

本文中评论的leak主要应用于Android中常见的组件被持有未开释情况(大部分情况下是子线程处理事务后更新UI),至于资源未关闭等问题不在评论规模中,但也请加以重视