Android Studio提供了查看和分析内存泄露的方式,具体从哪个版本开始支持我们不做考究:
编写测试代码,一定会内存泄露的:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
set.setOnClickListener {
startActivity(Intent(this, HandlerActivity::class.java))
}
}
}
class HandlerActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_handler)
mHandler.sendEmptyMessageDelayed(0, 20000)
btn2.setOnClickListener {
finish()
}
}
var mHandler = object: Handler() {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
btn2?.text = "222"
}
}
}
一个MainActivity打开HandlerActivity,然后迅速点击按钮关闭。通过如下步骤可以查看内存泄露情况:
可以看到我的测试用例里,4个泄露点,右下角References会指出引用关系,我们的HandlerActivity
发生了内存泄漏,从引用路径来看,是被匿名内部类的实例mHandler
持有引用了,而Handler
的引用是被Message
持有了,Message
引用是被MessageQueue
持有了...
结合我们所学的Handler知识和这次引用路径分析,这次内存泄漏完整的引用链应该是:
主线程 —> threadlocal —> Looper —> MessageQueue —> Message —> Handler —> Activity
所以这次引用的头头
就是主线程
,主线程肯定是不会被回收的,只要是运行中的线程
都不会被JVM回收,跟静态变量
一样被JVM特殊照顾。
问题来了:
一:为什么Activity的引用会被Handler持有? 引申出另一个问题, 非静态内部类为什么持有外部类的引用:
这是因为内部类虽然和外部类写在同一个文件中,但是编译后还是会生成不同的class
文件,其中内部类的构造函数中会传入外部类的实例,然后就可以通过this$0
访问外部类的成员。
//原代码
class InnerClassOutClass{
class InnerUser {
private int age = 20;
}
}
//class代码
class InnerClassOutClass$InnerUser {
private int age;
InnerClassOutClass$InnerUser(InnerClassOutClass var1) {
this.this$0 = var1;
this.age = 20;
}
}
二:kotlin中的内部类与Java有什么不一样吗?
可以看到我在测试代码中加了一句:
btn2?.setText("2222")
如果删掉这句话,你会发现Leaks是0,即不会有内存泄露的发生。
这是因为在kotlin
中的匿名内部类
分为两种情况:
-
在Kotlin中
,匿名内部类如果没有使用到外部类的对象引用时候,是不会持有外部类的对象引用的,此时的匿名内部类其实就是个静态匿名内部类
,也就不会发生内存泄漏。 -
在Kotlin中
,匿名内部类如果使用了对外部类的引用,像我刚才使用了btn2
,这时候就会持有外部类的引用了,就会需要考虑内存泄漏
的问题。
同样kotlin
中对于内部类也是和Java
有区别的:
-
Kotlin中所有的内部类都是默认静态的,也就都是
静态内部类
。 -
如果需要调用外部的对象方法,就需要用
inner
修饰,改成和Java一样的内部类,并且会持有外部类的引用,需要考虑内存泄漏问题。
如何解决内存泄露呢?
-
不要让
长生命周期对象
持有短生命周期对象
的引用,而是用长生命周期对象
持有长生命周期对象
的引用。 -
将对象的强引用改成
弱引用
强引用
就是对象被强引用后,无论如何都不会被回收。弱引用
就是在垃圾回收时,如果这个对象只被弱引用关联(没有任何强引用关联他),那么这个对象就会被回收。软引用
就是在系统将发生内存溢出的时候,回进行回收。虚引用
是对象完全不会对其生存时间构成影响,也无法通过虚引用来获取对象实例,用的比较少。
class HandlerActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_handler)
MyHandler(WeakReference(this)).sendEmptyMessageDelayed(0, 20000)
btn2.setOnClickListener {
finish()
}
}
class MyHandler(var mActivity: WeakReference<HandlerActivity>) : Handler() {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
mActivity?.get()?.btn2?.text = "222"
}
}
}
这样就不会泄露了。