一点事情
为了方便大家学习这个系列,我新建了一个github仓库
,会更新Android体系架构
所有文章,还有面试专题,思维导图链接等等,地址:
https://github.com/JiMuzz/Android-Architecture
也可以直接点击文末 阅读原文
查看。
前言
今天说Java模块内容:反射。
反射介绍
正常情况下,我们知晓我们要操作的类和对象是什么,可以直接操作这些对象中的变量和方法,比如一个User类:
User user=new User(); user.setName("Bob");
但是有的场景,我们无法正常去操作:
- 只知道类路径,无法直接实例化的对象。
- 无法直接操作某个对象的变量和方法,比如私有方法,私有变量。
- 需要hook系统逻辑,比如修改某个实例的参数。
等等情况。
所以我们就需要一种机制能让我们去操作任意的类和对象。
这种机制,就是反射
。简单的说,反射就是:
对于任意一个类
,都能够知道这个类的所有属性和方法;对于任意一个对象
,都能够调用它的任意方法和属性。
常用API举例
先设定一个User类:
package com.example.testapplication.reflection; public class User { private int age; public String name; public User() { System.out.println("调用了User()"); } private User(int age, String name) { this.name = name; this.age = age; System.out.println("调用了User(age,name)"+"__age:"+age+"__name:"+name); } public User(String name) { this.name = name; System.out.println("调用了User(name)"+"__name:"+name); } private String getName() { System.out.println("调用了getName()"); return this.name; } private String setName(String name) { this.name = name; System.out.println("调用了setName(name)__"+name); return this.name; } public int getAge() { System.out.println("调用了getAge()"); return this.age; } }
获取Class对象
主要有三种方法获取Class对象
:
- 根据类路径获取类对象
- 直接获取
- 实例对象的getclass方法
//1、根据类路径获取类对象 try { Class clz = Class.forName("com.example.testapplication.reflection.User"); } catch (ClassNotFoundException e) { e.printStackTrace(); } //2、直接获取 Class clz = User.class; //3、对象的getclass方法 Class clz = new User().getClass();
获取类的构造方法
1、获取类所有构造方法
Class clz = User.class; //获取所有构造函数(不包括私有构造方法) Constructor[] constructors1 = clz.getConstructors(); //获取所有构造函数(包括私有构造方法) Constructor[] constructors2 = clz.getDeclaredConstructors();
2、获取类的单个构造方法
try { //获取无参构造函数 Constructor constructor1 = clz.getConstructor(); //获取参数为String的构造函数 Constructor constructor2 =clz.getConstructor(String.class); //获取参数为int,String的构造函数 Class[] params = {int.class,String.class}; Constructor constructor3 =clz.getDeclaredConstructor(params); } catch (NoSuchMethodException e) { e.printStackTrace(); }
需要注意的是,User(int age, String name)
为私有构造方法,所以需要使用getDeclaredConstructor
获取。
调用类的构造方法生成实例对象
1、调用Class对象的newInstance
方法
这个方法只能调用无参构造函数,也就是Class
对象的newInstance
方法不能传入参数。
Object user = clz.newInstance();
2、调用Constructor
对象的newInstance
方法
Class[] params = {int.class,String.class}; Constructor constructor3 =clz.getDeclaredConstructor(params); constructor3.setAccessible(true); constructor3.newInstance(22,"Bob");
这里要注意下,虽然getDeclaredConstructor
能获取私有构造方法,但是如果要调用这个私有方法,需要设置setAccessible(true)
方法,否则会报错:
can not access a member of class com.example.testapplication.reflection.User with modifiers "private"
获取类的属性(包括私有属性)
Class clz = User.class; Field field1 = clz.getField("name"); Field field2 = clz.getDeclaredField("age");
同样的,getField
获取public
类变量,getDeclaredField
可以获取所有变量(包括私有变量属性)。
所以一般直接用getDeclaredField即可。
修改实例的属性
接上例,获取类的属性后,可以去修改类实例的对应属性,比如我们有个user
的实例对象,我们来修改它的name和age。
//修改name,name为public属性 Class clz = User.class; Field field1 = clz.getField("name"); field1.set(user,"xixi"); //修改age,age为private属性 Class clz = User.class; Field field2 = clz.getDeclaredField("age"); field2.setAccessible(true); field2.set(user,123);
获取类的方法(包括私有方法)
//获取getName方法 Method method1 = clz.getDeclaredMethod("getName"); //获取setName方法,带参数 Method method2 = clz.getDeclaredMethod("setName", String.class); //获取getage方法 Method method3 = clz.getMethod("getAge");
调用实例的方法
method1.setAccessible(true); Object name = method1.invoke(user); method2.setAccessible(true); method2.invoke(user, "xixi"); Object age = method3.invoke(user);
反射优缺点
虽然反射很好用,增加了程序的灵活性,但是也有他的缺点:
性能问题
。由于用到动态类型(运行时才检查类型),所以反射的效率比较低。但是对程序的影响比较小,除非对性能要求比较高。所以需要在两者之间平衡。不够安全
。由于可以执行一些私有的属性和方法,所以可能会带来安全问题。不易读写
。当然这一点也有解决方案,比如jOOR库,但是不适用于Android定义为final的字段。
Android中的应用
插件化(Hook)
Hook 技术又叫做钩子函数,在系统没有调用该函数之前,钩子程序就先捕获该消息,钩子函数先得到控制权,这时钩子函数既可以加工处理(改变)该函数的执行行为,还可以强制结束消息的传递。
在插件化中,我们需要找到可以hook的点,然后进行一些插件的工作,比如替换Activity,替换mH
等等。这其中就用到大量反射的知识,这里以替换mH为例:
// 获取到当前的ActivityThread对象 Class<?> activityThreadClass = Class.forName("android.app.ActivityThread"); Field currentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread"); currentActivityThreadField.setAccessible(true); Object currentActivityThread = currentActivityThreadField.get(null); //获取这个对象的mH Field mHField = activityThreadClass.getDeclaredField("mH"); mHField.setAccessible(true); Handler mH = (Handler) mHField.get(currentActivityThread); //替换mh为我们自己的HandlerCallback Field mCallBackField = Handler.class.getDeclaredField("mCallback"); mCallBackField.setAccessible(true); mCallBackField.set(mH, new MyActivityThreadHandlerCallback(mH));
动态代理
动态代理的特点是不需要提前创建代理对象,而是利用反射机制
在运行时创建代理类,从而动态实现代理功能。
public class InvocationTest implements InvocationHandler { // 代理对象(代理接口) private Object subject; public InvocationTest(Object subject) { this.subject = subject; } @Override public Object invoke(Object object, Method method, Object[] args) throws Throwable { //代理真实对象之前 Object obj = method.invoke(subject, args); //代理真实对象之后 return obj; } }
三方库(注解)
我们可以发现很多库都会用到注解,而获取注解的过程也会有反射的过程,比如获取Activity
中所有变量的注解:
public void getAnnotation(Activity activity){ Class clazz = activity.getClass(); //获得activity中的所有变量 Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { field.setAccessible(true); //获取变量上加的注解 MyAnnotation test = field.getAnnotation(MyAnnotation.class); //... } }
这种通过反射处理注解的方式称作运行时注解,也就是程序运行状态的时候才会去处理注解。但是上文说过了,反射会在一定程度上影响到程序的性能,所以还有一种处理注解的方式:编译时注解。
所用到的注解处理工具是APT
。
APT是一种注解处理器,可以在编译时进行扫描和处理注解,然后生成java代码文件,这种方法对比反射就能比较小的影响到程序的运行性能。
这里就不说APT的使用了,下次会专门有章节提到~
Android体系架构
Android体系架构:https://github.com/JiMuzz/Android-Architecture
参考
https://www.jianshu.com/p/3382cc765b39