邊實驗邊分析 - Kotlin中对内存泄漏的一点优化

邊實驗邊分析 - Kotlin中对内存泄漏的一点优化

我们分析完JAVA的非静态内部类导致内存泄漏的问题后,同样的来看一下Kotlin中的表现,同样的我们还是从分析有經驗的Android開發同事説到這樣一句話:“非靜態的内部類會持有外部類的引用,使用時需要注意内存泄漏問題”开始。

首先我们知道在Kotlin中,有嵌套类,内部类和匿名内部类,在分析非静态内部类之前,
我们先来看一下匿名内部类,匿名内部类在JAVA中可以直接传入匿名实例对象,而在Kotlin中,需要通过object关键字进行创建

class KLeakActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Thread(object : Runnable{
            override fun run() {
                println("doSomething start")
            }
        }).start()
    }
}

我们来看一下字节码:
在这里插入图片描述
可以看出,Kotlin对于匿名内部类默认是不会持有外部的引用的,这一点和Java不同,这一点正是Kotlin做的优化。
接下来,如果你这样写的话,编译器会提示你使用lambda而不是使用这种方式来写,所以我们将其改成lambda来看一下

class KLeakActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Thread { println("doSomething start") }.start()
    }
}

查看一下字节码看一下有什么区别:
在这里插入图片描述
这边执行了一个sget-object操作,我们看一下$KLeakActivity$onCreate$1
在这里插入图片描述
可以看到,这边其实是一个静态的构造方法,并且,没有持有外部类的引用,在这边将INSTANCE值赋值,这样在上面的sget-object操作就可以获取到这个静态值,并强转成Runnable对象,因为其实现了run方法,所以将其强转后再传入Thread,最后调用其start方法执行。

Kotlin的lambda和Java在字节码上有些不同,Java会变成调用静态方法的方式,而Kotlin则会创建一个静态实例对象进行调用。
另外,如果看过上一篇的小伙伴应该记得,Java lambda有区分在lambda中使用外部实例和不使用外部实例2种,在Kotlin中也是一样的,如果将代码改成下面这样

var i = 0
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    Thread { i = 1 }.start()
}

则从字节码上会发现,$KLeakActivity$onCreate$1则没有了静态构造方法,默认的构造中通过KLeakActivity作为参数进行了init,持有了KLeakActivity实例,所以,这种写法一样会有造成内存泄漏的隐患。

知道了匿名内部类的知识后,我们接着往下看内部类,如果要使用内部类,和Java会有区别
Kotlin中的内部类需要使用inner关键字进行标记,并且会持有外部类的引用,相当于JAVA中的非静态内部类,我们来看一下

class KLeakActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val messageCall = MessageCall()
        removeWechatBtn.setOnClickListener {
            messageCall.doSomething()
        }
    }

    inner class MessageCall {
        fun doSomething(){
            Thread {
                println("doSomething start")
                SystemClock.sleep(20000)
                println("doSomething end")
            }.start()
        }
    }
}

我们这边直接使用lambda来写doSomething方法,因为我们从上面的匿名内部类的知识知道,写lambda和不写lambda Kotlin都会给我们进行优化相应的优化,而这种优化默认都不会持有外部类的引用,所以我们这边就可以不需要考虑lambda和非lambda的区别了

接下来,我们来看一下字节码
在这里插入图片描述
在当前Activity oncreate的时候,初始化KLeakActivity$onCreate$1,也就是我们的OnClickListener对象,接下来,因为我们使用了内部类的原因,传入了KLeakActivity$MessageCall的实例给OnClickListener对象,然后看KLeakActivity$onCreate$1的字节码

在这里插入图片描述
调用了KLeakActivity$MessageCall里面的doSomething方法,再到messageCall字节码

在这里插入图片描述
这边,和我们之前分析的匿名内部类是一样的操作,所以能够看出来,最终的doSomething方法不会影响到内存的泄漏,而messageCall内部类也确实是被持有了,不过不是被KLeakActivity$MessageCall$doSomething$1持有,所以不会导致其释放不掉

而当我们修改下代码

class KLeakActivity : Activity() {
	var i = 0
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val messageCall = MessageCall()
        removeWechatBtn.setOnClickListener {
            messageCall.doSomething()
        }
    }

    inner class MessageCall {
        fun doSomething(){
            Thread {
                println("doSomething start")
                i = 1
                SystemClock.sleep(20000)
                println("doSomething end")
            }.start()
        }
    }
}

这样的话,会导致KLeakActivity$MessageCall$doSomething$1持有了MessageCall实例,而MessageCall实例又持有了KLeakActivity实例,最终导致内存泄漏的发生。

接下来我们来看一下嵌套类,嵌套类相当于静态的内部类,所以自然而然的不会导致内存的泄漏,大家可以自行分析并查看字节码进行验证。

class KLeakActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val messageCall = MessageCall()
        messageCall.doSomething()
    }

    inner class MessageCall {
        fun doSomething(){
            Thread {
                println("doSomething start")
                SystemClock.sleep(20000)
                println("doSomething end")
            }.start()
        }
    }
}

在这里插入图片描述
可以看到,其不会持有外部类的引用,doSomething方法里面的套路和之前一样,会变为静态对象进行静态调用所以不会导致内存泄漏的问题发生。

总结一下的话:
1.嵌套类:不会导致内存泄漏
2.内部类:类方法内使用外部实例会导致内存泄漏,类方法内不使用外部实例不会导致内存泄漏
3.匿名内部类:类方法内使用外部实例会导致内存泄漏,类方法内不使用外部实例不会导致内存泄漏

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卡卡爾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值