最近编(zhan)写(tie)了大概这样一段代码:
fun showAnim() {
val delay = 5000L
val handler = Handler()
handler.postDelayed( {
fadeIn(R.id.text_toLeak)
val moreDelay = 3000L
handler.postDelayed( {
fadeOut(R.id.text_toLeak)
}, moreDelay)
}, delay)
}
仔细一看,这不典型的 Runnable
内存泄漏嘛! 虽然说 Kotlin 中的内部类已经默认静态,不持有外部类的引用了,但 Lambda 表达式或匿名函数仍然会有隐性的持有外部类的强引用:
Ref: Kotlin : safe lambdas (no memory leak)?
赶紧写个简单的类让 Runnable 不再持有外部类的强引用:
class WeakRunnable(block: () -> Unit): Runnable {
val weakBlock = WeakReference(block)
override fun run() {
weakBlock.get()?.invoke()
}
}
调用的时候也很方便:
fun showAnim() {
val delay = 5000L
val handler = Handler()
handler.postDelayed(WeakRunnable {
fadeIn(R.id.text_toLeak)
val moreDelay = 3000L
handler.postDelayed(WeakRunnable {
fadeOut(R.id.text_toLeak)
}, moreDelay)
}, delay)
}
不过还要注意一点,这里 WeakRunnable
的实例的生存周期是只在这个局部函数内的。虽然 Android
GC 不会立即触发,这种写法 Kotlin 也不会 NPE,大部分情况可能看上去没有问题。 但实际上这种写法很有可能导致正在队列中的动画被 GC 掉,完全不被显示。
这里同一时间队列里只有一个动画效果(或者说描述一段动画的 Block ),可以在当前的用一个变量记录下来,从而暂时避免被 GC 掉的命运:
fun showAnim() {
val delay = 5000L
val handler = Handler()
handler.postDelayed(regAnimWeakRef {
fadeIn(R.id.text_toLeak)
val moreDelay = 3000L
handler.postDelayed(regAnimWeakRef {
fadeOut(R.id.text_toLeak)
}, moreDelay)
}, delay)
}
private var currentAnimBlock: (() -> Unit)? = null
// let view hold current animation block ref to avoid unexpected gc
private fun regAnimWeakRef(block: () -> Unit): WeakRunnable {
currentAnimBlock = block
return WeakRunnable(block)
}
(哪有没看完动画就 GC 的道理! 以上)