基于动态代理与Kotlin扩展方法的防抖方案

2,283 阅读1分钟

背景

今天首页上看到了点击防抖的文章,老话题了,从最开始的手动代码控制,到后来有ButterKnife注解的方法,有基于RxBinding的方法,有基于RxJava的方法,甚至还有基于字节码插桩的方法,可谓层出不穷,技术是为功能服务的,具体用什么方式可以根据项目情况选择。

现在得益于Kotlin扩展方法的语法糖,我们又有了新的选择,首页也有同学分享了,在此我也分享一下我们的方法,不同之处在于,我们基于Java的动态代理,实现了对任意接口的防抖。这个接口可以只包含一个方法,也包含多个方法,但是有一个限制是方法返回值必须为Void,因为一旦不为Void,那这个方法压根就不应该防抖,有类似场景,可以考虑RxJava实现。一个接口中多个方法回调这个主要用在Adapter的回调用。

扩展方法代码

/**
 * @param during 防抖时间间隔
 * @param combine 一个接口中的多个回调方法是否共用防抖时间
 */
fun <T> T.throttle(during: Long = 2000L, combine: Boolean = true): T {
    return Proxy.newProxyInstance(this!!::class.java.classLoader, this!!::class.java.interfaces,
        object : InvocationHandler {

            private val map = HashMap<Method?, Long>()

            override fun invoke(proxy: Any, method: Method, args: Array<out Any>?): Any? {
                val current = System.currentTimeMillis()
                return if (current - (map[if (combine) null else method] ?: 0) > during) {
                    map[if (combine) null else method] = current
                    method.invoke(this@throttle, *args.orEmpty())
                } else {
                    resolveDefaultReturnValue(method)
                }

            }

        }
    ) as T
}

private fun resolveDefaultReturnValue(method: Method): Any? {
    return when (method.returnType.name.toLowerCase(Locale.US)) {
        Void::class.java.simpleName.toLowerCase(Locale.US) -> null
        else -> throw IllegalArgumentException("无法正确对返回值不为空的回调进行节流")
    }
}

使用示例

mAdapter.setOnItemClickListener(object: OnItemClickListener {
    override fun onOpen() {
    }

    override fun onClose() {
    }

}.throttle())

mBtnTestThrottle.setOnClickListener(View.OnClickListener {
    Log.i("throttle", "click time ${System.currentTimeMillis()}")
}.throttle())