Android:注解反射+APT 的使用

前言:

当我们需要对一段代码进行拦截修改的时候,我们有很多方案,最常见的是通过反射。但是反射是在运行时的工作,对性能有很大的影响,所以提出了APT技术,该技术可以在编译期对代码进行拦截,并进行修改,生成新的可执行的类。

注解反射

当我们使用注解的时候,大部分都是配合反射一起使用,通过反射可以轻易获取到注解上的类或者属性,然后在对该属性进行代码修改。现在让我们通过反射注解的方式对组件进行初始化工作。

//通过注解生成View;
    private void getAllAnnotationView() {
        //获得某个类的所有声明的字段,即包括public、private和proteced,但是不包括父类的申明字段。
        Field[] fields = this.getClass().getDeclaredFields();
        for (Field field : fields){
            try {
                //获取该元素上的所有注解,包含从父类继承
                if(field.getAnnotations() != null){
                    //确定注解类型   给@getViewTo 赋值
                    if(field.isAnnotationPresent(GetViewTo.class)){
                        //允许修改反射属性
                        field.setAccessible(true);
                        GetViewTo getViewTo = field.getAnnotation(GetViewTo.class);
                        //findViewById将注解的id,找到View注入成员变量中
                        field.set(this, findViewById(getViewTo.value()));
                    }

                    //给@Title 赋值
                    if(field.isAnnotationPresent(Title.class)){
                        //允许修改反射属性
                        field.setAccessible(true);
                        Title abc = field.getAnnotation(Title.class);
                        //findViewById将注解的id,找到View注入成员变量中
                        field.set(this, abc.value());
                    }
                }
            }catch (Exception e){
            }
        }
    }

以上的代码是对添加了 @GetViewTo 属性的变量进行赋值。然后我们可以在项目中直接使用:

 @GetViewTo(R.id.btn)
 private Button btn;

这样我们通过反射的方式对组件进行初始化就完成了!!

APT的使用

APT是什么意思,他其实是AOP的一种实现方案,aop就是面向切面的开发,而安卓aop三剑客分别是:apt,aspectJ,javassist;

 

分类说明案例
APT(注解处理器)定义编译期的注解,再通过继承Proccesor实现代码生成逻辑,实现了编译期生成代码的逻辑。(compile任务前,修改java文件)DataBinding,Dagger2, ButterKnife, EventBus3
AspectJJake Wharton,支持编译期和加载时代码注入 ( class阶段,修改java代码)

主要用于性能监控,日志埋点等

Javassist 编译期间修改class文件,与之相似的ASM,在class文件被转化为dex文件之前去修改(修改的.class)

热更新,ARouter

ASM (字节码修改器)

ASM是一种基于java字节码层面的代码分析和修改工具,ASM的目标是生成,转换和分析已编译的java class文件,可使用ASM工具读/写/转换JVM指令集。通俗点讲就是来处理javac编译之后的class文件 (修改的.class)

 

这四种aop思想,目前我们先只说APT技术,同样是做组件的初始化工作:

1)首先我们创建一个为 apt-annotation 的java lib moudle(此处是java lib) 插件

在apt-annotation中创建一个注解类 BindView

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
    int value();
}

2) 创建一个为 apt-compiler 的java lib moudle(此处是java lib) 插件

我们创建一个BindViewProcessor类

/**
 * Created by malei on 2020/9/14
 * Describe:
 */
@SupportedSourceVersion(SourceVersion.RELEASE_7) //java的支持版本
@SupportedAnnotationTypes("com.example.apt_annotation.BindView") //标注注解处理器支持的注解类型
public class BindViewProcessor extends AbstractProcessor {

    private Filer mFilerUtils;       // 文件管理工具类
    private Types mTypesUtils;    // 类型处理工具类
    private Elements mElementsUtils;  // Element处理工具类
    private Map<TypeElement, Set<ViewInfo>> mToBindMap = new HashMap<>(); //用于记录需要绑定的View的名称和对应的id

    //要绑定的View的信息载体
    class ViewInfo {
        String viewName;    //view的变量名
        int id; //xml中的id

        public ViewInfo(String viewName, int id) {
            this.viewName = viewName;
            this.id = id;
        }
    }

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        mFilerUtils = processingEnv.getFiler();
        mTypesUtils = processingEnv.getTypeUtils();
        mElementsUtils = processingEnv.getElementUtils();
    }
    
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (set != null && set.size() != 0) {
            Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);//获得被BindView注解标记的element

            categories(elements);//对不同的Activity进行分类

            //对不同的Activity生成不同的帮助类
            for (TypeElement typeElement : mToBindMap.keySet()) {
                //获取要生成的帮助类中的所有代码
                String code = generateCode(typeElement);
                //构建要生成的帮助类的类名
                String helperClassName = typeElement.getQualifiedName() + "$$Autobind";

                //输出帮助类的java文件,在这个例子中就是MainActivity$$Autobind.java文件
                //输出的文件在build->source->apt->目录下
                try {
                    JavaFileObject jfo = mFilerUtils.createSourceFile(helperClassName, typeElement);
                    Writer writer = jfo.openWriter();
                    writer.write(code);
                    writer.flush();
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return true;
        }
        return false;
    }

    private String generateCode(TypeElement typeElement) {

        //获取要绑定的View所在类的名称
        String rawClassName = typeElement.getSimpleName().toString();
        //获取要绑定的View所在类的包名
        String packageName = ((PackageElement) mElementsUtils.getPackageOf(typeElement)).getQualifiedName().toString();
        //要生成的帮助类的名称
        String helperClassName = rawClassName + "$$Autobind";

        StringBuilder builder = new StringBuilder();
        builder.append("package ").append(packageName).append(";\n");   //构建定义包的代码
        builder.append("import com.example.apt_api.IBindHelper;\n\n"); //构建import类的代码

        builder.append("public class ").append(helperClassName).append(" implements ").append("IBindHelper");   //构建定义帮助类的代码
        builder.append(" {\n"); //代码格式,可以忽略
        builder.append("\t@Override\n");    //声明这个方法为重写IBindHelper中的方法
        builder.append("\tpublic void inject(" + "Object" + " target ) {\n");   //构建方法的代码

        for (ViewInfo viewInfo : mToBindMap.get(typeElement)) { //遍历每一个需要绑定的view
            builder.append("\t\t"); //代码格式,可以忽略
            builder.append(rawClassName + " substitute = " + "(" + rawClassName + ")" + "target;\n");    //强制类型转换

            builder.append("\t\t"); //代码格式,可以忽略
            builder.append("substitute." + viewInfo.viewName).append(" = ");    //构建赋值表达式
            builder.append("substitute.findViewById(" + viewInfo.id + ");\n");  //构建赋值表达式
        }

        builder.append("\t}\n");    //代码格式,可以忽略
        builder.append('\n');   //代码格式,可以忽略
        builder.append("}\n");  //代码格式,可以忽略

        return builder.toString();
    }

    private void categories(Set<? extends Element> elements) {
        //遍历每一个element
        for (Element element : elements){
            //被@BindView标注的应当是变量,这里简单的强制类型转换
            VariableElement variableElement = (VariableElement) element;
            //返回封装该元素的类
            TypeElement enclosingElement = (TypeElement) variableElement.getEnclosingElement();

            //views储存着一个Activity中将要绑定的view的信息
            Set<ViewInfo> views = mToBindMap.get(enclosingElement);
            //如果views不存在就new一个
            if (views == null) {
                views = new HashSet<>();
                mToBindMap.put(enclosingElement, views);
            }
            //获取到一个变量的注解
            BindView bindAnnotation = variableElement.getAnnotation(BindView.class);
            //取出注解中的value值,这个值就是这个view要绑定的xml中的id
            int id = bindAnnotation.value();
            //把要绑定的View的信息存进views中
            views.add(new ViewInfo(variableElement.getSimpleName().toString(), id));
        }
    }
}

build.gradle中需要添加依赖:

implementation project(path: ':apt-annotation')

3)创建一个android lib 包,提供向外支持:

创建接口:

public interface IBindHelper {
    void inject(Object target);
}

创建工具类:

public class AutoBind {
    private static volatile AutoBind instance = null;

    public AutoBind() {
    }

    public static AutoBind getInstance() {
        if(instance == null) {
            synchronized (AutoBind.class) {
                if (instance == null) {
                    instance = new AutoBind();
                }
            }
        }
        return instance;
    }

    public void inject(Object target) {
        String className = target.getClass().getCanonicalName();
        String helperName = className + "$$Autobind";
        try {
            IBindHelper helper = (IBindHelper) (Class.forName(helperName).getConstructor().newInstance());

            helper.inject(target);
        }   catch (Exception e) {
            e.printStackTrace();
        }
    }
}

gradle.buidle需要依赖:

api project(':apt-annotation')

4)最后我们在app gradle中需要依赖如下:

annotationProcessor project(':apt-compiler')
implementation project(':apt-api')

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值