代理與反射總結

AndroidHint發表於2019-09-16

一、代理

代理分為靜態代理和動態代理。靜態代理在執行前已經存在,代理類和委託類的關係在執行前就已經確定了。而動態代理則在執行時確定的,根據反射機制生成的,代理類和委託類的關係在執行時才能確定。

1、靜態代理

在講Binder機制時,有一個非常典型的靜態代理。這裡拿過來說一下:

public static com.example.runningh.myapplication.phone.IPhoneManager asInterface(android.os.IBinder obj) {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.example.runningh.myapplication.phone.IPhoneManager))) {
        return ((com.example.runningh.myapplication.phone.IPhoneManager)iin);
      }
      return new com.example.runningh.myapplication.phone.IPhoneManager.Stub.Proxy(obj);
}
複製程式碼

如果返回的IInterface服務與當前程式不在同一個程式則new了一個Stub.Proxy代理類,當客戶端去訪問相關方法時,代理類會去呼叫服務端的方法並返回給客戶端,這就是一個典型的靜態代理模式。

2、動態代理

在實現時並不需要指定代理哪個物件,在需在執行時指定實現即可,它的優點是更加靈活和方便。我們來看一下它是怎麼使用的。

Object proxy = Proxy.newProxyInstance(ClassLoader loader, Class[] claz , InvocationHanlder handler);
複製程式碼

動態代理通過上面的方法原型返回一個代理物件。例如有如下呼叫:

Class<?> serviceManager = Class.forName("android.os.ServiceManager");
Method getServiceMethod = serviceManager.getDeclaredMethod("getService", String.class);
IBinder binder = (IBinder) getServiceMethod.invoke(null, "alarm");
IBinder myBinder = (IBinder) Proxy.newProxyInstance(serviceManager.getClassLoader()
      , new Class[]{IBinder.class}, new MyProxy(binder));
複製程式碼

我們詳細分析一下它的三個方法引數。

  • ClassLoader loader。這是一個類載入器,表示我們使用哪一個類載入器來載入這個代理類。一般我們使用委託類的類載入器。
  • Class[] claz。指定newProxyInstance返回的物件(也就是我們的代理類)要實現哪些介面。這是一個陣列,表明是可以實現多個介面的。上面的例子實現了IBinder介面,所以它是可以強制型別轉換成IBinder型別的。
  • InvocationHandler handler。這是一個介面。其中有一個invoke方法。其實無論你呼叫了代理物件的什麼方法,最終都會走到invoke方法中。所有在invoke方法中可以針對方法名來實現一些我們想要做的操作。

二、反射

反射機制這樣的一種功能,對於類來說,它是在執行過程中可以知道任意一個類的所有屬性和方法;對於物件來說,可以呼叫它的任意一個屬性和方法。 我們來看一下反射的一些相關操作:

1、Class物件的獲取:
        Class.forName(類名全路徑); //這是Class的一個靜態方法
        類物件.getClass();  //通過類物件.getClass方法
        boolean.class, Boolean.class, int.class, Integer.class; //基本資料的Class物件獲取,注意boolean.class和Boolean.class並不一樣

2、實現介面的獲取:
        Class<?> clazz  = Class.forName(類名全路徑);
        Class<?>[] interfaces = clazz.getInterfaces()

3、通過指定引數獲取建構函式及例項化
        Class<?> clazz  = Class.forName(類名全路徑);
        Constructor<?> constructor = clazz.getConstructor(Class<?>  ... class);//獲取公有的建構函式(public修飾符的)
        Constructor<?> constructor = clazz.getDeclaredConstructor(); //獲取建構函式,包括公有和私有的
        constructor.newInstance(Object args);

4、獲取所有建構函式和引數型別
        Class<?> clazz  = Class.forName(類名全路徑);
        Constructor<?>[] constructors = clazz.getConstructors();//公共的建構函式
        Constructor<?>[] constructors = clazz.getDeclaredConstructors()//所有的建構函式,包括私有的

         for (int i = 0; i < constructors.length; i++) {
            Class<?> clazzs[] = constructors[i].getParameterTypes();//獲取引數型別
             Log.d("ABC", "constructors[" + i + "] =" + constructors[i]);
            for (int j = 0; j < clazzs.length; j++) {
                if (j == clazzs.length - 1)
                    Log.d("ABC", clazzs[j].getName() + " ;");
            }
          }

5、獲取欄位,修改欄位
        Class<?> clazz  = Class.forName(類名全路徑);

        Field field = clazz.getField(String name);//獲取公有欄位
        Field field = clazz.getDeclaredField(String name);//獲取欄位,包括私有的
        Field[] field = clazz.getFields();//獲取所有公有欄位
        Field[] field = clazz.getDeclaredFields();//獲取所有欄位,包括私有的

        Field field = clazz.getDeclaredField("price");
        field.setAccessible(true);//設定取消訪問檢查,私有型別也可以訪問
        field.set(obj, 100);

6、獲取方法
        Class<?> clazz  = Class.forName(類名全路徑);

        clazz.getMethod(String name ,Class<?> ... parame);//獲取公共指定方法
        clazz.getDeclaredMethod(String name ,Class<?> ... parame)//獲取指定方法,包括私有的方法
        clazz.getMethods()//獲取所有的公共方法
        clazz.getDeclaredMethods();//獲取所有的方法,包括私有方法

        Method method = clazz.getMethod("setPrice", int.class);
        method.setAccessible(true);
        method.invoke(clazz.newInstance(), 200);
複製程式碼

三、實踐應用

在這裡我們通過hook系統的剪下板的複製貼上方法,讓它在複製貼上時都說一句話。 首先我們看一下系統服務是怎麼獲取的:

Context.getSystemService(Context.CLIPBOARD_SERVICE);
複製程式碼

而我們知道Context其實是ContextImpl物件,進入到ContextImpl的getSystemService方法:

@Override
public Object getSystemService(String name) {
    return SystemServiceRegistry.getSystemService(this, name);
}
複製程式碼

其中又呼叫了SystemServiceRegistry類的靜態方法:

public static Object getSystemService(ContextImpl ctx, String name) {
    ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    return fetcher != null ? fetcher.getService(ctx) : null;
}
複製程式碼

從SYSTEM_SERVICE_FETCHERS中獲取對應的ServiceFetcher,ServiceFetcher呼叫getService方法返回相應的服務。而SYSTEM_SERVICE_FETCHERS的初始化是在SystemServiceRegistry類的靜態程式碼塊初始化的,我們將複製貼上服務的初始化拿出來看:

registerService(Context.CLIPBOARD_SERVICE, ClipboardManager.class,
      new CachedServiceFetcher<ClipboardManager>() {
      @Override
      public ClipboardManager createService(ContextImpl ctx) {
          return new ClipboardManager(ctx.getOuterContext(),
                ctx.mMainThread.getHandler());
}});
複製程式碼

可以看到複製貼上服務其實就是一個ClipboardManager物件。而ClipboardManager物件裡面的所有方法都是通過getService()返回的代理物件執行的。getService返回了什麼,我們繼續看原始碼(注意①這裡,我們等一下會回來看這部分的程式碼):

static private IClipboard getService() {
      synchronized (sStaticLock) {
          if (sService != null) {
            return sService;
          }
          IBinder b = ServiceManager.getService("clipboard");
          //注意這裡,後面拿到代理的IBinder物件後會執行到asInterface的queryLocalInterface方法。
          sService = IClipboard.Stub.asInterface(b); 
          return sService;
      }
}

public static android.content.IClipboard asInterface(android.os.IBinder obj) {
    if ((obj == null)) {
        return null; 
    }
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); 
    if (((iin != null) && (iin instanceof android.content.IClipboard))) {
        return ((android.content.IClipboard) iin);
    }
    return new android.content.IClipboard.Stub.Proxy(obj);
}
複製程式碼

如果sService物件不為null,則返回。否則通過ServiceManager的getService方法獲取IBinder物件並呼叫IClipboard.Stub.asInterface(IBinder binder)生成。ServiceManager的getService方法做了什麼呢?繼續看原始碼:

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;
}
複製程式碼

如果sCache裡面有IBinder物件則返回,否則繼續走到else那一步。看到這裡我們就知道了可以通過替換sCache的IBinder為自己自定義的代理類,從而達到欺騙系統以外返回的還是原來的IBinder物件來達到我們上面想要的效果。

下面是具體的程式碼:

ReflectActivity:

public class ReflectActivity extends Activity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.reflect_activity);
        ClipHelper.binder();
    }
}
複製程式碼

佈局檔案就一個EditText,就不貼出來了。在onCreate方法中呼叫ClipHelper.binder方法。

ClipHelper:

public class ClipHelper {
    public static void binder() {
        try {
            Class<?> serviceManager = Class.forName("android.os.ServiceManager"); //獲取ServiceManager物件
            Method getServiceMethod = serviceManager.getDeclaredMethod("getService", String.class); //呼叫getService方法
            IBinder binder = (IBinder) getServiceMethod.invoke(null, "clipboard"); //呼叫剪下板的方法,獲取原始的IBinder物件
            IBinder myBinder = (IBinder) Proxy.newProxyInstance(serviceManager.getClassLoader()
                    , new Class[]{IBinder.class}, new MyProxy(binder)); //獲取代理的IBinder物件
            Field sCache = serviceManager.getDeclaredField("sCache");
            sCache.setAccessible(true);
            HashMap<String, IBinder> map = (HashMap<String, IBinder>) sCache.get(null);
            map.put("clipboard", myBinder); //將該代理物件放到sCache裡面
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}
複製程式碼

ClipHelper主要是生成了一個IBinder的代理物件,並將該代理物件放入到了sCache裡面。

MyProxy:

public class MyProxy implements InvocationHandler {
    private IBinder orginBinder;

    public MyProxy(IBinder binder) {
        this.orginBinder = binder;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("queryLocalInterface")) {
            Class<?> mStubClass = Class.forName("android.content.IClipboard$Stub");
            Class<?> mClipBoard = Class.forName("android.content.IClipboard");
            return Proxy.newProxyInstance(mStubClass.getClassLoader(), new Class[]{mClipBoard}, new MyClipBoard(orginBinder, mStubClass));
        }
        return method.invoke(orginBinder, args);
    }
}
複製程式碼

hook住IBinder的queryLocalInterface方法。可以回去看一下上面的注意①那部分,IBinder執行到了queryLocalInterface方法並返回了一個IClipboard物件。這個物件就是剪下板服務物件,這是我們需要hook的。所以這裡再次通過動態代理返回一個代理物件。該代理物件實現了IClipboard介面。這裡的代理物件為MyClipBoard。如下所示。

MyClipBoard:

public class MyClipBoard implements InvocationHandler {
    private Object mBase;

    public MyClipBoard(IBinder binder, Class stub) {
        try {
            Method asInterface = stub.getDeclaredMethod("asInterface", IBinder.class);
            mBase = asInterface.invoke(null, binder);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (method.getName().equals("getPrimaryClip")) {
            return ClipData.newPlainText(null, "怎麼老是你");
        }
        if (method.getName().equals("hasPrimaryClip")) {
            return true;
        }
        return method.invoke(mBase, args);
    }
}
複製程式碼

我們只需要hook住剪下板的getPrimaryClip和hasPrimaryClip方法,其他方法還是按照正常的方式呼叫。而正常的方式需要一個正常的剪下板服務,還是看回上面的注意①的程式碼,正常的剪下板服務是通過IClipboard.Stub.asInterface(binder)獲取的,所以上面的mBase就是剪下板服務,通過asInterface靜態方法獲取,故在建構函式中有呼叫IClipboard.Stub的asInterface方法。

上述就是全部的程式碼,在EditText中長按貼上就會出現“怎麼老是你”,而這句話就是我們通過hook住系統的剪下板服務得到的。

四、總結

動態代理和反射相結合一般可以用來改變系統的一些方法從而達到我們想要的效果。當我們不能拿到原始碼進行修改時,可以通過這樣的一種手段進行改造,但是千萬要注意,這是有風險的,改不好可能會隱藏潛在的bug,所以使用時必須要小心。 這篇文章是我看了http://blog.csdn.net/yulong0809/article/details/56842027這裡的原作者闡述代理、hook、反射知識點後,自己做的一些總結,感謝原作者的無私分享。

相關文章