首先通过一个简单的例子来看下反射的使用。
先定义一个测试类。
package com.example.myapplication;
public class TestClass {
public void hi(int age,String name){
System.out.println("大家好 ,我叫" + name+",今年 " + age+"岁");
}
}
然后在activity中调用
Class testClass = null;
Method method = null;
try {
testClass = Class.forName("com.example.myapplication.TestClass");
method = testClass.getDeclaredMethod("hi",new Class[]{int.class,String.class});
method.invoke(testClass.newInstance(),20,"abc");
} catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException | ClassNotFoundException e) {
e.printStackTrace();
}
最后输出
I/System.out: 大家好 ,我叫abc,今年 20岁
也就是说,从Class中在获取了method(也就是方法名)之后,再调用method.invoke,这些就相当于是新建了一个TestClass对象然后调用这个对象的method。
虽然之前有用到过反射是由于webview加了系统签名后崩溃然后根据这篇文章找到了解决办法,但是对于反射其实是不太了解的。大多数时候虽然会用一个东西但是实际并没有理解其背后的原理。
【工作总结】系统签名app运行webview(Android5.0+)闪退问题
Android源码中的很多类,是无法获取也无法修改的,例如上面的例子中的WebViewFactory,但是有时候为了达成目的又不得不修改WebViewFactory,不然程序无法正常运行,这种时候java的反射就会派上用场。
那么这个的原理是什么?
首先来看看这一系列文章:
Java 编程的动态性
这系列文章,探讨了从源代码到执行程序所涉及的许多幕后细节。
建议通读一遍。
这里面有几个关键字:用二进制表示的类,装入类
用二进制表示的类
编译器将java语言编写的.java文件编译,生成一个二进制类(二进制类格式实际上是由 JVM 规范定义的),然后存储在扩展名为 .class 的文件中。.class 会被装入运行中的 JVM。
出处:Java 编程的动态性
装入类:
诸如 C 和 C++ 这些编译成本机代码的语言通常在编译完源代码之后需要链接这个步骤。这一链接过程将来自独立编译好的各个源文件的代码和共享库代码合并起来,从而形成了一个可执行程序。使用 Java 语言,由编译器生成的类在被装入到 JVM 之前通常保持原状。
在装入并初始化类时,JVM 内部会完成许多操作,包括解码二进制类格式、检查与其它类的兼容性、验证字节码操作的顺序以及最终构造 java.lang.Class 实例来表示新类。这个 Class 对象成了 JVM 创建新类的所有实例的基础。它还是已装入类本身的标识 ― 对于装入到 JVM 的同一个二进制类,可以有多个副本,每个副本都有其自己的 Class 实例。即使这些副本都共享同一个类名,但对 JVM 而言它们都是独立的类。
这里装入类看起来比较别扭,一开始有点抓不准是名词还是动作。是"装入类" 还是"装入 类",后面想想应该是指把.class装入JVM。
看完类和类装入之后再去看反射会对理解反射有一定的帮助。
引入反射
出处:引入反射
使用反射不同于常规的Java编程,其中它与 元数据--描述其它数据的数据协作。Java语言反射接入的特殊类型的原数据是JVM中类和对象的描述。反射使您能够运行时接入广泛的类信息。它甚至使您能够读写字段,调用运行时选择的类的方法。
首先,获取class。
Class clas = MyClass.class;
这种是在MyClass.java是自己定义的情况下的写法。当需要在运行时从某些外部源读取类名,即类似上面从Android 源码中的类名。此时就需要使用一个类装入器来查找类信息。以下介绍一种方法:
// "name" is the class name to load
Class clas = null;
try {
clas = Class.forName(name);
} catch (ClassNotFoundException ex) {
// handle exception case
}
// use the loaded class
如果已经装入了类,您将得到现有的 Class 信息。如果类未被装入,类装入器将现在装入并返回新创建的类实例。
关于Class.forName()可以看这一篇:
Class.forName()的作用与使用总结
获取了二进制格式的.class类之后,下面就是 基于类的反射。
对于类中的构造函数、字段和方法,java.lang.Class 提供四种独立的反射调用,以不同的方式来获得信息。
1.构造函数的反射
首先来看构造函数的反射调用。Constructor 的意思是构造者。
Constructor getConstructor(Class[] params)
Constructor[] getConstructors()
Constructor getDeclaredConstructor(Class[] params)
Constructor[] getDeclaredConstructors()
这里返回的是Constructor 或Constructor[]。 那么如何使用?文中是通过一个实例去说明getConstructor的使用。
public class TwoString {
private String m_s1, m_s2;
public TwoString(String s1, String s2) {
m_s1 = s1;
m_s2 = s2;
System.out.println("m_s1 = " + m_s1 + " , m_s2 = " +m_s2);
}
}
然后,就是构造函数的反射调用。Constructor.newInstance就是相当于调用TwoString的构造函数。
Class[] types = new Class[] { String.class, String.class };
Constructor cons = TwoString.class.getConstructor(types);
Object[] args = new Object[] { "a", "b" };
TwoString ts = cons.newInstance(args);
在Android studio中真正运行的话要稍微修改一点,因为上面的代码没有加try catch异常所以会提示报错,按提示自动补全就好。
Class[] types = new Class[] { String.class, String.class };
Constructor cons = null;
try {
cons = TwoString.class.getConstructor(types);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
Object[] args = new Object[] { "a", "b" };
try {
TwoString ts = (TwoString) cons.newInstance(args);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
最后运行输出I/System.out: m_s1 = a , m_s2 = b
。
也就是说,上面的那些代码,完成了构造函数的调用,也就是一个类的新建。
2.字段的反射
下面来看字段的反射。
Class中也提供了4个方法。
Field getField(String name)
Field[] getFields()
Field getDeclaredField(String name)
Field[] getDeclaredFields()
下面的实例是给新增加一个 int 类型的字段赋值。
首先,在TwoString 新增一个count。
public class TwoString {
private String m_s1, m_s2;
public int count;
public TwoString(String s1, String s2) {
m_s1 = s1;
m_s2 = s2;
}
}
然后新增函数。
public int incrementField(String name, Object obj) throws NoSuchFieldException, IllegalAccessException {
Field field = obj.getClass().getDeclaredField(name);
int value = field.getInt(obj) + 1;
field.setInt(obj, value);
return value;
}
最后调用
TwoString mTwoString = new TwoString("a","b");
try {
incrementField("count",mTwoString);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
Log.d(TAG,"count = " + mTwoString.count);
输出log如下。
D/MainActivity: count = 1
3.方法的反射
依旧是提供了4个方法。
Method getMethod(String name, Class[] params)
Method[] getMethods()
Method getDeclaredMethod(String name, Class[] params)
Method[] getDeclaredMethods()
下面来看实例。
public int incrementProperty(String name, Object obj) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
String prop = Character.toUpperCase(name.charAt(0)) +
name.substring(1);
String mname = "get" + prop;
Class[] types = new Class[] {};
Method method = obj.getClass().getMethod(mname, types);
Object result = method.invoke(obj, new Object[0]);
int value = ((Integer)result).intValue() + 1;
mname = "set" + prop;
types = new Class[] { int.class };
method = obj.getClass().getMethod(mname, types);
method.invoke(obj, new Object[] { new Integer(value) });
return value;
}
定义一个JavaBean 。
public class JavaBean {
private int count;
public int getCount(){
return count;
}
public void setCount(int count){
this.count = count;
}
}
最后调用
JavaBean javaBean = new JavaBean();
try {
incrementProperty("count",javaBean);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
Log.d(TAG,"count = " + javaBean.getCount());
输出log如下。
D/MainActivity: count = 1
因为默认初始化赋值0,+1后为1所以javaBean中count最后值为1。上面的代码演示了调用getCount和setCount的调用。
以上就是构造函数、字段和方法的反射。
关于反射,就看到这里为止。还有更多的用法这里暂不深入。
参考链接:
类和类装入
引入反射
结合反射与 XML 实现 Java 编程的动态性
Class.forName()的作用与使用总结
【工作总结】系统签名app运行webview(Android5.0+)闪退问题
理解 Android Hook 技术以及简单实战