Android Delayed Animation 内存泄漏

最近编(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 的道理! 以上)

comments powered by Disqus