说到设计模式,想必很多人都会想到,常见的设计模式之一的动态代理。特别是,对很多中高级Android程序员而言,更是如此。因为著名的网络框架Retrofit,关于网络调用部分,就是采用动态代理,将网络请求,委托给OkHttp实现。但在使用Kotlin语言,来实现动态代理时,存在一些坑。这篇博文,将为你揭开这些坑的生成原因和填坑的方法。
class KotlinDynamicProxy(private val baseProxy: BaseProxy?) : InvocationHandler {
override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? {
try {
if (baseProxy== null) return null
// 动态代理调用
method?.let { return it.invoke(baseProxy, args) }
return null
} catch (e: Exception) {
Log.d("Debug","动态代理异常:${e.message}")
}
return null
}
}
interface IAnimalProxy {
fun noneArgs()
fun singlePrimitiveArg(test: Int)
fun singleLambdaArg(listener: (Int)->Unit)
fun multipArgs(a: Int, b: Int)
}
class AnimalProxy:IAnimalProxy {
override fun noneArgs(){ Log.d("Debug","noneArgs") }
override fun singlePrimitiveArg(test: Int){ Log.d("Debug","singlePrimitiveArg") }
override fun singleLambdaArg(listener: (Int)->Unit){ Log.d("Debug","singleLambdaArg") }
override fun multipArgs(a: Int, b: Int){ Log.d("Debug","multipArgs") }
}
fun foo() {
val proxy = Proxy.newProxyInstance(
IAnimalProxy ::class.java.classLoader,
arrayOf(IAnimalProxy ::class.java),
BinderDynamicProxy(new AnimalProxy())
) as IAnimalProxy
noneArgs(proxy)
singlePrimitiveArg(proxy)
singleLambdaArg(proxy)
multipArgs(proxy)
}
fun noneArgs(proxy: AnimalProxy){ proxy.noneArgs() }
执行结果:成功,log正常打出,没问题。
fun singlePrimitiveArg(proxy: AnimalProxy){ proxy.singlePrimitiveArg(1) }
执行结果:成功,log正常打出,没问题。
fun singleLambdaArg(proxy: AnimalProxy){ proxy.singleLambdaArg{} }
执行结果:失败,抛出异常:
method com.xxx.AnimalProxy.singleLambdaArg argument 1 has type kotlin.jvm.functions.Function1, got java.lang.Object[]
失败的原因是,编译器会将,lambda表达式,即函数类型参数,转成kotlin的通用标准接口Function1。也就是说,编译后的代码是不同的,具体如下:
// 编译前
fun singleLambdaArg(listener: (Int)->Unit)
// 编译后
void singleLambdaArg(Function1 function1)
你可能会好奇,这个Function1是啥,Function1的本质就是一个接口,这个接口接受单个参数,这个参数是个泛型。所以,它可以完善的替换上述例子中的lambda表达式(listener: (Int)->Unit)
。附带学习资料:Kotlin 高阶函数。
好了,我们回归到问题本身,即编译后的方法是这样的:
// 编译后
void singleLambdaArg(Function1 function1)
但是,我们动态代理时,传过去的参数却是个Array类型:args: Array
。
人家要个Function1参数,我们却传了个Array,那肯定不行,所以,才会抛出异常。解决方案我们先不说,继续看最后一种场景。
fun multipArgs(proxy: AnimalProxy){ proxy.multipArgs(100,200) }
执行结果:失败,抛出异常:
Wrong number of arguments; expected 2, got 1
失败的原因,很简单,因为multipArgs
方法,需要2个参数,我们只却只传了一个参数。你可能会问,我不是已经传了100和200,2个参数了吗?怎么说是1个。问的好!我们再来看看,我们动态代理传参数是怎么写的:
override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? {
// 省略代码
method?.let { return it.invoke(baseProxy, args) }
// 省略代码
}
我们这里,只传了一个参数args,这个参数的类型是:Array。那你可能会问,这里的Array,即args内部已经包含了100和200,2个Int参数了呀。
但实际上,如果你这个类是个Java类,则没问题,如你所想,是正确的。
但如果是个kotlin类,则有问题,编译器会认为你是要传一个Array类型的参数过去。
好了,问题和原因都解释清楚了,那我们怎么解决,3和4,两种情况的问题呢?
答案是:延展操作符(spread operator):*(星号)【个人觉得叫拆包操作符更合适】
正确的动态代理写法如下:
class KotlinDynamicProxy(private val baseProxy: BaseProxy?) : InvocationHandler {
override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? {
try {
if (baseProxy== null) return null
method?.let {
// 延展操作符,不能作用在可空对象上。无参数时,直接传null即可。
if (args == null) return it.invoke(baseProxy)
// args ==变成==> *args
else return it.invoke(baseProxy, *args)
}
return null
} catch (e: Exception) {
Log.d("Debug","动态代理异常:${e.message}")
}
return null
}
}
关键修改: it.invoke(baseProxy, args)
改成 it.invoke(baseProxy, *args)
这样修改后,
基于第3种情况,编译器,会拆包,取出Array中的Function1对象,传给method.invoke方法。
基于第4种情况,编译器,会拆包,取出Array中的两个参数,传给method.invoke方法。
好了,到这里,你应该学会了如何正确的使用Kotlin动态代理了,另外我再附上相关的学习资料: