概要
我们在之前的一篇Android-Framework-Plugin插件话框架-Hook Activity过程 中对
Hook技术
进行过基础讲解,Hook技术
就是以AOP 切面编程思想
去实现的底层代理。
Hook
中文名"钩子",它使用的是一种面向切面的编程思想(业内专称AOP编程思想
),主要作用是在事件传递过程中对事件进行拦截、修改、监听,将自身的代码动态性替换进去,当这些方法被调用时,保证执行的是我们代码,已达到我们预期的效果
Hook技术实现
- 反射技术
- 动态代理
反射技术
Java反射机制
是在运行状态中可获取/修改 类/对象的所有的方法和属性,没有公开私有之分;这种可动态获取/修改类或对象的方法和属性 被称为JAVA反射机制
反射可实现的功能
- 可获取并修改类任一的属性值
- 获取对象的所属类
- 构造任一类的实例
- 调用类中任一方法
- 修改类中任一方法的参数
反射的使用
获取Class
如图所示:
实现方法 | 使用限制 |
---|---|
Object.getClass() | 不适用于基础类型,如 int、float 等等 |
ClassName.class | 不适用于无法被Import的类,如private Class 或 Android把一些类加上了 @hide 注解,此类无法被正常调用 |
Class.forName(包名+类名) | 无限制 |
实例Demo
共有实体类:boy.java
public class Boy {
int age;
String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private String girlHobby(String mineHobby) {
String brief = "My name is" + name + ", I'm " + age + "year old.\n";
String hobby = "我的爱好是" + mineHobby;
return brief + hobby;
}
}
私有实体类:girl.java
package com.thtf.leanpackage.plugin_hook.demo;
class Girl {
int age;
String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private String girlHobby(String mineHobby) {
String brief = "My name is" + name + ", I'm " + age + "year old.\n";
String hobby = "我的爱好是" + mineHobby;
return brief + hobby;
}
}
主运行程序 HookMian.java
package com.thtf.leanpackage.plugin_hook;
import com.thtf.leanpackage.plugin_hook.demo.Boy;
public class HookMian {
public static void main(String[] args) {
//Object.getClass()
Boy boy = new Boy();
Class boyClass1 = boy.getClass();
System.out.println(boyClass1.getCanonicalName());
//Class Name.class
Class boyClass2 = Boy.class;
System.out.println(boyClass2.getCanonicalName());
//Class.forName(包名+类名)
try {
Class boyClass3 = Class.forName("com.thtf.leanpackage.plugin_hook.demo.Boy");
Class girlClass = Class.forName("com.thtf.leanpackage.plugin_hook.demo.Girl");
System.out.println(girlClass.getCanonicalName());
System.out.println(boyClass3.getCanonicalName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
通过上面所述的三种方法获取到了
Class
通过Class获取类中包含的属性方法
下面我们来列举通过Class的哪些方法获取反射目标类
中方法和属性
Class中方法名 | 使用描述 |
---|---|
getDeclaredFields() | 获取所有的属性,但不包括从父类继承下来的属性 |
getDeclaredField(String name) | 根据获取name 参数指定的属性 |
getDeclaredMethods() | 获取所有的方法,包括 private、public、protected
|
getDeclaredMethod(String name, Class<?>... parameterTypes) | 根据参数name 获取特定的方法名,参数parameterTypes 代表name 方法名中的参数类型,以.class 表示,例如String.class,Float.class
|
getDeclaredConstructors() | 返回一个对象数组,反映声明类所有的构造方法 |
getDeclaredConstructor(Class<?>... parameterTypes) | 获取特定的构造方法,参数parameterTypes 表示参数类型 |
getSuperclass() | 获取反射目标类的父类 |
getInterfaces() | 获取反射目标类中所有实现的接口 |
getMethods() | 获取自身的所有的 public 方法,包括从父类继承下来的 |
getMethod(String name, Class<?>... parameterTypes) | 根据参数name 获取特定的public修饰符下的属性,parameterTypes 表示方法传递的参数类型 |
getFields() | 获取自身的所有的 public 属性,包括从父类继承下来的。 |
getField(String name) | 根据参数name 获取特定的public修饰符下的属性 |
Java Demo 案例
package com.thtf.leanpackage.plugin_hook;
import com.thtf.leanpackage.plugin_hook.demo.Boy;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class HookMian {
public static void main(String[] args) {
//Object.getClass()
Boy boy = new Boy();
Class boyClass1 = boy.getClass();
System.out.println(boyClass1.getCanonicalName());
//Class Name.class
Class boyClass2 = Boy.class;
System.out.println(boyClass2.getCanonicalName());
//Class.forName(包名+类名)
try {
Class boyClass3 = Class.forName("com.thtf.leanpackage.plugin_hook.demo.Boy");
Class girlClass = Class.forName("com.thtf.leanpackage.plugin_hook.demo.Girl");
System.out.println(girlClass.getCanonicalName());
System.out.println(boyClass3.getCanonicalName());
//构造器
Constructor[] boyConstructors = boyClass3.getConstructors();
for (Constructor c : boyConstructors) {
System.out.println("getConstructor:" + c.toString());
}
boyConstructors = boyClass3.getDeclaredConstructors();
for (Constructor c : boyConstructors) {
System.out.println("getDeclaredConstructors:" + c.toString());
}
//获取指定方法属性
Method boyHobby = boyClass3.getDeclaredMethod("boyHobby", String.class);
Field ageField = boyClass3.getDeclaredField("age");
System.out.println("获取的方法名:" + boyHobby.getName());
System.out.println("获取的属性:" + ageField.getName());
//获取反射类中的公共方法
Method girlLittleName = girlClass.getMethod("girlLittleName", String.class);
System.out.println("获取的公共方法名:" + girlLittleName.getName());
//获取所有方法
Method[] methods = girlClass.getDeclaredMethods();
for (Method method : methods) {
System.out.println("Declared Method :" + method.getName());
}
//获取公共方法
methods=girlClass.getMethods();
for (Method method : methods) {
System.out.println("Public Method :" + method.getName());
}
//获取所有的属性
Field[] filed1 = boyClass3.getDeclaredFields();
for (Field f : filed1) {
System.out.println("Declared Field :" + f.getName());
}
//获取公共的字段属性
Field[] filed2 = boyClass3.getFields();
for (Field f : filed2) {
System.out.println("Field :" + f.getName());
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
我们通过反射获取的
属性对象
类型都是Field类型
,而获取的方法对象
的类型都是Method类型
,接下来我们来讲解一下Field 和 Method
都可以触发那些操作.
Field 和 Method 操作解析
Field
操作通过反射获取的属性,主要作用读取属性值
以及为属性赋值
Method
操作通过反射获取的方法,反射的核心就在于此;通过调取Method
的invoke()
实现对原始方法的代理,从而达到反射的目的.
public class HookMian {
public static void main(String[] args) {
try {
//反射获取Class对象
Class boyClass3 = Class.forName("com.thtf.leanpackage.plugin_hook.demo.Boy");
//实现反射类中对属性的操作
realizeField(boyClass3);
//实现反射类中对方法的操作
realizeMthod(boyClass3);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
private static void realizeField(Class boyClass3) {
try {
//实例化反射类对象
Object boy = boyClass3.newInstance();
//获取其中一个属性
Field nameField = boyClass3.getDeclaredField("age");
//将其状态设置成可访问
nameField.setAccessible(true);
//为属性赋值
nameField.setInt(boy, 15);
System.out.println(nameField.getInt(boy));
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
private static void realizeMthod(Class boyClass) {
try {
//实例化
Object girlInstance = boyClass.newInstance();
//获取其中的指定的方法
Method girlHobbyMethod = boyClass.getDeclaredMethod("boyHobby", String.class);
//设置成可访问状态
girlHobbyMethod.setAccessible(true);
//为得到方法传递参数,并获取执行结果
Object result = girlHobbyMethod.invoke(girlInstance, "11");
System.out.println("我执行的结果: " + result);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
Andorid反射实例
我们以View
的OnClick事件
为Hook
的实例做讲解,我们先来对View.setOnClickListener()
做分析讲解:
我们在
Activity
中为控件设置onClick事件
,进入View类
中查看setOnClickListener(@Nullable OnClickListener l)
源码分析,我们发现参数OnClickListener
最终存储在了静态内部类ListenerInfo类
中的mOnClickListener
属性
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}
接下来我们对onClick事件
进行反射操作:
- 首先我们首先在
主程序
中设置控件的事件监听
,然后设置我们的Hook的监听事件
,当我们触发了控件的onClick事件
由于我们将其进行了Hook代理
,所以会触发我们的Hook代理操作
public class HookViewClickActivity extends Activity implements View.OnClickListener {
private Button hook_btn;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.hook_click_layout);
hook_btn = (Button) findViewById(R.id.hook_btn);
hook_btn.setOnClickListener(this);
try {
HookHelper.hookOnClickListener(hook_btn);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.hook_btn:
Toast.makeText(HookViewClickActivity.this, "OnClick 被正常调用了", Toast.LENGTH_SHORT).show();
break;
}
}
}
- 我们来看具体的
Hook操作步骤
,主要的就是将我们的Proxy
替换掉mOnClickListener属性
package com.thtf.leanpackage.view.hook_click;
import android.view.View;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class HookHelper {
public static void hookOnClickListener(View view) throws Exception {
// [1] 通过反射获取 ListenerInfo 对象
Method listenerInfoMethod = View.class.getDeclaredMethod("getListenerInfo");
listenerInfoMethod.setAccessible(true);
//获取getListenerInfo函数执行的返回结果
Object listenerInfoObj = listenerInfoMethod.invoke(view);
// [2]反射获取到存储OnClickListener事件的属性 mOnClickListener
Class<?> listenerInfoClazz = Class.forName("android.view.View$ListenerInfo");
//获取ListenerInfo的属性mOnClickListener
Field mOnClickListenerField = listenerInfoClazz.getDeclaredField("mOnClickListener");
mOnClickListenerField.setAccessible(true);
//获取mOnClickListener的属性值
View.OnClickListener originOnClickListener = (View.OnClickListener) mOnClickListenerField.get(listenerInfoObj);
// [3]用 Hook代理类 替换mOnClickListener属性
View.OnClickListener hookedOnClickListener = new HookedClickListenerProxy(originOnClickListener);
//为mOnClickListener属性 赋值
mOnClickListenerField.set(listenerInfoObj, hookedOnClickListener);
}
}
- 那么我们在自定义的
Proxy类
,如何做处理的:
package com.thtf.leanpackage.view.hook_click;
import android.view.View;
import android.widget.Toast;
public class HookedClickListenerProxy implements View.OnClickListener {
private View.OnClickListener listener;
public HookedClickListenerProxy(View.OnClickListener listener){
this.listener=listener;
}
@Override
public void onClick(View v) {
//自定义操作
Toast.makeText(v.getContext(),"Hook Click Listener",Toast.LENGTH_SHORT).show();
if (listener!=null){
listener.onClick(v);
}
}
}
总结
- 1.获取
Class对象
是反射机制的入口- 2.通过
Class对象
获取Field
,对反射类中属性进行读取或赋值等操作- 3.通过
Class对象
获取Method
,并实现Method.invoke()
从而实现对反射类中所包含的方法进行代理操作
网友评论