前言:插件化在Android開發(fā)中的優(yōu)點不言而喻,也有很多文章介紹插件化的優(yōu)勢,所以在此不再贅述。前一陣子在項目中用到 DroidPlugin 插件框架 ,近期準備投入生產環(huán)境時出現(xiàn)了一些小問題,所以決心花些時間研究了一下 DroidPlugin 插件框架的原理,以便再出現(xiàn)問題時也能從容應對。打開源碼后發(fā)現(xiàn)盡是大把大把的 hook、binder、classloader 等等,很難摸清頭緒,幸運的是,有很多熱心的大神已經(jīng)對 DroidPlugin 的原理進行了透徹的剖析,文末會有本人對參考文章的致謝。
本系列文章的代碼已經(jīng)上傳至github,下載地址:https://github.com/lgliuwei/DroidPluginStudy 本篇文章對應的代碼在 com.liuwei.proxy_hook.proxy 包內。
· 代理模式
在 DroidPlugin 中用到了大量的動態(tài)代理,所以如果我們想理解 DroidPlugin 的原理,首先我們需要知道什么是動態(tài)代理,說到動態(tài)代理,我們難免會想起靜態(tài)代理,那么代理是什么呢?
代理模式的意圖是通過提供一個代理( Proxy )或者占位符來控制對該對象的訪問。類比我們生活中,代理也是隨處可見,其中中介就是一個很好的例子,把代理看做生活中的中介,將更加易于理解,試想一下,如果我們想租房或者買房的話通過中間是不是就可以讓我們非常省心。
一、靜態(tài)代理
為了保證與所代理的對象功能行為的一致性,代理類一般需要實現(xiàn)實體類所實現(xiàn)的同一個接口,以下即為一個最基本的代理模式的結構。
首先先定義一個接口,供實體類和代理類實現(xiàn)。(如:接口 Sbuject1 )
1 /**2 * Created by liuwei on 17/3/1.3 */4 public interface Subject1 {5 void method1();6 void method2();7 }
然后創(chuàng)建一個 Subject1 的實現(xiàn)類。
1 /** 2 * 實體類 3 * Created by liuwei on 17/3/1. 4 */ 5 public class RealSubject1 implements Subject1 { 6 @Override 7 public void method1() { 8 Logger.i(RealSubject1.class, "我是RealSubject1的方法1"); 9 }10 @Override11 public void method2() {12 Logger.i(RealSubject1.class, "我是RealSubject1的方法2");13 }14 }
再為 RealSubject1 創(chuàng)建一個代理類。
1 /** 2 * 靜態(tài)代理類 3 * Created by liuwei on 17/3/1. 4 */ 5 public class ProxySubject1 implements Subject1 { 6 private Subject1 subject1; 7 public ProxySubject1(Subject1 subject1) { 8 this.subject1 = subject1; 9 }10 @Override11 public void method1() {12 Logger.i(ProxySubject1.class, "我是代理,我會在執(zhí)行實體方法1之前先做一些預處理的工作");13 subject1.method1();14 }15 @Override16 public void method2() {17 Logger.i(ProxySubject1.class, "我是代理,我會在執(zhí)行實體方法2之前先做一些預處理的工作");18 subject1.method2();19 }20 }
可以發(fā)現(xiàn),代理模式還是很簡單的,很快我們就寫好一個最基本的代理結構,接下來寫個測試類跑一下看看效果。
1 /** 2 * Created by liuwei on 17/3/1. 3 */ 4 public class ProxyTest { 5 public static void main(String[] args){ 6 // static proxy 7 ProxySubject1 proxySubject1 = new ProxySubject1(new RealSubject1()); 8 proxySubject1.method1(); 9 proxySubject1.method2();10 }
輸出結果非常簡單,這里就不再貼出來了。我們看到,在測試類中只需要調用 ProxySubject1 的對像即可對實現(xiàn)對 RealSubject1 的操作。同時我們也發(fā)現(xiàn)在初始化 ProxySubject1 時需要傳入 RealSubject1 的對象,當然,我們完全可以把獲取 RealSubject1 的對象封裝到代理類內部,這只是代理模式根據(jù)業(yè)務需要的不同體現(xiàn)而已。有很多人把這一點作為區(qū)分代理模式和適配器模式的依據(jù),這個是不對的,由于本篇的重點是為插件化的原理做鋪墊,至于代理模式和適配器模式的區(qū)別日后會專門寫一篇文章介紹,這里就不細說了。
其實,從這個簡單的示例中也許并沒有體現(xiàn)出代理模式的優(yōu)勢,而且還要多創(chuàng)建一個代理類,反而看起來好像更麻煩了。其實代理模式很明顯的好處就是通過代理,可以控制對實體對象的訪問,從而提高了安全性。而且可以在調用實體類的方法時做一些預處理和善后的工作,這樣就保證了實體類可以拋開復雜的業(yè)務邏輯而只去實現(xiàn)一些最純粹的功能,提高了代碼的可讀性和靈活性。
二、動態(tài)代理
動態(tài)代理是本文的重點,也是 DroidPlugin 插件化框架的基礎。動態(tài)代理乍一聽起來好像也挺高大上的,但幸運的是,它并沒有我們想象中那么高深莫測,所以我們大可不必對它有任何的畏懼之感。
假設我們在上文靜態(tài)代理的例子中又多了一個 RealSubject2 的類,它實現(xiàn)的接口是 Subject2,這時候我們如果想對 RealSubject2 進行代理需要如何做?這個簡單,我們直接類比 ProxySubject1 再創(chuàng)建一個 ProxySubject2 即可,這樣是可以的,但如果有非常多的實體類并且都實現(xiàn)了不同的接口,那我們豈不是需要創(chuàng)建很多的代理類:ProxySubject1,ProxySubject2 ... ProxySubjectN!還有沒有更優(yōu)雅一些的方法?答案是肯定的,動態(tài)代理即可解決這個問題。(當然,這并不是動態(tài)代理唯一的優(yōu)點)
動態(tài)代理是在實現(xiàn)階段不需要關心代理誰,在運行階段才指定代理對象。創(chuàng)建一個動態(tài)代理類很簡單,JDK已經(jīng)給我們提供好了動態(tài)代理接口 InvocationHandler 我們只需要實現(xiàn)它即可創(chuàng)建一個動態(tài)代理類,以下是一個簡單的小例子:
1 /** 2 * 動態(tài)代理 3 * Created by liuwei on 17/3/1. 4 * 注:動態(tài)代理的步驟: 5 * 1、寫一個InvocationHandler的實現(xiàn)類,并實現(xiàn)invoke方法,return method.invoke(...); 6 * 2、使用Proxy類的newProxyInstance方法生成一個代理對象。例如:生成Subject1的代理對象,注意第三個參數(shù)中要將一個實體對象傳入 7 * Proxy.newProxyInstance( 8 Subject1.class.getClassLoader(), 9 new Class[] {Subject1.class},10 new DynamicProxyHandler(new RealSubject1()));11 12 */13 public class DynamicProxyHandler implements InvocationHandler {14 private Object object;15 16 public DynamicProxyHandler(Object object) {17 this.object = object;18 }19 20 @Override21 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {22 Logger.i(DynamicProxyHandler.class, "我正在動態(tài)代理[" + object.getClass().getSimpleName() + "]的[" + method.getName() + "]方法");23 return method.invoke(object, args);24 }25 26 /**27 * 調用Proxy.newProxyInstance即可生成一個代理對象28 * @param object29 * @return30 */31 public static Object newProxyInstance(Object object) {32 // 傳入被代理對象的classloader,實現(xiàn)的接口,還有DynamicProxyHandler的對象即可。33 return Proxy.newProxyInstance(object.getClass().getClassLoader(),34 object.getClass().getInterfaces(),35 new DynamicProxyHandler(object));36 }37 }
這是一個名為 DynamicProxyHandler 的動態(tài)代理類,其中 invoke 方法完成了對代理對象方法的調用,是必須實現(xiàn)的。接下來使用此類代理其他的實體類也非常簡單,只需使用 Proxy 的newProxyInstance() 方法并傳入相應的參數(shù)即可獲取一個代理對象,接下來我們在測試類里面添加一下代碼,代碼如下:
1 /** 2 * Created by liuwei on 17/3/1. 3 */ 4 public class ProxyTest { 5 public static void main(String[] args){ 6 // static proxy 7 ProxySubject1 proxySubject1 = new ProxySubject1(new RealSubject1()); 8 proxySubject1.method1(); 9 proxySubject1.method2();10 11 // 如果想對RealSubject2代理顯然不得不重新再寫一個代理類。12 ProxySubject2 proxySubject2 = new ProxySubject2(new RealSubject2());13 proxySubject2.method1();14 proxySubject2.method2();15 16 Logger.i(ProxyTest.class, "----------分割線----------\n");17 18 // 如果寫一個代理類就能對上面兩個都能代理就好了,動態(tài)代理就解決了這個問題19 Subject1 dynamicProxyHandler1 = (Subject1) DynamicProxyHandler.newProxyInstance(new RealSubject1());20 dynamicProxyHandler1.method1();21 dynamicProxyHandler1.method2();22 23 Subject2 dynamicProxyHandler2 = (Subject2)DynamicProxyHandler.newProxyInstance(new RealSubject2());24 dynamicProxyHandler2.method1();25 dynamicProxyHandler2.method2();26 }27 }
輸出結果非常簡單,這里不再給出。
三、小結
至此,相信我們對動態(tài)代理已經(jīng)有一個基本的認識,其實代理模式除了上文中提到的普通代理(靜態(tài)代理的一種)、動態(tài)代理之外還有很多種方式,如遠程代理、虛擬代理、智能代理等等,這里就不一一介紹了。
其實插件化的原理簡單來說是使用動態(tài)代理,通過反射等機制將系統(tǒng)中的一些方法hook掉,從而達到劫持系統(tǒng)方法的目的以實現(xiàn)對系統(tǒng)方法的篡改。例如通過 hook 掉 AMS 的 startActivity 方法來啟動一個沒有在清單文件中配置的 Activity 。下一篇文章將詳細介紹 Hook 機制,以及反射在 Hook 中的實際體現(xiàn)?! ?/p>
致謝:最后我想說的是“吃水不忘挖井人”!非常感謝術哥的《Android插件化原理解析——概要》系列文章,本人正是在參考了這些內容的思路之后才有能力寫下本系列文章。本人在Android的插件化領域可以說算是一個小白,寫下本系列文章的目的一方面是在實踐中加深自己的理解,另一方面是根據(jù)本人以小白角度對插件化原理的體會用更加簡單易懂的方式傳達出來,從而幫助小白也能讀懂插件化!
本文鏈接:http://www.cnblogs.com/codingblock/p/6580364.html。
------------------------------------------------
本文歡迎轉載,但請務必保留本文鏈接,謝謝!
作者:CodingBlock
出處:http://www.cnblogs.com/codingblock/
文中如有錯誤歡迎指正,感激不盡!
http://www.cnblogs.com/codingblock/p/6580364.html