学习笔记:通过反射实现类似ButterKnife功能(layout和view注入)

264 阅读2分钟

1.了解反射相关的Api

getFields() // 获取所有的public属性
getDeclaredFields() // 获取所有的属性包括private属性
getMethods() // 获取所有的public方法
getDeclaredMethods() // 获取所有的方法包括private方法
getAnnotation(Class<A> annotationClass) // 获取参数类型的注解对象
invoke(Object obj, Object... args) // 传递object对象及参数调用该对象对应的方法

2.项目目录

inject目录下包含有一个annotation文件夹和一个InjectManager类 annotation目录存放自定的注解,InjectManager实现具体的功能

3.自定义注解

//ElementType.TYPE:作用在类上
//ElementType.FIELD:作用在属性上
//ElementType.METHOD:作用在方法上

// 注入layout自定义注解
@Target(ElementType.TYPE)  // 定义注解的类型 这是一个作用在类的上注解
@Retention(RetentionPolicy.RUNTIME) 
public @interface InjectLayout { 
    int value(); // 注解传进来的参数,int类型:R.layout.xxx
}

// 注入View自定义注解
@Target(ElementType.FIELD)  // 定义注解的类型 这是一个作用在属性的上注解
@Retention(RetentionPolicy.RUNTIME)
public @interface InjectView {
    int value(); // 注解传进来的参数,int类型:R.id.xxx
}

4.InjectManager类实现

import android.app.Activity;
import com.ruiheng.injectdemo.inject.annotation.InjectLayout;
import com.ruiheng.injectdemo.inject.annotation.InjectView;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class InjectManager {
    
    public static void injectLayout(Activity activity) {
        Class<? extends Activity> clazz = activity.getClass();
        //获取当前类上的InjectLayout注解
        InjectLayout injectLayout = clazz.getAnnotation(InjectLayout.class);
        if (injectLayout != null) { //如果当前类上有InjectLayout注解
            //获取layoutId
            int layoutId = injectLayout.value();
            try {
                //通过getMethod获取setContentView方法
                Method method = clazz.getMethod("setContentView", int.class);
                //执行setContentView操作
                method.invoke(activity, layoutId);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void injectView(Activity activity) {

        Class<? extends Activity> clazz = activity.getClass();
        //通过反射获取所有属性
        //  clazz.getFields()只能获取public的属性,包括父类的属性
        // clazz.getDeclaredFields()能获取所有属性包括private的属性
        Field[] fields = clazz.getDeclaredFields();
        //遍历所有属性
        for (Field field : fields) {
            InjectView annotation = field.getAnnotation(InjectView.class);
            if (annotation != null) {
                try {
                    //获取findView方法
                    Method findViewById = clazz.getMethod("findViewById", int.class);
                    //通过findView方法获取View
                    Object view = findViewById.invoke(activity, annotation.value());
                    //打开私有权限
                    field.setAccessible(true);
                    //给属性设置值
                    field.set(activity, view);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

5.使用

    可以在activity的基类中统一调用
     InjectManager.injectLayout(this);
     InjectManager.injectView(this);

    在子类activity使用注解注入

6.总结

昨天通过公开课学习手撸butterknife类似效果,今天自己手撸一遍,做个记录。
整体实现是通过反射+注解的方式实现,跟butterknife的实现方式还不太一样(butterknife是通过apt+注解,编译器编译时自动生成辅助类)。
OS:一直不喜欢用butterKnife,觉得findViewById和@BindView在代码量上也没啥差别(都是一行嘛。。。)