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