在《Android 组件化架构》和《Android 插件化开发指南》这两本书中,两位作者都提到了 jOOR 反射框架。刚看到 jOOR 的时候,觉得使用 jOOR 和常规的反射操作比起来优雅很多,但是当我在不同的使用场景下对它进行进行一些测试时,却发现了一些问题,于是我就在想难道没有更好的解决方案了吗?想到后,我就写了 OkReflect。
Java 反射机制能让程序在运行时,对于任意一个类,都能够知道这个类的所有属性和方法。对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态获取的信息以及动态调用对象的方法的功能就叫做 Java 反射机制。
String str = "666"; 复制代码
try { Class clazz = String.class; Constructor constructor = clazz.getConstructor(String.class); String instance = (String) constructor.newInstance("666"); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } 复制代码
这么一看,反射也太麻烦了,有啥用?但如果你想创建的实例的构造函数是私有的,你调不到,这时候反射就派上用场了。
假设我们现在有一个 Client 类,它的构造函数和方法都是私有的,这时候我们通过反射就能创建这个类,并且调用它的私有方法。
public class Client { private String name; private Client(String name) { this.name = name; } private void setName(String name) { this.name = name; } } 复制代码
try { Class clazz = Class.forName("Client"); Constructor constructor = clazz.getDeclaredConstructor(String.class); constructor.setAccessible(true); Client client = (Client) constructor.newInstance("小张"); Method method = clazz.getDeclaredMethod("setName", String.class); method.setAccessible(true); method.invoke(client, "老王"); System.out.println(""); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } 复制代码
创建类和调用方法的效果是达到了,但是这代码看上去也太不美观了,别怕,有 jOOR。
jOOR 是一个老牌的反射框架,它对 Java 反射的操作进行了封装。
String world = on("java.lang.String") // 相当于 Class.forName() .create("Hello World") // 调用构造函数创建实例 .call("substring", 6) // 调用 substring 方法 .get(); // 获取方法返回结果 复制代码
挺好,异常都不见了,但你还真不能直接这样用,出问题了还是会报异常。jOOR 自定义了 ReflectException,反射中发生的异常会被封装成 ReflectException,所以还是要包一层 try…catch。
try { String world = on("java.lang.String") .create("Hello World") .call("substring", 6) .get(); } catch (ReflectException e) { // 处理异常 } 复制代码
虽然还是要包一层 try…catch,但是比最原始的反射操作是要好多了,但还有没有更好的办法呢?下面看 OkReflect 的处理。
try { String world = OkReflect.on("java.lang.String") .create("Hello World") .call("substring", 6) .get(); } catch (Exception e) { // 处理异常 } 复制代码
这好像跟 jOOR 没啥不同啊,用来干嘛?别急,OkReflect 还允许你在回调中处理异常。
String world = OkReflect.on("java.lang.String") .create("Hello World") .call("substring", 6) .error(new OkReflect.OkReflectErrorCallback() { @Override public void onError(@NotNull Exception e) { // 处理异常 } }) .get(); 复制代码
如果你用的是 Kotin 语言,那你还可以这样写。
val world: String? = OkReflect.on("java.lang.String") .create("Hello World") .call("substring", 6) .error{ // 处理异常 } .get() 复制代码
在 jOOR 中,如果你调用的方法是有返回值的,那你下一个调用的方法就会用上一个方法返回的值来调用,当我们并不关心返回值的时候,这种默认操作会带来一些不便之处。假设我们现在有一个 Manager 类。
public class Manager { public List<String> items = new ArrayList<>(); public int count; public int addData(String data) { this.items.add(data); count ++; return count; } public String getData(int i) { return items.get(i); } } 复制代码
假如我们用 jOOR 在添加数据后获取数据,在调用方法后 jOOR 会抛出 NoSuchMethodException 异常,这是因为 jOOR 会使用 addData 返回的 count 来调用 getData 方法,而 count 是 Integer,Integer 中的确没有 getData 方法。
// jOOR String data = on("Manager") .create() .call("addData", "data") .call("getData", 0) .get(); 复制代码
如果使用 OkReflect 进行这个操作,则不会出现这个问题,因为 OkReflect 默认是使用实例来调用方法的。
// OkReflect String data = OkReflect.on("Manager") .create() .call("addData", "data") .call("getData", 0) .get(); 复制代码
那如果你想在添加数据后忽略返回值,而是要拿到该实例时要怎么做?在 jOOR 中你只能获取返回值的结果,但是在 OkRefelct 中,你可以使用 getInstance 方法来获取实例而不是返回结果,比如下面这这样的
// OkReflect Manager manager = OkReflect.on("Manager") .create() .call("addData", "data") .getInstance(); 复制代码
在 jOOR 中,获取的返回值的类型是没有保证的,也就是在 try…catch 中使用 ReflectException 的话包含的范围还太小,要使用 Exception 才能捕获到类型转换异常。
但是在 OkReflect 中,在返回时对类型转换异常进行了捕获,如果类型不对,则会返回空,这时候你可以使用空值来进行判断是否转换成功,如果你想要的话,你也可以在 ErrorCallback 中处理该异常,比如下面这样的。
String manager = OkReflect.on("Manager") .create() .call("addData", "data") .error(new OkReflect.OkReflectErrorCallback() { @Override public void onError(@NotNull Exception e) { // 处理异常 } }) .getInstance(); 复制代码
在 Android 中的 Field 类是没有 modifier 字段的,这导致常规的修改 final 字段的方法在 Android 中不可行。jOOR 和 OkReflect 都通过反射修改了 Field 中的 modifier 字段,但是 OkReflect 对系统进行了判断,因此 OkReflect 在 Android 中也可以修改 final 字段。
jOOR 的 on 方法中只能传入 class 信息,而 OkReflect 中可以传入实例。这有什么用呢?比如 Android 中的 Instrumentation,自己创建一个实例进行 Hook 是非常麻烦的,所以通常都是用 Activity 中已有的 Instrumentation 进行 Hook。下面是使用实例来调用方法的例子。
Client client = new Client(); OkReflect.on(client) .call("setName", "Alex") .get("name"); 复制代码
allprojects { repositories { ... maven { url 'https://jitpack.io' } } } 复制代码
dependencies { implementation 'com.github.zeshaoaaa:OkReflect:master-SNAPSHOT' } 复制代码
<repositories> <repository> <id>jitpack.io</id> <url>https://jitpack.io</url> </repository> </repositories> 复制代码
<dependency> <groupId>com.github.zeshaoaaa</groupId> <artifactId>OkReflect</artifactId> <version>master-SNAPSHOT</version> </dependency> 复制代码