首先说说java反射机制
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的以及动态调用对象的方法的功能称为Java的反射机制。
使用场景
- 逆向代码 ,例如反编译
- 与注解相结合的框架 例如Retrofit
- 单纯的反射机制应用框架 例如EventBus 2.x
- 动态生成类框架 例如Gson
大牛框架
Annotations、ButterKnife、eventBus、afinal、ThinkAndroid还有国内的xutils等
ViewUtils介绍
主要说明xUtils里面的ViewUtils
在BaseActivity中初始化 init
在子集的Activity中赋值
@Inject(R.id.text)
TextView text;
反射实践
首先定义注解User类
public class User {
private String name;
private int age;
private String eat(String eat){
System.out.println("eat = "+eat);
return eat+"真好吃";
}
}
定义注解对象
@Target(ElementType.FIELD)//用于限制当前自定义注解类作用的对象
//@Retention(RetentionPolicy.SOURCE)//用于源码阶段 该注解类只会在源码出现,当将源码编译成字节码的时候注解信息就被清除
//@Retention(RetentionPolicy.CLASS) //该注解类会被编译到字节码中,但是当虚拟机去加载这个字节码的时候,注解信息就被清除
@Retention(RetentionPolicy.RUNTIME)//该注解类永远保留到被加载到虚拟机中
public @interface ViewInject {
int age();
String name();
}
然后将注解对象放到对应的注解类上
@ViewInject(name="张三",age = 12)
private String name;
这里如果将注解放到eat方法上面就会报错,因为定义的注解是FIELD类型
然后开始反射赋值,在字段上面赋值
@ViewInject(name="张三",age = 12)
private String name;
最后在开始通过Java反射赋值
//需求:获取User类中name字段上的自定义注解的值,然后将该值的age通过反射设置给User属性,将name 设置给user的name属性
User user = new User();
第一步 获取user类的字节码
// user.getClass();
// User.class;
// Class.forName("xxxxx")
Class clazz = User.class;
第二步 将字节码里面的name字段获取到,其中getField的方法只能获取public方法
// clazz.getField(name) 这个方法只能获取声明为public的方法
Field declaredField = clazz.getDeclaredField("name");
Field declaredField2 = clazz.getDeclaredField("age");
第三步 获取字段上面的注解对象
ViewInject annotation = declaredField.getAnnotation(ViewInject.class);
if(annotation!=null){
int age = annotation.age();
String name = annotation.name();
printlns("age = "+ age + ",name = "+ name);
/**
* 通过反射将这个两个值设置给user对象
*/
declaredField.setAccessible(true);//设置允许反射,其实就是允许暴力反射
declaredField2.setAccessible(true);
declaredField.set(user,name);
declaredField2.set(user,age);
printlns(user.toString());
}else {
printlns("字段上面没有定义注解");
}
//通过反射调用eat的方法
Method declaredMethod = clazz.getDeclaredMethod("eat",String.class);
declaredMethod.setAccessible(true);
Object object = declaredMethod.invoke(user, "牛肉拉面");
printlns((String) object);
这里注意几点:(干货)
- getDeclaredField和getField区别:第一个是能获取所有字段,第二个是只能获取定义了public的字段,所有如果要获取所有的字段就要用到getDeclaredField
- setAccessible作用:默认是false,设置是否能够获取私用字段,方法同理。
- 命名value 和 id 区别:value是抽象方法,在使用时候不需要写,下面就会将到。
自己编写ioc框架
首先我们新建 ViewInject注解类,ViewUtils实现类
在ViewInject类中定义注解的抽象方法名,这里说一下,定义value 和 其他名字比如 id
如果定义了id方法,在使用的时候,需要出现 id 名称
@ViewInject(id = R.id.text)
TextView text;
而如果注解抽象方法明是 value 的话,则不需要
@ViewInject(R.id.text)
TextView text;
然后再ViewUtils类里面定义 init()方法,这里需要订单形参,网上说的好多方法参数大部分使用的是Activity,因为这样是面向父类编程,但是我不这么认为,既然面向父类编程,为啥,不彻底恩,直接用Context 上下文 做为形参,当然也不是说用Activity不对。
写了这些,可以在MainActivity中先定义上
public class MainActivity extends AppCompatActivity {
@ViewInject(R.id.text)
TextView text;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewUtils.init(this);
if(text != null){
text.setText("这是成功后赋值");
}
}
}
最后我们开始编写最核心的逻辑 ViewUtils 类
第一:先编写字段View的绑定
- 先获取字节码
- 获取所有的字段
- 遍历字段查找还有注解字段
- 获取注解字段的值
- 通过反射,获取类的对应方法,将值赋值给字段
代码实现如下
/**
* 这是绑定视图View
* @param context
* @throws NoSuchMethodException
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
private static void bindView(Context context) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
/**
* 1 获取字节码
*/
Class clazz = context.getClass();
/**
* 2 获取所以的字段
*/
Field[] fields = clazz.getDeclaredFields();
if(fields != null && fields.length > 0){
for (int i =0 ;i<fields.length;i++){
Field field = fields[i];
/**
* 3.遍历字段查找还有注解字段
*/
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if(viewInject!=null){
/**
* 4.获取注解字段的值
*/
int valueId = viewInject.value();
if(valueId>0){
/**
* 通过反射,获取类的对应方法,将值赋值给字段
*/
Method findByIdM = clazz.getMethod("findViewById",int.class);
findByIdM.setAccessible(true);
Object findByIdObj = findByIdM.invoke(context,valueId);
field.setAccessible(true);
field.set(context,findByIdObj);
}
}
}
}
}
下面开始实现绑定点击事件
- 先获取字节码
- 获取所有的方法
- 遍历方法,获取方法上所有的注解
- 遍历注解,获取注解的类型,获取注解的注解,判断是否存
- 通注解类型获取反射注解的方法,获取所有的字段
- 利用动态代理模式将注解的onclickListener进行代理设置,这里需要新建代理类
- 获取注解信息,并且实例化
- 将实例化的对象进行事件绑定
代理类
public class DayHandler implements InvocationHandler{
private Map<String,Method> mtdMap;
private WeakReference<Object> object;
public DayHandler(Object object){
this.object = new WeakReference<Object>(object);
}
public void addMethod(String name,Method method){
if(mtdMap == null){
mtdMap = new HashMap<>();
}
mtdMap.put(name,method);
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
Object handler = object.get();
if(handler!=null){
String name = method.getName();
method = mtdMap.get(name);
if(method!=null){
return method.invoke(handler,objects);
}
}
return null;
}
}
注解类
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventBus {
String listenerSetter();
Class listenerType();
String listenerBackName();
}
这个
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventBus(listenerType = View.OnClickListener.class, listenerSetter = "setOnClickListener", listenerBackName = "onClick")
public @interface OnClick {
int[] value();
}
注解实现
private static void bindOnClick(Context context) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class<? extends Context> clazz = context.getClass();
Method[] methods = clazz.getDeclaredMethods();
//遍历所有的方法
for (Method method : methods) {
Annotation[] annotations = method.getAnnotations();
//拿到方法上的所有的注解
for (Annotation annotation : annotations) {
Class<? extends Annotation> annotationType = annotation.annotationType();
//拿到注解上的注解
EventBus eventBus = annotationType.getAnnotation(EventBus.class);
//如果设置为EventBase
if (eventBus != null) {
//取出设置监听器的名称,监听器的类型,调用的方法名
String listenerSetter = eventBus
.listenerSetter();
Class<?> listenerType = eventBus.listenerType();
String methodName = eventBus.listenerBackName();
try {
//拿到Onclick注解中的value方法
Method aMethod = annotationType.getDeclaredMethod("value");
//取出所有的viewId
int[] viewIds = (int[]) aMethod.invoke(annotation, null);
//通过InvocationHandler设置代理
DayHandler handler = new DayHandler(context);
handler.addMethod(methodName, method);
Object listener = Proxy.newProxyInstance(
listenerType.getClassLoader(),
new Class<?>[] { listenerType }, handler);
//遍历所有的View,设置事件
for (int viewId : viewIds) {
Method findViewById = clazz.getMethod("findViewById", int.class);
Object view = findViewById.invoke(context, viewId);
Method setEventListenerMethod = view.getClass().getMethod(listenerSetter, listenerType);
setEventListenerMethod.invoke(view, listener);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}