1.反射概述
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象。
反射就是把java类中的各种成分映射成一个个的Java对象
例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。
(其实:一个类中这些成员方法、构造方法、在加入类中时,都有一个类来描述,称为class类)
如图是类的正常加载过程:反射的原理在于class对象。
熟悉一下加载的时候:Class对象的由来是将class文件读入内存,并为之创建一个Class对象。
其中这个Class对象很特殊。我们先了解一下这个Class类
2.反射的使用
2.1 获取class对象的三种方式
1)Object.getClass()
2) 任何数据类型(包括基本数据类型)都有一个静态的class属性
3) Class类的ForName(String name) 方法,这个常用,因为不用导包,只需要传入包名+类名
package fanshe;
/**
* 获取Class对象的三种方式
* 1 Object ——> getClass();
* 2 任何数据类型(包括基本数据类型)都有一个“静态”的class属性
* 3 通过Class类的静态方法:forName(String className)(常用)
*
*/
public class Fanshe {
public static void main(String[] args) {
//第一种方式获取Class对象
Student stu1 = new Student();//这一new 产生一个Student对象,一个Class对象。
Class stuClass = stu1.getClass();//获取Class对象
System.out.println(stuClass.getName());
//第二种方式获取Class对象
Class stuClass2 = Student.class;
System.out.println(stuClass == stuClass2);//判断第一种方式获取的Class对象和第二种方式获取的是否是同一个
//第三种方式获取Class对象
try {
Class stuClass3 = Class.forName("fanshe.Student");//注意此字符串必须是真实路径,就是带包名的类路径,包名.类名
System.out.println(stuClass3 == stuClass2);//判断三种方式是否获取的是同一个Class对象
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
在运行期间,只有一个class对象产生。
2.2 通过反射获取构造,成员方法,属性
public class Student {
private int age;//年龄
private String name;//姓名
private String address;//地址
private static String sTest;
public Student() {
throw new IllegalAccessError("Access to default Constructor Error!");
}
private Student(int age, String name, String address) {
this.age = age;
this.name = name;
this.address = address;
sTest = "测试反射";
}
private int getAge() {
return age;
}
private void setAge(int age) {
this.age = age;
}
private String getName() {
return name;
}
private void setName(String name) {
this.name = name;
}
private String getAddress() {
return address;
}
private void setAddress(String address) {
this.address = address;
}
private static String getTest() {
return sTest;
}
}
刻意用了private来修饰成员变量和方法 下面代码用构造器,方法和属性和静态方法分别来获取一下:
public class StudentClient {
public static void main(String[] args) throws Exception{
Class> clazz=Class.forName("ClassLoader.Student");
Constructor constructors=clazz.getDeclaredConstructor(int.class,String.class,String.class);
constructors.setAccessible(true);
//利用构造器生成对象
Object mStudent=constructors.newInstance(27,"小文","北京市海定区XX号");
System.out.println(mStudent.toString());
//获取隐藏的int属性
Field mAgeField=clazz.getDeclaredField("age");
mAgeField.setAccessible(true);
int age= (int) mAgeField.get(mStudent);
System.out.println("年龄为:"+age);
//调用隐藏的方法
Method getAddressMethod=clazz.getDeclaredMethod("getAge");
getAddressMethod.setAccessible(true);
int newage= (int) getAddressMethod.invoke(mStudent);
System.out.println("年龄为:"+newage);
//调用静态方法
Method getTestMethod=clazz.getDeclaredMethod("getTest");
getTestMethod.setAccessible(true);
String result= (String) getTestMethod.invoke(null);
System.out.println("调用静态方法:"+result);
}
}
getDeclared***获取的是仅限于本类的所有的不受访问限制的;
而get***获取的是包括父类的但仅限于public修饰符的。
Field和method时一样的道理。
最后一个需要注意的是调用静态方法和调用实例方法有点区别,调用实例方法一定需要一个类的实例,而调用静态方法不需要实例的引用,其实这是JVM的在执行方法上的有所区别。
3.反射的原理
我们知道,每个java文件最终都会被编译成一个.class文件,这些Class对象承载了这个类的所有信息,包括父类、接口、构造函数、方法、属性等,这些class文件在程序运行时会被ClassLoader加载到虚拟机中。
当一个类被加载以后,Java虚拟机就会在内存中自动产生一个Class对象,而我们一般情况下用new来创建对象,实际上本质都是一样的,有了class对象的引用,就相当于有了Method,Field,Constructor的一切信息,在Java中,有了对象的引用就有了一切,剩下怎么发挥是开发者自己的想象力所能决定的了。
4.反射的作用及使用场景
反射是一种具有与Java类进行动态交互能力的一种机制,在Java和Android开发中,一般情况下下面几种场景会用到反射机制.
● 需要访问隐藏属性或者调用方法改变程序原来的逻辑,这个在开发中很常见的,由于一些原因,系统并没有开放一些接口出来,这个时候利用反射是一个有效的解决方法
● 自定义注解,注解就是在运行时利用反射机制来获取的。
●在开发中动态加载类,比如在Android中的动态加载解决65k问题等等,模块化和插件化都离不开反射。
5. 利用反射来进行hook的例子:
hook view的点击事件,避免重复点击,或者发送埋点等。
import android.app.Activity;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import chihane.jdaddressselector.demo.R;
public class HookListenerActivity extends Activity {
@Override protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_hook_listener);
Button button = (Button) this.findViewById(R.id.id_btn);
button.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
Toast.makeText(getApplicationContext(), "点击事件", Toast.LENGTH_SHORT).show();
}
});
getWindow().getDecorView().post(new Runnable() {
@Override public void run() {
View decorView = getWindow().getDecorView();
HookClickListenerUtils.getInstance().hookDecorViewClick(decorView);
}
});
}
@Override public void onAttachedToWindow() {
super.onAttachedToWindow();
Log.e("onAttachedToWindow", "onAttachedToWindow");
// 第二种方式
// View decorView = getWindow().getDecorView();
// HookClickListenerUtils.getInstance().hookDecorViewClick(decorView);
}
@Override public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
Log.e("onWindowFocusChanged", "onWindowFocusChanged中" + hasFocus);
}
}
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class HookClickListenerUtils {
private static HookClickListenerUtils mHookClickListenerUtils;
private HookClickListenerUtils() {
}
public static HookClickListenerUtils getInstance() {
synchronized ("getInstance") {
if (mHookClickListenerUtils == null) {
mHookClickListenerUtils = new HookClickListenerUtils();
}
}
return mHookClickListenerUtils;
}
/***
* 递归调用
* @param decorView
*/
public void hookDecorViewClick(View decorView) {
if (decorView instanceof ViewGroup) {
int count = ((ViewGroup) decorView).getChildCount();
for (int i = 0; i < count; i++) {
if (((ViewGroup) decorView).getChildAt(i) instanceof ViewGroup) {
hookDecorViewClick(((ViewGroup) decorView).getChildAt(i));
} else {
hookViewClick(((ViewGroup) decorView).getChildAt(i));
}
}
} else {
hookViewClick(decorView);
}
}
public void hookViewClick(View view) {
try {
Class viewClass = Class.forName("android.view.View");
Method getListenerInfoMethod = viewClass.getDeclaredMethod("getListenerInfo");
if (!getListenerInfoMethod.isAccessible()) {
getListenerInfoMethod.setAccessible(true);
}
Object listenerInfoObject = getListenerInfoMethod.invoke(view);// 反射view中的getListenerInfo方法
// 第二步:获取到view中的ListenerInfo中的mOnClickListener属性
Class mListenerInfoClass = Class.forName("android.view.View$ListenerInfo");
Field mOnClickListenerField = mListenerInfoClass.getDeclaredField("mOnClickListener");
mOnClickListenerField.setAccessible(true);
// 这里为何传入的View.OnClickListener为Field.get()的值
mOnClickListenerField.set(listenerInfoObject, new HookClickListener((View.OnClickListener) mOnClickListenerField.get(listenerInfoObject)));
} catch (Exception e) {
e.printStackTrace();
}
}
public static class HookClickListener implements View.OnClickListener {
private View.OnClickListener onClickListener;
public HookClickListener(View.OnClickListener onClickListener) {
this.onClickListener = onClickListener;
}
@Override public void onClick(View v) {
Toast.makeText(v.getContext(), "hook住点击事件了,禽兽", Toast.LENGTH_SHORT).show();
if (onClickListener != null) {
onClickListener.onClick(v);
}
}
}
}