代理与反射总结

一、代理

代理分为静态代理和动态代理。静态代理在运行前已经存在,代理类和委托类的关系在运行前就已经确定了。而动态代理则在运行时确定的,根据反射机制生成的,代理类和委托类的关系在运行时才能确定。

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、反射知识点后,自己做的一些总结,感谢原作者的无私分享。

猜你喜欢

转载自juejin.im/post/5d73188c518825312330b1e8