動(dòng)態(tài)代理分析與仿Retrofit實(shí)踐

我們一直都在使用Retroift,都知道它的核心是動(dòng)態(tài)代理。例如在之前的文章重溫Retrofit源碼,笑看協(xié)程實(shí)現(xiàn)中也簡(jiǎn)單提及到動(dòng)態(tài)代理(來(lái)填之前挖的坑...)。

咳咳,大家不要關(guān)注起因,還是要回歸當(dāng)前的內(nèi)容。

這次主要是來(lái)分析一下動(dòng)態(tài)代理的作用與實(shí)現(xiàn)原理。既然都已經(jīng)分析了原理,最后自然也要?jiǎng)邮址抡?code>Retrofit來(lái)簡(jiǎn)單實(shí)現(xiàn)一個(gè)Demo。

通過(guò)最后的Demo實(shí)現(xiàn),相信動(dòng)態(tài)代理你也基本沒(méi)什么問(wèn)題了。

靜態(tài)代理

既然說(shuō)到動(dòng)態(tài)代理,自然少不了靜態(tài)代理。那么靜態(tài)代理到底是什么呢?我們還是通過(guò)一個(gè)簡(jiǎn)單的場(chǎng)景來(lái)了解。

假設(shè)有一個(gè)Bird接口來(lái)代表鳥(niǎo)的一些特性,例如fly飛行特性

interface Bird {
    fun fly()
}

現(xiàn)在分別有麻雀、老鷹等動(dòng)物,因?yàn)樗鼈兌际区B(niǎo)類,所以都會(huì)實(shí)現(xiàn)Bird接口,內(nèi)部實(shí)現(xiàn)自己的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)
    }
}

麻雀與老鷹的飛行能力都實(shí)現(xiàn)了,現(xiàn)在有個(gè)需求:需要分別統(tǒng)計(jì)麻雀與老鷹飛行的時(shí)長(zhǎng)。

你會(huì)怎么做呢?相信在我們剛學(xué)習(xí)編程的時(shí)候都會(huì)想到的是:這還不簡(jiǎn)單直接在麻雀與老鷹的fly方法中分別統(tǒng)計(jì)就可以了。

如果實(shí)現(xiàn)的鳥(niǎo)類種類不多的話,這種實(shí)現(xiàn)不會(huì)有太大的問(wèn)題,但是一旦實(shí)現(xiàn)的鳥(niǎo)類種類很多,那么這種方法重復(fù)做的邏輯將會(huì)很多,因?yàn)槲覀円矫恳环N鳥(niǎo)類的fly方法中都去添加統(tǒng)計(jì)時(shí)長(zhǎng)的邏輯。

所以為了解決這種無(wú)意義的重復(fù)邏輯,我們可以通過(guò)一個(gè)ProxyBird來(lái)代理實(shí)現(xiàn)時(shí)長(zhǎng)的統(tǒng)計(jì)。

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實(shí)現(xiàn)了Bird接口,同時(shí)接受了外部傳進(jìn)來(lái)的實(shí)現(xiàn)Bird接口的對(duì)象。當(dāng)調(diào)用ProxyBirdfly方法時(shí),間接調(diào)用了傳進(jìn)來(lái)的對(duì)象的fly方法,同時(shí)還進(jìn)行來(lái)時(shí)長(zhǎng)的統(tǒng)計(jì)。

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

上面這種模式就是靜態(tài)代理,可能有許多讀者都已經(jīng)不知覺(jué)的使用到了這種方法,只是自己沒(méi)有意識(shí)到這是靜態(tài)代理。

那它的好處是什么呢?

通過(guò)上面的例子,很自然的能夠體會(huì)到靜態(tài)代理主要幫我們解決的問(wèn)題是:

  1. 減少重復(fù)邏輯的編寫(xiě),提供統(tǒng)一的便捷處理入口。
  2. 封裝實(shí)現(xiàn)細(xì)節(jié)。

動(dòng)態(tài)代理

既然已經(jīng)有了靜態(tài)代理,為什么又要來(lái)一個(gè)動(dòng)態(tài)代理呢?

任何東西的產(chǎn)生都是有它的必要性的,都是為了解決前者不能解決的問(wèn)題。

所以動(dòng)態(tài)代理就是來(lái)解決靜態(tài)代理所不能解決的問(wèn)題,亦或者是它的缺點(diǎn)。

假設(shè)我們現(xiàn)在要為Bird新增一種特性:chirp鳥(niǎo)叫。

那么基于前面的靜態(tài)代理,需要做些什么改變呢?

  1. 修改Bird接口,新增chirp方法。
  2. 分別修改SparrowEagle,為它們新增chirp的具體實(shí)現(xiàn)。
  3. 修改ProxyBird,實(shí)現(xiàn)chirp代理方法。

1、3還好,尤其是2,一旦實(shí)現(xiàn)Bird接口的鳥(niǎo)類種類很多的話,將會(huì)非常繁瑣,這時(shí)就真的是牽一發(fā)動(dòng)全身了。

這還是改動(dòng)現(xiàn)有的Bird接口,可能你還需要新增另外一種接口,例如Fish魚(yú),實(shí)現(xiàn)有關(guān)魚(yú)的特性。

這時(shí)又要重新生成一個(gè)新的代理ProxyFish來(lái)管理有關(guān)魚(yú)的代理。

所以從這一點(diǎn),我們可以發(fā)現(xiàn)靜態(tài)代理的機(jī)動(dòng)性很差,對(duì)于那些實(shí)現(xiàn)了之后不怎么改變的功能,可以考慮使用它來(lái)實(shí)現(xiàn),這也完全符合它的名字中的靜態(tài)的特性。

那么這種情況動(dòng)態(tài)代理就能夠解決嗎?別急,能否解決接著往下看。

接著上面,我們?yōu)?code>Bird新增chirp方法

interface Bird {
    fun fly()
    
    fun chirp()
}

然后再通過(guò)動(dòng)態(tài)代理的方式來(lái)實(shí)現(xiàn)這個(gè)接口

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.

方式很簡(jiǎn)單,通過(guò)Proxy.newProxyInstance靜態(tài)方法來(lái)創(chuàng)建一個(gè)實(shí)現(xiàn)Bird接口的代理。該方法主要有三個(gè)參數(shù)分別為:

  1. ClassLoader: 生成代理類的類類加載器。
  2. interface 接口Class數(shù)組: 對(duì)應(yīng)的接口Class。
  3. InvocationHandler: InvocationHandler對(duì)象,所有代理方法的回調(diào)。

這里關(guān)鍵點(diǎn)是第三個(gè)參數(shù),所有通過(guò)調(diào)用代理類的代理方法都會(huì)在InvocationHandler對(duì)象中通過(guò)它的invoke方法進(jìn)行回調(diào)

public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}

這就是上面將判斷調(diào)用具體接口方法的邏輯寫(xiě)在InvocationHandler對(duì)象的invoke方法的原因。

那它到底是如何實(shí)現(xiàn)的呢?怎么就成了一個(gè)代理類呢?我也沒(méi)看到代理類在哪???怎么就所有調(diào)用都通過(guò)InvocationHandler的呢?

有這些疑問(wèn)很正常,開(kāi)始接觸動(dòng)態(tài)代理時(shí)都會(huì)有這些疑問(wèn)。導(dǎo)致這些疑問(wèn)的直接原因是我們不能直接看到所謂的代理類。因?yàn)閯?dòng)態(tài)代理是在運(yùn)行時(shí)生成代理類的,所以不像在編譯時(shí)期一樣能夠直接看到源碼。

那么下面目標(biāo)就很明確了,解決看不到源碼的問(wèn)題。

既然是運(yùn)行時(shí)生成的,那么在運(yùn)行的時(shí)候?qū)⑸傻拇眍悓?xiě)到本地目錄下不就可以了嗎?至于如何寫(xiě)Proxy已經(jīng)提供了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()
            }
        }
    }
}

運(yùn)行上面的代碼就會(huì)在Downloads目錄下找到$Proxy0.class文件,將其直接拖到編譯器中,打開(kāi)后的具體代碼如下:

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同時(shí)實(shí)現(xiàn)了我們熟悉的Bird接口;然后在它的構(gòu)造方法中接受了一個(gè)var1參數(shù),它的類型是InvocationHandler。繼續(xù)看方法,實(shí)現(xiàn)了類的默認(rèn)三個(gè)方法equals、toStringhashCode,同時(shí)也找到了我們需要的flychirp方法。

例如fly方法,調(diào)用了

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

這里的h就是之前的var1,即InvocationHandler對(duì)象。

到這里迷霧已經(jīng)揭曉了,調(diào)用invoke方法,同時(shí)將代理類的自身this、對(duì)應(yīng)的method信息與方法參數(shù)傳遞過(guò)去。

所以我們只需要在動(dòng)態(tài)代理的最后一個(gè)參數(shù)InvocationHandlerinvoke方法中進(jìn)行處理不同代理方法的相關(guān)邏輯。這樣做的好處是,不管你如何新增與刪除Bird中的接口方法,我都只要調(diào)整invoke的處理邏輯即可,將改動(dòng)的范圍縮小到最小化。

這就是動(dòng)態(tài)代理的好處之一(另一個(gè)主要的好處自然是減少代理類的書(shū)寫(xiě))。

Android中運(yùn)用動(dòng)態(tài)代理的典型非Retrofit莫屬。由于是一個(gè)網(wǎng)絡(luò)框架,一個(gè)App對(duì)于網(wǎng)絡(luò)請(qǐng)求來(lái)說(shuō)接口自然是隨著App的迭代不斷增加的。對(duì)于這種變化頻繁的情況,Retrofit使用動(dòng)態(tài)代理為入口,暴露出一個(gè)對(duì)應(yīng)的Service接口,而相關(guān)的接口請(qǐng)求方法都在Service中進(jìn)行定義。所以我們每新增一個(gè)接口,都不需要做過(guò)多的別的修改,相關(guān)的網(wǎng)絡(luò)請(qǐng)求邏輯都封裝到動(dòng)態(tài)代理的invoke方法中,當(dāng)然Retrofit原理是借助添加Annomation注解的方式來(lái)解析不同網(wǎng)絡(luò)請(qǐng)求的方式與相關(guān)的參數(shù)邏輯。最終再將解析的數(shù)據(jù)進(jìn)行封裝傳遞給下層的OKHttp。

所以Retrofit的核心就是動(dòng)態(tài)代理與注解的解析。

這篇文章的原理解析部分就完成了,最后既然分析了動(dòng)態(tài)代理與Retrofit的關(guān)系,我這里提供了一個(gè)Demo來(lái)鞏固一下動(dòng)態(tài)代理,同時(shí)借鑒Retroift的一些思想對(duì)一個(gè)簡(jiǎn)易版的打點(diǎn)系統(tǒng)進(jìn)行上層封裝。

Demo

Demo是一個(gè)簡(jiǎn)單的模擬打點(diǎn)系統(tǒng),通過(guò)定義Statistic類來(lái)創(chuàng)建動(dòng)態(tài)代理,暴露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
    }

}

通過(guò)入口傳進(jìn)來(lái)的Service接口,從而創(chuàng)建對(duì)應(yīng)的動(dòng)態(tài)代理類,然后將對(duì)Service接口中的方法調(diào)用的邏輯處理都封裝到了LoadServiceinvoke方法中。當(dāng)然Statistic也借助了注解來(lái)解析不同的打點(diǎn)類型事件。

例如,我們需要分別對(duì)ButtonText進(jìn)行點(diǎn)擊與展示打點(diǎn)統(tǒng)計(jì)。

首先我們可以如下定義對(duì)應(yīng)的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)
}

然后再通過(guò)Statistic來(lái)獲取動(dòng)態(tài)代理的代理類對(duì)象

private val mStatisticService = Statistic.instance.create(StatisticService::class.java)

有了對(duì)應(yīng)的代理類對(duì)象,剩下的就是在對(duì)應(yīng)的位置直接調(diào)用。

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)
        }
    }
}

這樣一個(gè)簡(jiǎn)單的打點(diǎn)上層邏輯封裝就完成了。由于篇幅有限(懶...)內(nèi)部具體的實(shí)現(xiàn)邏輯就不展開(kāi)了。

相關(guān)源碼都在android-api-analysis項(xiàng)目中,感興趣的可以自行查看。

使用前請(qǐng)先把分支切換到feat_proxy_dev

項(xiàng)目

android_startup: 提供一種在應(yīng)用啟動(dòng)時(shí)能夠更加簡(jiǎn)單、高效的方式來(lái)初始化組件,優(yōu)化啟動(dòng)速度。不僅支持Jetpack App Startup的全部功能,還提供額外的同步與異步等待、線程控制與多進(jìn)程支持等功能。

AwesomeGithub: 基于Github客戶端,純練習(xí)項(xiàng)目,支持組件化開(kāi)發(fā),支持賬戶密碼與認(rèn)證登陸。使用Kotlin語(yǔ)言進(jìn)行開(kāi)發(fā),項(xiàng)目架構(gòu)是基于Jetpack&DataBindingMVVM;項(xiàng)目中使用了Arouter、RetrofitCoroutine、GlideDaggerHilt等流行開(kāi)源技術(shù)。

flutter_github: 基于Flutter的跨平臺(tái)版本Github客戶端,與AwesomeGithub相對(duì)應(yīng)。

android-api-analysis: 結(jié)合詳細(xì)的Demo來(lái)全面解析Android相關(guān)的知識(shí)點(diǎn), 幫助讀者能夠更快的掌握與理解所闡述的要點(diǎn)。

daily_algorithm: 每日一算法,由淺入深,歡迎加入一起共勉。

推薦閱讀

重溫Retrofit源碼,笑看協(xié)程實(shí)現(xiàn)

我為何棄用Jetpack的App Startup?

AwesomeGithub組件化探索之旅

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容