我在活动中有一段简单的代码......
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
}
});
valueAnimator.start();
}
}
如果活动终止,将会有内存泄漏(Leak Canary证实)。
然而,当我将此代码转换为相同的Kotlin代码(使用shift-alt-command-k)时,它如下所示
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val valueAnimator = ValueAnimator.ofFloat(0.0f, 1.0f)
valueAnimator.repeatCount = ValueAnimator.INFINITE
valueAnimator.addUpdateListener { }
valueAnimator.start()
}
}
内存泄漏不再发生。为什么?是因为匿名类对象被转换为Lambda?
答案 0 :(得分:6)
这两个版本之间的区别非常简单。
AnimatorUpdateListener
的Java版本包含对外部类的隐式引用(在您的情况下为MainActivity)。因此,如果动画在不再需要活动时继续运行,则侦听器会持续保持对活动的引用,从而防止对其进行垃圾回收。
Kotlin试图在这里变得更聪明。它看到传递给ValueAnimator
的lambda不引用外部作用域中的任何对象(即MainActivity
),因此它创建了一个单个实例 {{3}每次你重新开始动画时都会重复使用它。并且此实例没有对外部作用域的任何隐式引用。
旁注:如果您将对外部作用域的某个对象的引用添加到lambda,Kotlin将生成代码,每次动画[re]时都会创建更新侦听器的新实例已启动,并且这些实例将保留对MainActivity
的隐式引用(为了访问您决定在lambda中使用的对象而需要)。
另一方面注意事项:我强烈建议您阅读名为" Kotlin in Action"因为它包含了很多关于Kotlin的有用信息,并且我对Kotlin编译器如何选择是否将外部作用域的隐式引用放入SAM转换后创建的对象中的解释来自本书。
答案 1 :(得分:2)
我认为你会发现“Show Kotlin Bytecode”视图非常有助于确切了解发生了什么。见here for the InteliJ shortcut。 (本来会为你做这件事,但如果没有更好的申请背景,那就很难)
但是由于Kotlin在与Java相同的JVM上运行(因此使用与Java相同的垃圾收集器),您应该期望一个类似安全的运行时环境。话虽这么说,当涉及到Lamdas和明确的引用时,它们被转换。并且它可以在不同的情况下变化:
与Java一样,Kotlin的情况因情况而异。
- 如果将lambda传递给内联函数并且没有标记为noinline,那么整个事情就会消失,并且没有额外的类 或者创建对象。
- 如果lambda没有捕获,那么它将作为一个单独的类发出,其实例一次又一次地被重用(一个类+一个 对象分配)。
- 如果lambda捕获,则每次使用lambda时都会创建一个新对象。
来源:http://openjdk.java.net/jeps/8158765
这个答案应该解释你所看到的,不能自己解释得更好:https://stackoverflow.com/a/42272484/979052 不同的问题,我知道,但背后的理论是一样的 - 希望有所帮助
答案 2 :(得分:1)
正如您已经建议的那样,addUpdateListener
的论点在两个版本中实际上都是不同的。
我们来看一个例子。我用一个抽象方法JavaAbstract
:
foo
public interface JavaInterface {
void foo();
}
这用于JavaInterfaceClient
:
public class JavaInterfaceClient {
public void useInterfaceInstance(JavaAbstract inst){
inst.foo();
}
}
让我们看看如何从 Kotlin 中调用useInterfaceInstance
:
首先,在示例中使用简单的lambda(SAM Conversion):
JavaInterfaceClient().useInterfaceInstance {}
用Java表示的结果字节码:
(new JavaInterfaceClient()).useInterfaceInstance((JavaInterface)null.INSTANCE);
如您所见,非常简单,没有对象实例化。
第二次,带有匿名实例:
JavaInterfaceClient().useInterfaceInstance(object : JavaInterface {
override fun foo() {
}
})
用Java表示的结果字节码:
(new JavaInterfaceClient()).useInterfaceInstance((JavaInterface)(new JavaInterface() {
public void foo() {
}
}));
在这里,我们可以观察到新的对象实例化,这与SAM转换/ lambda方法有关。您应该尝试代码中的第二个示例。