Android 動態(tài)代理以及利用動態(tài)代理實現(xiàn) ServiceHook

這篇博客主要介紹使用 InvocationHandler 這個接口來達(dá)到 hook 系統(tǒng) service ,從而實現(xiàn)一些很有意思特殊功能的詳細(xì)步驟。</br>
  轉(zhuǎn)載請注明出處:http://blog.csdn.net/self_study/article/details/55050627</br>
  對技術(shù)感興趣的同鞋加群 544645972 一起交流。</br>

Java 的動態(tài)代理

首先我們要介紹的就是 Java 動態(tài)代理,Java 的動態(tài)代理涉及到兩個類:InvocationHandler 接口和 Proxy 類,下面我們會著重介紹一下這兩個類,并且結(jié)合實例來著重分析一下使用的正確姿勢等。在這之前簡單介紹一下 Java 中 class 文件的生成和加載過程,Java 編譯器編譯好 Java 文件之后會在磁盤中產(chǎn)生 .class 文件。這種 .class 文件是二進制文件,內(nèi)容是只有 JVM 虛擬機才能識別的機器碼,JVM 虛擬機讀取字節(jié)碼文件,取出二進制數(shù)據(jù),加載到內(nèi)存中,解析 .class 文件內(nèi)的信息,使用相對應(yīng)的 ClassLoader 類加載器生成對應(yīng)的 Class 對象:</br>

這里寫圖片描述
</br>
.class 字節(jié)碼文件是根據(jù) JVM 虛擬機規(guī)范中規(guī)定的字節(jié)碼組織規(guī)則生成的,具體的 .class 文件格式介紹可以查看博客 深入理解Java Class文件格式Java 虛擬機規(guī)范。</br>
  通過上面我們知道 JVM 是通過字節(jié)碼的二進制信息加載類的,那么我們?nèi)绻谶\行期系統(tǒng)中,遵循 Java 編譯系統(tǒng)組織 .class 文件的格式和結(jié)構(gòu),生成相應(yīng)的二進制數(shù)據(jù),然后再把這個二進制數(shù)據(jù)轉(zhuǎn)換成對應(yīng)的類,這樣就可以在運行中動態(tài)生成一個我們想要的類了:</br>
這里寫圖片描述

  Java 中有很多的框架可以在運行時根據(jù) JVM 規(guī)范動態(tài)的生成對應(yīng)的 .class 二進制字節(jié)碼,比如 ASM 和 Javassist 等,這里就不詳細(xì)介紹了,感興趣的可以去查閱相關(guān)的資料。這里我們就以動態(tài)代理模式為例來介紹一下我們要用到這兩個很重要的類,關(guān)于動態(tài)代理模式,我在 java/android 設(shè)計模式學(xué)習(xí)筆記(9)---代理模式中已經(jīng)介紹過了,但是當(dāng)時并沒有詳細(xì)分析過 InvocationHandler 接口和 Proxy 類,這里就來詳細(xì)介紹一下。在代理模式那篇博客中,我們提到了代理模式分為動態(tài)代理和靜態(tài)代理:</br>
這里寫圖片描述
</br>
上面就是靜態(tài)代理模式的類圖,當(dāng)在代碼階段規(guī)定這種代理關(guān)系時,ProxySubject 類通過編譯器生成 .class 字節(jié)碼文件,當(dāng)系統(tǒng)運行之前,這個 .class 文件就已經(jīng)存在了。動態(tài)代理模式的結(jié)構(gòu)和上面的靜態(tài)代理模式的結(jié)構(gòu)稍微有所不同,它引入了一個 InvocationHandler 接口和 Proxy 類。在靜態(tài)代理模式中,代理類 ProxySubject 中的方法,都指定地調(diào)用到特定 RealSubject 中對應(yīng)的方法,ProxySubject 所做的事情無非是調(diào)用觸發(fā) RealSubject 對應(yīng)的方法;動態(tài)代理工作的基本模式就是將自己方法功能的實現(xiàn)交給 InvocationHandler 角色,外界對 Proxy 角色中每一個方法的調(diào)用,Proxy 角色都會交給 InvocationHandler 來處理,而 InvocationHandler 則調(diào)用 RealSubject 的方法,如下圖所示:</br>
這里寫圖片描述
</br>

InvocationHandler 接口和 Proxy

我們來分析一下動態(tài)代理模式中 ProxySubject 的生成步驟:<ol><li>獲取 RealSubject 上的所有接口列表;</li><li>確定要生成的代理類的類名,系統(tǒng)默認(rèn)生成的名字為:com.sun.proxy.$ProxyXXXX ;</li><li>根據(jù)需要實現(xiàn)的接口信息,在代碼中動態(tài)創(chuàng)建該 ProxySubject 類的字節(jié)碼;</li><li> 將對應(yīng)的字節(jié)碼轉(zhuǎn)換為對應(yīng)的 Class 對象;</li><li> 創(chuàng)建 InvocationHandler 的實例對象 h,用來處理 Proxy 角色的所有方法調(diào)用;</li><li>以創(chuàng)建的 h 對象為參數(shù),實例化一個 Proxy 角色對象。</li></ol>具體的代碼為:

Subject.java

public interface Subject {
    String operation();
}

RealSubject.java

public class RealSubject implements Subject{
    @Override
    public String operation() {
        return "operation by subject";
    }
}

ProxySubject.java

public class ProxySubject implements InvocationHandler{
     protected Subject subject;
    public ProxySubject(Subject subject) {
        this.subject = subject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //do something before
        return method.invoke(subject, args);
    }
}

測試代碼

Subject subject = new RealSubject();
ProxySubject proxy = new ProxySubject(subject);
Subject sub = (Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(),
        subject.getClass().getInterfaces(), proxy);
sub.operation();

以上就是動態(tài)代理模式的最簡單實現(xiàn)代碼,JDK 通過使用 java.lang.reflect.Proxy 包來支持動態(tài)代理,我們來看看這個類的表述:

Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the 
superclass of all dynamic proxy classes created by those methods.

一般情況下,我們使用下面的 [newProxyInstance](https://developer.android.com/reference/java/lang/reflect/Proxy.html#newProxyInstance(java.lang.ClassLoader, java.lang.Class<?>[], java.lang.reflect.InvocationHandler)) 方法來生成動態(tài)代理:</br>

Public methods
static Object newProxyInstance(ClassLoader loader, Class[]<?> interfaces, InvocationHandler h) </br>Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler.

對應(yīng)的參數(shù)為:

Parameters
loader ClassLoader: the class loader to define the proxy class
interfaces Class: the list of interfaces for the proxy class to implement
h InvocationHandler: the invocation handler to dispatch method invocations to

我們來仔細(xì)看看這個函數(shù)的實現(xiàn):

public static Object newProxyInstance(ClassLoader loader, Class<?>[]interfaces,InvocationHandler h) throws IllegalArgumentException { 
    // 檢查 h 不為空,否則拋異常
    if (h == null) { 
        throw new NullPointerException(); 
    } 

    // 獲得與指定類裝載器和一組接口相關(guān)的代理類類型對象
    Class cl = getProxyClass(loader, interfaces); 

    // 通過反射獲取構(gòu)造函數(shù)對象并生成代理類實例
    try { 
        Constructor cons = cl.getConstructor(constructorParams); 
        return (Object) cons.newInstance(new Object[] { h }); 
    } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); 
    } catch (IllegalAccessException e) { throw new InternalError(e.toString()); 
    } catch (InstantiationException e) { throw new InternalError(e.toString()); 
    } catch (InvocationTargetException e) { throw new InternalError(e.toString()); 
    } 
}

Proxy 類的 getProxyClass 方法調(diào)用了 ProxyGenerator 的 generatorProxyClass 方法去生成動態(tài)類:

public static byte[] generateProxyClass(final String name, Class[] interfaces)

這個方法我們下面將會介紹到,這里先略過,生成這個動態(tài)類的字節(jié)碼之后,通過反射去生成這個動態(tài)類的對象,通過 Proxy 類的這個靜態(tài)函數(shù)生成了一個動態(tài)代理對象 sub 之后,調(diào)用 sub 代理對象的每一個方法,在代碼內(nèi)部,都是直接調(diào)用了 InvocationHandler 的 invoke 方法,而 invoke 方法根據(jù)代理類傳遞給自己的 method 參數(shù)來區(qū)分是什么方法,我們來看看 InvocationHandler 類的介紹:</br>

InvocationHandler is the interface implemented by the invocation handler of a proxy instance.

Each proxy instance has an associated invocation handler. When a method is invoked on a proxy 
instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.
Public methods
abstract Object invoke(Object proxy, Method method, Object[] args)</br>Processes a method invocation on a proxy instance and returns the result.

方法的參數(shù)和返回:

Parameters
proxy Object: the proxy instance that the method was invoked on
method Method: the Method instance corresponding to the interface method invoked on the proxy instance. The declaring class of the Method object will be the interface that the method was declared in, which may be a superinterface of the proxy interface that the proxy class inherits the method through.
args Object: an array of objects containing the values of the arguments passed in the method invocation on the proxy instance, or null if interface method takes no arguments. Arguments of primitive types are wrapped in instances of the appropriate primitive wrapper class, such as java.lang.Integer or java.lang.Boolean.
Returns
Object the value to return from the method invocation on the proxy instance. If the declared return type of the interface method is a primitive type, then the value returned by this method must be an instance of the corresponding primitive wrapper class; otherwise, it must be a type assignable to the declared return type. If the value returned by this method is null and the interface method's return type is primitive, then a NullPointerException will be thrown by the method invocation on the proxy instance. If the value returned by this method is otherwise not compatible with the interface method's declared return type as described above, a ClassCastException will be thrown by the method invocation on the proxy instance.

上面提到的一點需要特別注意的是,如果 Subject 類中定義的方法返回值為 8 種基本數(shù)據(jù)類型,那么在 ProxySubject 類中必須要返回相應(yīng)的基本類型包裝類,即 int 對應(yīng)的返回為 Integer 等等,還需要注意的是如果此時返回 null,則會拋出 NullPointerException,除此之外的其他情況下返回值的對象必須要和 Subject 類中定義方法的返回值一致,要不然會拋出 ClassCastException。

生成源碼分析

那么通過 Proxy 類的 newProxyInstance 方法動態(tài)生成的類是什么樣子的呢,我們上面也提到了,JDK 為我們提供了一個方法 ProxyGenerator.generateProxyClass(String proxyName,class[] interfaces) 來產(chǎn)生動態(tài)代理類的字節(jié)碼,這個類位于 sun.misc 包中,是屬于特殊的 jar 包,于是問題又來了,android studio 創(chuàng)建的 android 工程是沒法找到 ProxyGenerator 這個類的,這個類在 jre 目錄下,就算我把這個類相關(guān)的 .jar 包拷貝到工程里面并且在 gradle 里面引用它,雖然最后能夠找到這個類,但是編譯時又會出現(xiàn)很奇葩的問題,所以,沒辦法嘍,android studio 沒辦法創(chuàng)建普通的 java 工程,只能自己裝一個 intellij idea 或者求助相關(guān)的同事了。創(chuàng)建好 java 工程之后,使用下面這段代碼就可以將生成的類導(dǎo)出到指定路徑下面:

public static void generateClassFile(Class clazz,String proxyName)
{
    //根據(jù)類信息和提供的代理類名稱,生成字節(jié)碼  
    byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
    String paths = "D:\\"; // 這里寫死路徑為 D 盤,可以根據(jù)實際需要去修改
    System.out.println(paths);
    FileOutputStream out = null;

    try {
        //保留到硬盤中  
        out = new FileOutputStream(paths+proxyName+".class");
        out.write(classFile);
        out.flush();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

調(diào)用代碼的方式為:

generateClassFile(ProxySubject.class, "ProxySubject");  

最后就會在 D 盤(如果沒有修改路徑)的根目錄下面生成一個 ProxySubject.class 的文件,使用 jd-gui 就可以打開該文件:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class ProxySubject
  extends Proxy
  implements Subject
{
  private static Method m1;
  private static Method m3;
  private static Method m2;
  private static Method m0;
  
  public ProxySubject(InvocationHandler paramInvocationHandler)
  {
    super(paramInvocationHandler);
  }
  
  public final boolean equals(Object paramObject)
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final String operation()
  {
    try
    {
      return (String)this.h.invoke(this, m3, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final String toString()
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  public final int hashCode()
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }
  
  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("Subject").getMethod("operation", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}

可以觀察到這個生成的類繼承自 java.lang.reflect.Proxy,實現(xiàn)了 Subject 接口,我們在看看生成動態(tài)類的代碼:

Subject sub = (Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(),
        subject.getClass().getInterfaces(), proxy);

可見這個動態(tài)生成類會實現(xiàn) subject.getClass().getInterfaces() 中的所有接口,并且還有一點是類中所有的方法都是 final 的,而且該類也是 final ,所以該類不可繼承,最后就是所有的方法都會調(diào)用到 InvocationHandler 對象 h 的 invoke() 方法,這也就是為什么最后會調(diào)用到 ProxySubject 類的 invoke() 方法了,畫一下它們的簡單類圖:</br>

這里寫圖片描述
</br>
從這個類圖可以很清楚的看明白,動態(tài)生成的類 ProxySubject(同名,所以后面加上了 Dynamic)持有了實現(xiàn) InvocationHandler 接口的 ProxySubject 類的對象 h,然后調(diào)用代理對象的 operation 方法時,就會調(diào)用到對象 h 的 invoke 方法中,invoke 方法中根據(jù) method 的名字來區(qū)分到底是什么方法,然后通過 method.invoke() 方法來調(diào)用具體對象的對應(yīng)方法。

Android 中利用動態(tài)代理實現(xiàn) ServiceHook

通過上面對 InvocationHandler 的介紹,我們對這個接口應(yīng)該有了大體的了解,但是在運行時動態(tài)生成的代理類有什么作用呢,其實它的作用就是在調(diào)用真正業(yè)務(wù)之前或者之后插入一些額外的操作:</br>


這里寫圖片描述

</br>
所以簡而言之,代理類的處理邏輯很簡單,就是在調(diào)用某個方法前及方法后插入一些額外的業(yè)務(wù)。而我們在 Android 中的實踐例子就是在真正調(diào)用系統(tǒng)的某個 Service 之前和之后選擇性的做一些自己特殊的處理,這種思想在插件化框架上也是很重要的。那么我們具體怎么去實現(xiàn) hook 系統(tǒng)的 Service ,在真正調(diào)用系統(tǒng) Service 的時候附加上我們需要的業(yè)務(wù)呢,這就需要介紹 ServiceManager 這個類了。

ServiceManager 介紹以及 hook 的步驟

<font size=4>第一步</font></br>
  關(guān)于 ServiceManager 的詳細(xì)介紹在我的博客:android IPC通信(下)-AIDL 中已經(jīng)介紹過了,這里就不贅述了,強烈建議大家去看一下那篇博客,我們這里就著重看一下 ServiceManager 的 getService(String name) 方法:

public final class ServiceManager {
    private static final String TAG = "ServiceManager";

    private static IServiceManager sServiceManager;
    private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();

    private static IServiceManager getIServiceManager() {
        if (sServiceManager != null) {
            return sServiceManager;
        }

        // Find the service manager
        sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
        return sServiceManager;
    }

    /**
     * Returns a reference to a service with the given name.
     * 
     * @param name the name of the service to get
     * @return a reference to the service, or <code>null</code> if the service doesn't exist
     */
    public static IBinder getService(String name) {
        try {
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                return getIServiceManager().getService(name);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in getService", e);
        }
        return null;
    }

    public static void addService(String name, IBinder service) {
    ...
    }
    ....
}

我們可以看到,getService 方法第一步會去 sCache 這個 map 中根據(jù) Service 的名字獲取這個 Service 的 IBinder 對象,如果獲取到為空,則會通過 ServiceManagerNative 通過跨進程通信獲取這個 Service 的 IBinder 對象,所以我們就以 sCache 這個 map 為切入點,反射該對象,然后修改該對象,由于系統(tǒng)的 android.os.ServiceManager 類是 @hide 的,所以只能使用反射,根據(jù)這個初步思路,寫下第一步的代碼:

Class c_ServiceManager = Class.forName("android.os.ServiceManager");
if (c_ServiceManager == null) {
    return;
}

if (sCacheService == null) {
    try {
        Field sCache = c_ServiceManager.getDeclaredField("sCache");
        sCache.setAccessible(true);
        sCacheService = (Map<String, IBinder>) sCache.get(null);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
sCacheService.remove(serviceName);
sCacheService.put(serviceName, service);

反射 sCache 這個變量,移除系統(tǒng) Service,然后將我們自己改造過的 Service put 進去,這樣就能實現(xiàn)當(dāng)調(diào)用 ServiceManager 的 getService(String name) 方法的時候,返回的是我們改造過的 Service 而不是系統(tǒng)的原生 Service。</br>
  <font size=4>第二步</font></br>
  第一步知道了如何去將改造過后的 Service put 進系統(tǒng)的 ServiceManager 中,第二步就是去生成一個 hook Service 了,怎么去生成呢?這就要用到我們上面介紹到的 InvocationHandler 類,我們先獲取原生的 Service ,然后通過 InvocationHandler 去構(gòu)造一個 hook Service,最后通過第一步的步驟 put 進 sCache 這個變量即可,第二步代碼:

public class ServiceHook implements InvocationHandler {
    private static final String TAG = "ServiceHook";

    private IBinder mBase;
    private Class<?> mStub;
    private Class<?> mInterface;
    private InvocationHandler mInvocationHandler;

    public ServiceHook(IBinder mBase, String iInterfaceName, boolean isStub, InvocationHandler InvocationHandler) {
        this.mBase = mBase;
        this.mInvocationHandler = InvocationHandler;

        try {
            this.mInterface = Class.forName(iInterfaceName);
            this.mStub = Class.forName(String.format("%s%s", iInterfaceName, isStub ? "$Stub" : ""));
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("queryLocalInterface".equals(method.getName())) {
            return Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[] { mInterface },
                    new HookHandler(mBase, mStub, mInvocationHandler));
        }

        Log.e(TAG, "ERROR!!!!! method:name = " + method.getName());
        return method.invoke(mBase, args);
    }

    private static class HookHandler implements InvocationHandler {
        private Object mBase;
        private InvocationHandler mInvocationHandler;

        public HookHandler(IBinder base, Class<?> stubClass,
                           InvocationHandler InvocationHandler) {
            mInvocationHandler = InvocationHandler;

            try {
                Method asInterface = stubClass.getDeclaredMethod("asInterface", IBinder.class);
                this.mBase = asInterface.invoke(null, base);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (mInvocationHandler != null) {
                return mInvocationHandler.invoke(mBase, method, args);
            }
            return method.invoke(mBase, args);
        }
    }
}

這里我們以 ClipboardService 的調(diào)用代碼為例:

IBinder clipboardService = ServiceManager.getService(Context.CLIPBOARD_SERVICE);
String IClipboard = "android.content.IClipboard";

if (clipboardService != null) {
    IBinder hookClipboardService =
            (IBinder) Proxy.newProxyInstance(clipboardService.getClass().getClassLoader(),
                            clipboardService.getClass().getInterfaces(),
                    new ServiceHook(clipboardService, IClipboard, true, new ClipboardHookHandler()));
    //調(diào)用第一步的方法
    ServiceManager.setService(Context.CLIPBOARD_SERVICE, hookClipboardService);
} else {
    Log.e(TAG, "ClipboardService hook failed!");
}

分析一下上面的這段代碼,分析之前,先要看一下 ClipboardService 的相關(guān)類圖:</br>

這里寫圖片描述
</br>從這張類圖我們可以清晰的看見 ClipboardService 的相關(guān)繼承關(guān)系,接下來就是分析代碼了:
<ul><li>調(diào)用代碼中,Proxy.newProxyInstance 函數(shù)的第二個參數(shù)需要注意,由于 ClipboardService 繼承自三個接口,所以這里需要把所有的接口傳遞進去,但是如果將第二個參數(shù)變更為 new Class[] { IBinder.class } 其實也是沒有問題的(感興趣的可以試一下,第一個參數(shù)修改為 IBinder.class.getClassLoader(),第二個參數(shù)修改為 new Class[]{IBinder.class},也是可以的),因為實際使用的時候,我們只是用到了 IBinder 類的 queryLocalInterface 方法,其他的方法都沒有使用到,接下來我們就會說明 queryLocalInterface 這個函數(shù)的作用;</li><li>Proxy.newProxyInstance 函數(shù)的第三個參數(shù)為 ServcieHook 對象,所以當(dāng)把這個 Service set 進 sCache 變量的時候,所有調(diào)用 ClipboardService 的操作都將調(diào)用到 ServiceHook 中的 invoke 方法中;</li><li>接著我們來看看 ServiceHook 的 invoke 函數(shù),很簡單,當(dāng)函數(shù)為 queryLocalInterface 方法的時候返回一個 HookHandler 的對象,其他的情況直接調(diào)用 method.invoke 原生系統(tǒng)的 ClipboardService 功能,為什么只處理 queryLocalInterface 方法呢,這個我在博客:java/android 設(shè)計模式學(xué)習(xí)筆記(9)---代理模式 中分析 AMS 的時候已經(jīng)提到了,asInterface 方法最終會調(diào)用到 queryLocalInterface 方法,queryLocalInterface 方法最后的返回結(jié)果會作為 asInterface 的結(jié)果而返回給 Service 的調(diào)用方,所以 queryLocalInterface 方法的最后返回的對象是會被外部直接調(diào)用的,這也解釋了為什么調(diào)用代碼中的第二個參數(shù)變更為 new Class[] { IBinder.class } 也是沒有問題,因為第一次調(diào)用到 queryLocalInterface 函數(shù)之后,后續(xù)的所有調(diào)用都到了 HookHandler 對象中,動態(tài)生成的對象中只需要有 IBinder 的 queryLocalInterface 方法即可,而不需要 IClipboard 接口的其他方法;</li><li>接下來是 HookHandler 類,首先我們看看這個類的構(gòu)造函數(shù),第一個參數(shù)為系統(tǒng)的 ClipboardService,第二個參數(shù)為

Class.forName(String.format("%s%s", iInterfaceName, isStub ? "$Stub" : ""))//"android.content.IClipboard"

這個參數(shù)咱們對照上面的類圖,這個類為 ClipboardService 的父類,它里面有一個 asInterface 的方法,通過反射 asInterface 方法然后將 IBinder 對象變成 IInterface 對象,為什么要這么做,可以去看看我的博客: java/android 設(shè)計模式學(xué)習(xí)筆記(9)---代理模式 中的最后總結(jié),通過 ServiceManager.getService 方法獲取一個 IBinder 對象,但是這個 IBinder 對象不能直接調(diào)用,必須要通過 asInterface 方法轉(zhuǎn)成對應(yīng)的 IInterface 對象才可以使用,所以 mBase 對象其實是一個 IInterface 對象:</br>

這里寫圖片描述
</br>
最后也證實了這個結(jié)果,為什么是 Proxy 對象這就不用我解釋了吧;
</li><li>最后是 HookHandler 的 invoke 方法,這個方法調(diào)用到了 ClipboardHookHandler 對象,我們來看看這個類的實現(xiàn):</br>

public class ClipboardHook {

    private static final String TAG = ClipboardHook.class.getSimpleName();

    public static void hookService(Context context) {
        IBinder clipboardService = ServiceManager.getService(Context.CLIPBOARD_SERVICE);
        String IClipboard = "android.content.IClipboard";

        if (clipboardService != null) {
            IBinder hookClipboardService =
                    (IBinder) Proxy.newProxyInstance(IBinder.class.getClassLoader(),
                            new Class[]{IBinder.class},
                            new ServiceHook(clipboardService, IClipboard, true, new ClipboardHookHandler()));
            ServiceManager.setService(Context.CLIPBOARD_SERVICE, hookClipboardService);
        } else {
            Log.e(TAG, "ClipboardService hook failed!");
        }
    }

    public static class ClipboardHookHandler implements InvocationHandler {

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String methodName = method.getName();
            int argsLength = args.length;
            //每次從本應(yīng)用復(fù)制的文本,后面都加上分享的出處
            if ("setPrimaryClip".equals(methodName)) {
                if (argsLength >= 2 && args[0] instanceof ClipData) {
                    ClipData data = (ClipData) args[0];
                    String text = data.getItemAt(0).getText().toString();
                    text += "this is shared from ServiceHook-----by Shawn_Dut";
                    args[0] = ClipData.newPlainText(data.getDescription().getLabel(), text);
                }
            }
            return method.invoke(proxy, args);
        }
    }
}

所以 ClipboardHookHandler 類的 invoke 方法最終獲取到了要 hook 的 Service 的 IInterface 對象(即為 IClipboard.Proxy 對象,最后通過 Binder Driver 調(diào)用到了系統(tǒng)的 ClipboardService 中),調(diào)用函數(shù)的 Method 對象和參數(shù)列表對象,獲取到了這些之后,不用我說了,就可以盡情的去做一些額外的操作了,我這里是在仿照知乎復(fù)制文字時,在后面加上類似的版權(quán)聲明。
</li></ul>

問題討論

上面就是 ServiceHook 的詳細(xì)步驟了,了解它必須要對 InvocationHandler 有詳細(xì)的了解,并且還要去看一下 AOSP 源碼,比如要去 hook ClipboardService ,那么就要去先看看 ClipboardService 的源碼,看看這個類中每個函數(shù)的名字和作用,參數(shù)列表中每個參數(shù)的順序和作用,而且有時候這還遠(yuǎn)遠(yuǎn)不夠,我們知道,隨著 Android 每個版本的更新,這些類可能也會被更新修改甚至刪除,很有可能對于新版本來說老的 hook 方法就不管用了,這時候必須要去了解最新的源碼,看看更新修改的地方,針對新版本去重新制定 hook 的步驟,這是一點需要慎重對待考慮的地方。

步驟

這里寫圖片描述

源碼

此為我們公司某位大神代碼,經(jīng)過整理修改而出,不知道有沒有版權(quán)問題,哈哈哈,謝謝周杰大神,雖然已經(jīng)不在公司,感謝感謝~~</br>
  源碼下載地址:https://github.com/zhaozepeng/ServiceHook</br>
  先來看看運行的效果:</br>
  

這里寫圖片描述
</br>
  最后把所有的代碼貼出來:</br>

ServiceManager.java

public class ServiceManager {

    private static Method sGetServiceMethod;
    private static Map<String, IBinder> sCacheService;
    private static Class c_ServiceManager;

    static {
        try {
            c_ServiceManager = Class.forName("android.os.ServiceManager");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static IBinder getService(String serviceName) {
        if (c_ServiceManager == null) {
            return null;
        }

        if (sGetServiceMethod == null) {
            try {
                sGetServiceMethod = c_ServiceManager.getDeclaredMethod("getService", String.class);
                sGetServiceMethod.setAccessible(true);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
        }

        if (sGetServiceMethod != null) {
            try {
                return (IBinder) sGetServiceMethod.invoke(null, serviceName);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return null;
    }

    public static void setService(String serviceName, IBinder service) {
        if (c_ServiceManager == null) {
            return;
        }

        if (sCacheService == null) {
            try {
                Field sCache = c_ServiceManager.getDeclaredField("sCache");
                sCache.setAccessible(true);
                sCacheService = (Map<String, IBinder>) sCache.get(null);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        sCacheService.remove(serviceName);
        sCacheService.put(serviceName, service);
    }
}

ServiceManager 這個類就是使用反射的方式去獲取對應(yīng) Service (這里不能使用 Context.getSystemService 函數(shù),因為它的返回不是 IBinder 對象,比如對于 ClipboardService,它就是 ClipboardManager 對象)和設(shè)置 service 到 sCache 變量中;

ServiceHook.java

public class ServiceHook implements InvocationHandler {
    private static final String TAG = "ServiceHook";

    private IBinder mBase;
    private Class<?> mStub;
    private Class<?> mInterface;
    private InvocationHandler mInvocationHandler;

    public ServiceHook(IBinder mBase, String iInterfaceName, boolean isStub, InvocationHandler InvocationHandler) {
        this.mBase = mBase;
        this.mInvocationHandler = InvocationHandler;

        try {
            this.mInterface = Class.forName(iInterfaceName);
            this.mStub = Class.forName(String.format("%s%s", iInterfaceName, isStub ? "$Stub" : ""));
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("queryLocalInterface".equals(method.getName())) {
            return Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[] { mInterface },
                    new HookHandler(mBase, mStub, mInvocationHandler));
        }

        Log.e(TAG, "ERROR!!!!! method:name = " + method.getName());
        return method.invoke(mBase, args);
    }

    private static class HookHandler implements InvocationHandler {
        private Object mBase;
        private InvocationHandler mInvocationHandler;

        public HookHandler(IBinder base, Class<?> stubClass,
                           InvocationHandler InvocationHandler) {
            mInvocationHandler = InvocationHandler;

            try {
                Method asInterface = stubClass.getDeclaredMethod("asInterface", IBinder.class);
                this.mBase = asInterface.invoke(null, base);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (mInvocationHandler != null) {
                return mInvocationHandler.invoke(mBase, method, args);
            }
            return method.invoke(mBase, args);
        }
    }
}

這個類上面介紹的很詳細(xì)了,在這里就不繼續(xù)介紹了;

ClipboardHook.java

public class ClipboardHook {

    private static final String TAG = ClipboardHook.class.getSimpleName();

    public static void hookService(Context context) {
        IBinder clipboardService = ServiceManager.getService(Context.CLIPBOARD_SERVICE);
        String IClipboard = "android.content.IClipboard";

        if (clipboardService != null) {
            IBinder hookClipboardService =
                    (IBinder) Proxy.newProxyInstance(IBinder.class.getClassLoader(),
                            new Class[]{IBinder.class},
                            new ServiceHook(clipboardService, IClipboard, true, new ClipboardHookHandler()));
            ServiceManager.setService(Context.CLIPBOARD_SERVICE, hookClipboardService);
        } else {
            Log.e(TAG, "ClipboardService hook failed!");
        }
    }

    public static class ClipboardHookHandler implements InvocationHandler {

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String methodName = method.getName();
            int argsLength = args.length;
            //每次從本應(yīng)用復(fù)制的文本,后面都加上分享的出處
            if ("setPrimaryClip".equals(methodName)) {
                if (argsLength >= 2 && args[0] instanceof ClipData) {
                    ClipData data = (ClipData) args[0];
                    String text = data.getItemAt(0).getText().toString();
                    text += "this is shared from ServiceHook-----by Shawn_Dut";
                    args[0] = ClipData.newPlainText(data.getDescription().getLabel(), text);
                }
            }
            return method.invoke(proxy, args);
        }
    }
}

這里的 hook ClipboardService 使用的是,將復(fù)制的文本取出,在結(jié)尾處添加我們自定義的文本,用戶再粘貼到其他地方的時候,就會出現(xiàn)我們添加上我們自定義文本后的文字了,還有需要注意的是這只會影響應(yīng)用進程的 ClipboardService,并不會影響主進程的相關(guān) Service,因為不管怎么 hook,修改的都是應(yīng)用進程的 ServiceManager 里面的 sCache 變量,應(yīng)用進程的 ServiceManager 其實也就相當(dāng)于一個 Binder Client,sCache 里面獲取不到對應(yīng)的 Service,它就會自動通過 Binder Driver 和 Binder Server (主進程的 ServiceManager)通信去獲取對應(yīng)的 Service,所以修改 Binder Client,其實是不會影響 Binder Server的,不明白的建議還是看看:android IPC通信(下)-AIDL。

測試代碼

switch (v.getId()) {
    case R.id.btn_copy:
        String input = mEtInput.getText().toString().trim();
        if (TextUtils.isEmpty(input)) {
            Toast.makeText(this, "input不能為空", Toast.LENGTH_SHORT).show();
            return;
         }

        //復(fù)制
        ClipData clip = ClipData.newPlainText("simple text", mEtInput.getText().toString());
        clipboard.setPrimaryClip(clip);
        break;
    case R.id.btn_show_paste:
        //黏貼
        clip = clipboard.getPrimaryClip();
        if (clip != null && clip.getItemCount() > 0) {
            Toast.makeText(this, clip.getItemAt(0).getText(), Toast.LENGTH_SHORT).show();
        }
        break;
}

測試代碼,我這里就不需要說明了,Clipboard 的簡單使用而已。</br>  
  這里只演示了 hook ClipboardService 的例子,其他的使用方式比如插件化等等在這里就不一一介紹了,感興趣的可以去網(wǎng)上查閱相關(guān)的資料。
  轉(zhuǎn)載請注明出處:http://blog.csdn.net/self_study/article/details/55050627

引用

http://blog.csdn.net/luanlouis/article/details/24589193
http://www.cnblogs.com/xiaoluo501395377/p/3383130.html
http://blog.csdn.net/self_study/article/details/51628486
http://paddy-w.iteye.com/blog/841798
http://www.cnblogs.com/flyoung2008/archive/2013/08/11/3251148.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容