Android 動(dòng)態(tài)代理以及利用動(dòng)態(tài)代理實(shí)現(xiàn) ServiceHook

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

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

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

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

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

InvocationHandler 接口和 Proxy

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

測(cè)試代碼

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

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

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)) 方法來生成動(dòng)態(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.

對(duì)應(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ì)看看這個(gè)函數(shù)的實(shí)現(xiàn):

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

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

    // 通過反射獲取構(gòu)造函數(shù)對(duì)象并生成代理類實(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 方法去生成動(dòng)態(tài)類:

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

這個(gè)方法我們下面將會(huì)介紹到,這里先略過,生成這個(gè)動(dòng)態(tài)類的字節(jié)碼之后,通過反射去生成這個(gè)動(dòng)態(tài)類的對(duì)象,通過 Proxy 類的這個(gè)靜態(tài)函數(shù)生成了一個(gè)動(dòng)態(tài)代理對(duì)象 sub 之后,調(diào)用 sub 代理對(duì)象的每一個(gè)方法,在代碼內(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.

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

生成源碼分析

那么通過 Proxy 類的 newProxyInstance 方法動(dòng)態(tài)生成的類是什么樣子的呢,我們上面也提到了,JDK 為我們提供了一個(gè)方法 ProxyGenerator.generateProxyClass(String proxyName,class[] interfaces) 來產(chǎn)生動(dòng)態(tài)代理類的字節(jié)碼,這個(gè)類位于 sun.misc 包中,是屬于特殊的 jar 包,于是問題又來了,android studio 創(chuàng)建的 android 工程是沒法找到 ProxyGenerator 這個(gè)類的,這個(gè)類在 jre 目錄下,就算我把這個(gè)類相關(guān)的 .jar 包拷貝到工程里面并且在 gradle 里面引用它,雖然最后能夠找到這個(gè)類,但是編譯時(shí)又會(huì)出現(xiàn)很奇葩的問題,所以,沒辦法嘍,android studio 沒辦法創(chuàng)建普通的 java 工程,只能自己裝一個(gè) 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ù)實(shí)際需要去修改
    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");  

最后就會(huì)在 D 盤(如果沒有修改路徑)的根目錄下面生成一個(gè) 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());
    }
  }
}

可以觀察到這個(gè)生成的類繼承自 java.lang.reflect.Proxy,實(shí)現(xiàn)了 Subject 接口,我們?cè)诳纯瓷蓜?dòng)態(tài)類的代碼:

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

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

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

Android 中利用動(dòng)態(tài)代理實(shí)現(xiàn) ServiceHook

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


這里寫圖片描述

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

ServiceManager 介紹以及 hook 的步驟

<font size=4>第一步</font></br>
  關(guān)于 ServiceManager 的詳細(xì)介紹在我的博客:android IPC通信(下)-AIDL 中已經(jīng)介紹過了,這里就不贅述了,強(qiá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 方法第一步會(huì)去 sCache 這個(gè) map 中根據(jù) Service 的名字獲取這個(gè) Service 的 IBinder 對(duì)象,如果獲取到為空,則會(huì)通過 ServiceManagerNative 通過跨進(jìn)程通信獲取這個(gè) Service 的 IBinder 對(duì)象,所以我們就以 sCache 這個(gè) map 為切入點(diǎn),反射該對(duì)象,然后修改該對(duì)象,由于系統(tǒng)的 android.os.ServiceManager 類是 @hide 的,所以只能使用反射,根據(jù)這個(gè)初步思路,寫下第一步的代碼:

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

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

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

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

這里寫圖片描述
</br>
最后也證實(shí)了這個(gè)結(jié)果,為什么是 Proxy 對(duì)象這就不用我解釋了吧;
</li><li>最后是 HookHandler 的 invoke 方法,這個(gè)方法調(diào)用到了 ClipboardHookHandler 對(duì)象,我們來看看這個(gè)類的實(shí)現(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 對(duì)象(即為 IClipboard.Proxy 對(duì)象,最后通過 Binder Driver 調(diào)用到了系統(tǒng)的 ClipboardService 中),調(diào)用函數(shù)的 Method 對(duì)象和參數(shù)列表對(duì)象,獲取到了這些之后,不用我說了,就可以盡情的去做一些額外的操作了,我這里是在仿照知乎復(fù)制文字時(shí),在后面加上類似的版權(quán)聲明。
</li></ul>

問題討論

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

步驟

這里寫圖片描述

源碼

此為我們公司某位大神代碼,經(jīng)過整理修改而出,不知道有沒有版權(quán)問題,哈哈哈,謝謝周杰大神,雖然已經(jīng)不在公司,感謝感謝~~</br>
  源碼下載地址:https://github.com/zhaozepeng/ServiceHook</br>
  先來看看運(yùn)行的效果:</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 這個(gè)類就是使用反射的方式去獲取對(duì)應(yīng) Service (這里不能使用 Context.getSystemService 函數(shù),因?yàn)樗姆祷夭皇?IBinder 對(duì)象,比如對(duì)于 ClipboardService,它就是 ClipboardManager 對(duì)象)和設(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);
        }
    }
}

這個(gè)類上面介紹的很詳細(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é)尾處添加我們自定義的文本,用戶再粘貼到其他地方的時(shí)候,就會(huì)出現(xiàn)我們添加上我們自定義文本后的文字了,還有需要注意的是這只會(huì)影響應(yīng)用進(jìn)程的 ClipboardService,并不會(huì)影響主進(jìn)程的相關(guān) Service,因?yàn)椴还茉趺?hook,修改的都是應(yīng)用進(jìn)程的 ServiceManager 里面的 sCache 變量,應(yīng)用進(jìn)程的 ServiceManager 其實(shí)也就相當(dāng)于一個(gè) Binder Client,sCache 里面獲取不到對(duì)應(yīng)的 Service,它就會(huì)自動(dòng)通過 Binder Driver 和 Binder Server (主進(jìn)程的 ServiceManager)通信去獲取對(duì)應(yīng)的 Service,所以修改 Binder Client,其實(shí)是不會(huì)影響 Binder Server的,不明白的建議還是看看:android IPC通信(下)-AIDL

測(cè)試代碼

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

測(cè)試代碼,我這里就不需要說明了,Clipboard 的簡(jiǎn)單使用而已。</br>  
  這里只演示了 hook ClipboardService 的例子,其他的使用方式比如插件化等等在這里就不一一介紹了,感興趣的可以去網(wǎng)上查閱相關(guān)的資料。
  轉(zhuǎn)載請(qǐng)注明出處: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)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,283評(píng)論 6 530
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 97,947評(píng)論 3 413
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,094評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,485評(píng)論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,268評(píng)論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,817評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,906評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,039評(píng)論 0 285
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,551評(píng)論 1 331
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,502評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,662評(píng)論 1 366
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,188評(píng)論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,907評(píng)論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,304評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,563評(píng)論 1 281
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,255評(píng)論 3 389
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,637評(píng)論 2 370

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