咱們一直都在使用Retroift
,都知道它的核心是動態代理。例如在以前的文章重溫Retrofit源碼,笑看協程實現中也簡單說起到動態代理(來填以前挖的坑...)。java
咳咳,你們不要關注原由,仍是要回歸當前的內容。android
此次主要是來分析一下動態代理的做用與實現原理。既然都已經分析了原理,最後天然也要動手仿照Retrofit
來簡單實現一個Demo
。git
經過最後的Demo
實現,相信動態代理你也基本沒什麼問題了。github
既然說到動態代理,天然少不了靜態代理。那麼靜態代理究竟是什麼呢?咱們仍是經過一個簡單的場景來了解。算法
假設有一個Bird
接口來表明鳥的一些特性,例如fly
飛行特性編程
interface Bird { fun fly() }
如今分別有麻雀、老鷹等動物,由於它們都是鳥類,因此都會實現Bird
接口,內部實現本身的fly
邏輯。api
// 麻雀 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
代理方法。一、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
接口的代理。該方法主要有三個參數分別爲:
這裏關鍵點是第三個參數,全部經過調用代理類的代理方法都會在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
是一個簡單的模擬打點系統,經過定義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: 每日一算法,由淺入深,歡迎加入一塊兒共勉。