咱们始终都在应用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接口的对象。当调用ProxyBirdfly办法时,间接调用了传进来的对象的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

下面这种模式就是动态代理,可能有许多读者都曾经不知觉的应用到了这种办法,只是本人没有意识到这是动态代理。

那它的益处是什么呢?

通过下面的例子,很天然的可能领会到动态代理次要帮咱们解决的问题是:

  1. 缩小反复逻辑的编写,提供对立的便捷解决入口。
  2. 封装实现细节。

动静代理

既然曾经有了动态代理,为什么又要来一个动静代理呢?

任何货色的产生都是有它的必要性的,都是为了解决前者不能解决的问题。

所以动静代理就是来解决动态代理所不能解决的问题,亦或者是它的毛病。

假如咱们当初要为Bird新增一种个性:chirp鸟叫。

那么基于后面的动态代理,须要做些什么扭转呢?

  1. 批改Bird接口,新增chirp办法。
  2. 别离批改SparrowEagle,为它们新增chirp的具体实现。
  3. 批改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接口的代理。该办法次要有三个参数别离为:

  1. ClassLoader: 生成代理类的类类加载器。
  2. interface 接口Class数组: 对应的接口Class。
  3. 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。持续看办法,实现了类的默认三个办法equalstoStringhashCode,同时也找到了咱们须要的flychirp办法。

例如fly办法,调用了

super.h.invoke(this, m4, (Object[])null)

这里的h就是之前的var1,即InvocationHandler对象。

到这里迷雾曾经揭晓了,调用invoke办法,同时将代理类的本身this、对应的method信息与办法参数传递过来。

所以咱们只须要在动静代理的最初一个参数InvocationHandlerinvoke办法中进行解决不同代理办法的相干逻辑。这样做的益处是,不论你如何新增与删除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接口中的办法调用的逻辑解决都封装到了LoadServiceinvoke办法中。当然Statistic也借助了注解来解析不同的打点类型事件。

例如,咱们须要别离对ButtonText进行点击与展现打点统计。

首先咱们能够如下定义对应的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&DataBindingMVVM;我的项目中应用了ArouterRetrofitCoroutineGlideDaggerHilt等风行开源技术。

flutter_github: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub绝对应。

android-api-analysis: 联合具体的Demo来全面解析Android相干的知识点, 帮忙读者可能更快的把握与了解所论述的要点。

daily_algorithm: 每日一算法,由浅入深,欢送退出一起共勉。

举荐浏览

重温Retrofit源码,笑看协程实现

我为何弃用Jetpack的App Startup?

AwesomeGithub组件化探索之旅