咱们始终都在应用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组件化探索之旅