I have a piece of simple code below in an activity...
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();
}
}
If the activity got terminated, there will be memory leak (as proven by Leak Canary).
However, when I covert this code to identical Kotlin code (using shift-alt-command-k), it is as below
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()
}
}
Memory leak no longer happen. Why? Is it because the anonymous class object got converted to Lambda?
The difference between these 2 versions is pretty simple.
Java version of the AnimatorUpdateListener
contains implicit reference to the outer class (MainActivity in your case). So, if the animation keeps running when the activity is not needed anymore, the listener keeps holding the reference to the activity, preventing it from being garbage-collected.
Kotlin tries to be more clever here. It sees that the lambda which you pass to the ValueAnimator
does not reference any objects from the outer scope (i.e. MainActivity
), so it creates a single instance of AnimatorUpdateListener
which will be reused whenever you [re]start the animation. And this instance does not have any implicit references to the outer scope.
Side note: if you add the reference to some object from outer scope to your lambda, Kotlin will generate the code which creates a new instance of the update listener every time the animation is [re]started, and these instances will be holding the implicit references to MainActivity
(required in order to access the object(s) which you decide to use in your lambda).
Another side note: I strongly recommend to read the book called "Kotlin in Action" as it contains lots of useful information on Kotlin in general, and my explanation of how Kotlin compiler make a choice about whether to put the implicit reference to outer scope into the object created after SAM conversion or not comes from this book.
I think you'll find the “Show Kotlin Bytecode” view very helpfull in seeing exactly what is going on. See here for the InteliJ shortcut. (Would have done this for you, but hard without greater context of your application)
But since Kotlin runs on the same JVM as Java (and so uses the same garbage collector as Java) you should expect a similarly safe runtime environment. That being said, when it comes to Lamdas and explicit references, when they're converted. And it can vary in different cases:
Like in Java, what happens in Kotlin varies in different cases.
- If the lambda is passed to an inline function and isn’t marked noinline, then the whole thing boils away and no additional classes
or objects are created.- If the lambda doesn’t capture, then it’ll be emitted as a singleton class whose instance is reused again and again (one class+one object allocation).
- If the lambda captures then a new object is created each time the lambda is used.
Source: http://openjdk.java.net/jeps/8158765
This answer should explain what your seeing, couldn't have explained it better myself: https://stackoverflow.com/a/42272484/979052 Different question, I know, but the theory behind it is the same - hope that helps
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With