咱们始终都在应用Retroift
,都晓得它的外围是动静代理。例如在之前的文章重温 Retrofit 源码,笑看协程实现中也简略提及到动静代理(来填之前挖的坑 …)。
咳咳,大家不要关注起因,还是要回归以后的内容。
这次次要是来剖析一下动静代理的作用与实现原理。既然都曾经剖析了原理,最初天然也要入手仿照 Retrofit
来简略实现一个Demo
。
通过最初的 Demo
实现,置信动静代理你也根本没什么问题了。
动态代理
既然说到动静代理,天然少不了动态代理。那么动态代理到底是什么呢?咱们还是通过一个简略的场景来理解。
假如有一个 Bird
接口来代表鸟的一些个性,例如 fly
航行个性
interface Bird {fun fly()
}
当初别离有麻雀、老鹰等动物,因为它们都是鸟类,所以都会实现 Bird
接口,外部实现本人的 fly
逻辑。
// 麻雀
class Sparrow : Bird {override fun fly() {println("Sparrow: is fly.")
Thread.sleep(1000)
}
}
// 老鹰
class Eagle : Bird {override fun fly() {println("Eagle: is fly.")
Thread.sleep(2000)
}
}
麻雀与老鹰的航行能力都实现了,当初有个需要:须要别离统计麻雀与老鹰航行的时长。
你会怎么做呢?置信在咱们刚学习编程的时候都会想到的是:这还不简略间接在麻雀与老鹰的 fly
办法中别离统计就能够了。
如果实现的鸟类品种不多的话,这种实现不会有太大的问题,然而一旦实现的鸟类品种很多,那么这种办法反复做的逻辑将会很多,因为咱们要到每一种鸟类的 fly
办法中都去增加统计时长的逻辑。
所以为了解决这种无意义的反复逻辑,咱们能够通过一个 ProxyBird
来代理实现时长的统计。
class BirdProxy(private val bird: Bird) : Bird {override fun fly() {println("BirdProxy: fly start.")
val start = System.currentTimeMillis() / 1000
bird.fly()
println("BirdProxy: fly end and cost time => ${System.currentTimeMillis() / 1000 - start}s")
}
}
ProxyBird
实现了 Bird
接口,同时承受了内部传进来的实现 Bird
接口的对象。当调用 ProxyBird
的fly
办法时,间接调用了传进来的对象的 fly
办法,同时还进行来时长的统计。
class Main {
companion object {
@JvmStatic
fun main(args: Array<String>) {ProxyBird(Sparrow()).fly()
println()
ProxyBird(Eagle()).fly()}
}
}
最初输入如下:
ProxyBird: fly start.
Sparrow: is fly.
ProxyBird: fly end and cost time => 1s
ProxyBird: fly start.
Eagle: is fly.
ProxyBird: fly end and cost time => 2s
下面这种模式就是动态代理,可能有许多读者都曾经不知觉的应用到了这种办法,只是本人没有意识到这是动态代理。
那它的益处是什么呢?
通过下面的例子,很天然的可能领会到动态代理次要帮咱们解决的问题是:
- 缩小反复逻辑的编写,提供对立的便捷解决入口。
- 封装实现细节。
动静代理
既然曾经有了动态代理,为什么又要来一个动静代理呢?
任何货色的产生都是有它的必要性的,都是为了解决前者不能解决的问题。
所以动静代理就是来解决动态代理所不能解决的问题,亦或者是它的毛病。
假如咱们当初要为 Bird
新增一种个性:chirp
鸟叫。
那么基于后面的动态代理,须要做些什么扭转呢?
- 批改
Bird
接口,新增chirp
办法。 - 别离批改
Sparrow
与Eagle
,为它们新增chirp
的具体实现。 - 批改
ProxyBird
,实现chirp
代理办法。
1、3 还好,尤其是 2,一旦实现 Bird
接口的鸟类品种很多的话,将会十分繁琐,这时就真的是牵一动员全身了。
这还是改变现有的 Bird
接口,可能你还须要新增另外一种接口,例如 Fish
鱼,实现无关鱼的个性。
这时又要从新生成一个新的代理 ProxyFish
来治理无关鱼的代理。
所以从这一点,咱们能够发现动态代理的机动性很差,对于那些实现了之后不怎么扭转的性能,能够思考应用它来实现,这也完全符合它的名字中的动态的个性。
那么这种状况动静代理就可能解决吗?别急,是否解决接着往下看。
接着下面,咱们为 Bird
新增 chirp
办法
interface Bird {fun fly()
fun chirp()}
而后再通过动静代理的形式来实现这个接口
class Main {
companion object {
@JvmStatic
fun main(args: Array<String>) {val proxy = (Proxy.newProxyInstance(this::class.java.classLoader, arrayOf(Bird::class.java), InvocationHandler { proxy, method, args ->
if (method.name == "fly") {println("calling fly.")
} else if (method.name == "chirp") {println("calling chirp.")
}
}) as Bird)
proxy.fly()
proxy.chirp()}
}
}
输入如下:
calling fly.
calling chirp.
形式很简略,通过 Proxy.newProxyInstance
静态方法来创立一个实现 Bird
接口的代理。该办法次要有三个参数别离为:
- ClassLoader: 生成代理类的类类加载器。
- interface 接口 Class 数组: 对应的接口 Class。
- InvocationHandler: InvocationHandler 对象,所有代理办法的回调。
这里关键点是第三个参数,所有通过调用代理类的代理办法都会在 InvocationHandler
对象中通过它的 invoke
办法进行回调
public interface InvocationHandler {public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
这就是下面将判断调用具体接口办法的逻辑写在 InvocationHandler
对象的 invoke
办法的起因。
那它到底是如何实现的呢?怎么就成了一个代理类呢?我也没看到代理类在哪啊?怎么就所有调用都通过 InvocationHandler
的呢?
有这些疑难很失常,开始接触动静代理时都会有这些疑难。导致这些疑难的间接起因是咱们不能间接看到所谓的代理类。因为动静代理是在运行时生成代理类的,所以不像在编译期间一样可能间接看到源码。
那么上面指标就很明确了,解决看不到源码的问题。
既然是运行时生成的,那么在运行的时候将生成的代理类写到本地目录下不就能够了吗?至于如何写 Proxy
曾经提供了 ProxyGenerator
。它的generateProxyClass
办法可能帮忙咱们失去生成的代理类。
class Main {
companion object {
@JvmStatic
fun main(args: Array<String>) {val byte = ProxyGenerator.generateProxyClass("\$Proxy0", arrayOf(Bird::class.java))
FileOutputStream("/Users/{path}/Downloads/\$Proxy0.class").apply {write(byte)
flush()
close()}
}
}
}
运行下面的代码就会在 Downloads
目录下找到 $Proxy0.class
文件,将其间接拖到编译器中,关上后的具体代码如下:
public final class $Proxy0 extends Proxy implements Bird {
private static Method m1;
private static Method m4;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(InvocationHandler var1) throws {super(var1);
}
public final boolean equals(Object var1) throws {
try {return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {throw var3;} catch (Throwable var4) {throw new UndeclaredThrowableException(var4);
}
}
public final void fly() throws {
try {super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);
}
}
public final void chirp() throws {
try {super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {throw var2;} catch (Throwable var3) {throw new UndeclaredThrowableException(var3);
}
}
static {
try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m4 = Class.forName("com.daily.algothrim.Bird").getMethod("fly");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.daily.algothrim.Bird").getMethod("chirp");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {throw new NoClassDefFoundError(var3.getMessage());
}
}
}
首先 $Proxy0
继承了 Proxy
同时实现了咱们相熟的 Bird
接口;而后在它的构造方法中承受了一个 var1
参数,它的类型是 InvocationHandler
。持续看办法,实现了类的默认三个办法equals
、toString
与hashCode
,同时也找到了咱们须要的 fly
与chirp
办法。
例如 fly
办法,调用了
super.h.invoke(this, m4, (Object[])null)
这里的 h
就是之前的 var1
,即InvocationHandler
对象。
到这里迷雾曾经揭晓了,调用 invoke
办法,同时将代理类的本身 this
、对应的method
信息与办法参数传递过来。
所以咱们只须要在动静代理的最初一个参数 InvocationHandler
的invoke
办法中进行解决不同代理办法的相干逻辑。这样做的益处是,不论你如何新增与删除 Bird
中的接口办法,我都只有调整 invoke
的解决逻辑即可,将改变的范畴放大到最小化。
这就是动静代理的益处之一(另一个次要的益处天然是缩小代理类的书写)。
在 Android
中使用动静代理的典型非 Retrofit
莫属。因为是一个网络框架,一个 App
对于网络申请来说接口天然是随着 App
的迭代一直减少的。对于这种变动频繁的状况,Retrofit
应用动静代理为入口,暴露出一个对应的 Service
接口,而相干的接口申请办法都在 Service
中进行定义。所以咱们每新增一个接口,都不须要做过多的别的批改,相干的网络申请逻辑都封装到动静代理的 invoke
办法中,当然 Retrofit
原理是借助增加 Annomation
注解的形式来解析不同网络申请的形式与相干的参数逻辑。最终再将解析的数据进行封装传递给上层的OKHttp
。
所以 Retrofit
的外围就是动静代理与注解的解析。
这篇文章的原理解析局部就实现了,最初既然剖析了动静代理与 Retrofit
的关系,我这里提供了一个 Demo
来坚固一下动静代理,同时借鉴 Retroift
的一些思维对一个简易版的打点零碎进行下层封装。
Demo
Demo
是一个简略的模仿打点零碎,通过定义 Statistic
类来创立动静代理,裸露 Service
接口,具体如下:
class Statistic private constructor() {
companion object {
@JvmStatic
val instance by lazy {Statistic() }
}
@Suppress("UNCHECKED_CAST")
fun <T> create(service: Class<T>): T {return Proxy.newProxyInstance(service.classLoader, arrayOf(service)) { proxy, method, args ->
return@newProxyInstance LoadService(method).invoke(args)
} as T
}
}
通过入口传进来的 Service
接口,从而创立对应的动静代理类,而后将对 Service
接口中的办法调用的逻辑解决都封装到了 LoadService
的invoke
办法中。当然 Statistic
也借助了注解来解析不同的打点类型事件。
例如,咱们须要别离对 Button
与Text
进行点击与展现打点统计。
首先咱们能够如下定义对应的 Service
接口,这里命名为StatisticService
interface StatisticService {@Scan(ProxyActivity.PAGE_NAME)
fun buttonScan(@Content(StatisticTrack.Parameter.NAME) name: String)
@Click(ProxyActivity.PAGE_NAME)
fun buttonClick(@Content(StatisticTrack.Parameter.NAME) name: String, @Content(StatisticTrack.Parameter.TIME) clickTime: Long)
@Scan(ProxyActivity.PAGE_NAME)
fun textScan(@Content(StatisticTrack.Parameter.NAME) name: String)
@Click(ProxyActivity.PAGE_NAME)
fun textClick(@Content(StatisticTrack.Parameter.NAME) name: String, @Content(StatisticTrack.Parameter.TIME) clickTime: Long)
}
而后再通过 Statistic
来获取动静代理的代理类对象
private val mStatisticService = Statistic.instance.create(StatisticService::class.java)
有了对应的代理类对象,剩下的就是在对应的地位间接调用。
class ProxyActivity : AppCompatActivity() {private val mStatisticService = Statistic.instance.create(StatisticService::class.java)
companion object {
private const val BUTTON = "statistic_button"
private const val TEXT = "statistic_text"
const val PAGE_NAME = "ProxyActivity"
}
override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)
val extraData = getExtraData()
setContentView(extraData.layoutId)
title = extraData.title
// statistic scan
mStatisticService.buttonScan(BUTTON)
mStatisticService.textScan(TEXT)
}
private fun getExtraData(): MainModel =
intent?.extras?.getParcelable(ActivityUtils.EXTRA_DATA)
?: throw NullPointerException("intent or extras is null")
fun onClick(view: View) {
// statistic click
if (view.id == R.id.button) {mStatisticService.buttonClick(BUTTON, System.currentTimeMillis() / 1000)
} else if (view.id == R.id.text) {mStatisticService.textClick(TEXT, System.currentTimeMillis() / 1000)
}
}
}
这样一个简略的打点下层逻辑封装就实现了。因为篇幅无限 (懒 …) 外部具体的实现逻辑就不开展了。
相干源码都在 android-api-analysis 我的项目中,感兴趣的能够自行查看。
应用前请先把分支切换到
feat_proxy_dev
我的项目
android_startup: 提供一种在利用启动时可能更加简略、高效的形式来初始化组件,优化启动速度。不仅反对 Jetpack App Startup
的全副性能,还提供额定的同步与异步期待、线程管制与多过程反对等性能。
AwesomeGithub: 基于 Github
客户端,纯练习我的项目,反对组件化开发,反对账户明码与认证登陆。应用 Kotlin
语言进行开发,我的项目架构是基于 Jetpack&DataBinding
的MVVM
;我的项目中应用了 Arouter
、Retrofit
、Coroutine
、Glide
、Dagger
与Hilt
等风行开源技术。
flutter_github: 基于 Flutter
的跨平台版本 Github
客户端,与 AwesomeGithub
绝对应。
android-api-analysis: 联合具体的 Demo
来全面解析 Android
相干的知识点, 帮忙读者可能更快的把握与了解所论述的要点。
daily_algorithm: 每日一算法,由浅入深,欢送退出一起共勉。
举荐浏览
重温 Retrofit 源码,笑看协程实现
我为何弃用 Jetpack 的 App Startup?
AwesomeGithub 组件化探索之旅