• android的APT技术


    转载请标明出处:https:////www.cnblogs.com/tangZH/p/12343786.html

                                http://77blogs.com/?p=199

    APT 是Annotation Processing Tool 的简称。

    它是注解处理器,在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编写者决定),APT还会编译生成源文件和原来的源文件,将它们一起生成class文件。
    简言之:APT可以根据注解,在编译时生成代码。

    事实上它是javac的一个工具,命令行运行javac后便可以看到:

     

     

     

     接下来我们就来实现一个apt的实例,类似于ButterKnife中@BindView注解,基本步骤如下:

    1、定义要被处理的注解。

    2、定义注解处理器(生成具体的类)。

    3、调用处理器生成的代码

     

    对应的,我们在工程中需要有这几个模块:

    1、app。测试我们的功能

    2、apt-annotation。一个Java library module,放置我们自定义注解

    3、apt-processor。一个Java library module,注解处理器模块

    4、apt-sdk。一个Android library module,通过反射调用apt-processor模块生成的方法,实现view的绑定。

    工程目录如下:

     

     

     

     

     

    1、在apt-annotation中自定义注解:

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

     

    2、apt-processor中引入依赖,它需要依赖apt-annotation,同时还需要依赖auto-service第三方库,后面创建注解处理器的时候需要用到。

    apt-processor/build.gradle文件中:

    implementation project(':apt-annotation')
    implementation 'com.google.auto.service:auto-service:1.0-rc2'

     

    3、在pat-processor中创建注解处理器:

    处理器需要继承AbstractProcessor,注意该module是 java module,如果创建的是android module的话那么就会找不到AbstractProcessor

    @AutoService(Processor.class)
    @SuppressWarnings("unused")
    public class BindViewProcessor extends AbstractProcessor {
        private Elements mElementUtils;
        private Map<String, ClassCreatorFactory> mClassCreatorFactoryMap = new HashMap<>();
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
            //拿到工具类
            mElementUtils = processingEnvironment.getElementUtils();
        }
    
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            //这个注解处理器是给哪个注解用的
            HashSet<String> supportType = new LinkedHashSet<>();
            supportType.add(BindView.class.getCanonicalName());
            return supportType;
        }
    
        @Override
        public SourceVersion getSupportedSourceVersion() {
            //返回java版本
            return SourceVersion.latestSupported();
        }
    
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            mClassCreatorFactoryMap.clear();
            //得到所有包含该注解的element集合
            Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
    
            for (Element element : elements) {
                //转换为VariableElement,VariableElement为element的子类
                VariableElement variableElement = (VariableElement) element;
                //可以获取类的信息的element,也是element的子类
                TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
                //获取包名加类名
                String fullClassName = classElement.getQualifiedName().toString();
                
                //保存到集合中
                ClassCreatorFactory factory = mClassCreatorFactoryMap.get(fullClassName);
                if (factory == null) {
                    factory = new ClassCreatorFactory(mElementUtils, classElement);
                    mClassCreatorFactoryMap.put(fullClassName, factory);
                }
                BindView bindViewAnnotation = variableElement.getAnnotation(BindView.class);
                int id = bindViewAnnotation.value();
                factory.putElement(id, variableElement);
            }
            //开始创建java类
            for (String key : mClassCreatorFactoryMap.keySet()) {
                ClassCreatorFactory factory = mClassCreatorFactoryMap.get(key);
                try {
                    JavaFileObject fileObject = processingEnv.getFiler().createSourceFile(
                            factory.getClassFullName(), factory.getTypeElement());
                    Writer writer = fileObject.openWriter();
                    //写入java代码
                    writer.write(factory.generateJavaCode());
                    writer.flush();
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return true;
        }
    }

    需要注意的是代码中不能有中文,否则编译不通过,我这里为了方便注释解释加上了中文。

     

    ClassCreatorFactory的代码如下,这个类负责提供需要写入新的类的代码:

    public class ClassCreatorFactory {
        private String mBindClassName;
        private String mPackageName;
        private TypeElement mTypeElement;
        private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>();
    
        ClassCreatorFactory(Elements elementUtils, TypeElement classElement) {
            this.mTypeElement = classElement;
            //PackageElement是element的子类,可以拿到包信息
            PackageElement packageElement = elementUtils.getPackageOf(mTypeElement);
            String packageName = packageElement.getQualifiedName().toString();
            String className = mTypeElement.getSimpleName().toString();
            this.mPackageName = packageName;
            //生成的类的名称
            this.mBindClassName = className + "_ViewBinding";
        }
    
        public void putElement(int id, VariableElement element) {
            mVariableElementMap.put(id, element);
        }
    
        public String generateJavaCode() {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.append("/**
    " + " * Auto Created by apt
    " + "*/
    ");
            stringBuilder.append("package ").append(mPackageName).append(";
    ");
            stringBuilder.append('
    ');
            stringBuilder.append("public class ").append(mBindClassName);
            stringBuilder.append(" {
    ");
            generateBindViewMethods(stringBuilder);
    
            stringBuilder.append('
    ');
            stringBuilder.append("}
    ");
            return stringBuilder.toString();
        }
    
        private void generateBindViewMethods(StringBuilder stringBuilder) {
            stringBuilder.append("	public void bindView(");
            stringBuilder.append(mTypeElement.getQualifiedName());
            stringBuilder.append(" owner) {
    ");
            for (int id : mVariableElementMap.keySet()) {
                VariableElement variableElement = mVariableElementMap.get(id);
                String viewName = variableElement.getSimpleName().toString();
                String viewType = variableElement.asType().toString();
                stringBuilder.append("		owner.");
                stringBuilder.append(viewName);
                stringBuilder.append(" = ");
                stringBuilder.append("(");
                stringBuilder.append(viewType);
                stringBuilder.append(")(((android.app.Activity)owner).findViewById( ");
                stringBuilder.append(id);
                stringBuilder.append("));
    ");
            }
            stringBuilder.append("  }
    ");
        }
    
        public String getClassFullName() {
            return mPackageName + "." + mBindClassName;
        }
    
        public TypeElement getTypeElement() {
            return mTypeElement;
        }
    }

     

    先不谈apt-sdk模块,我们先来看看生成的代码是怎么样的。

    在app的gradle中引入:

    implementation project(':apt-annotation')
    annotationProcessor project(':apt-processor')

    特别要注意的是apt-processor模块的依赖引进要用 annotationProcessor,否则编译报错

     

    两个activity中:

    public class MainActivity extends AppCompatActivity {
        @BindView(R.id.tv)
        TextView textView;
    
        @BindView(R.id.tv_1)
        TextView tv;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    }

     

    public class Main2Activity extends AppCompatActivity {
    
        @BindView(R.id.tv_2)
        TextView textView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main2);
        }
    }

     

    rebuild一下便可以看到在这个目录下有我们生成的文件了。

    gradle高版本出现编译后没出现文件的问题,无奈只好降低版本,我使用的版本是gradle  3.1.4 +  gralde_wrap  gradle-4.4-all.zip

     

     

     点进入其中一个可以看到是这样的代码:

    /**
     * Auto Created by apt
    */
    package com.example.aptsample;
    
    public class MainActivity_ViewBinding {
        public void bindView(com.example.aptsample.MainActivity owner) {
            owner.tv = (android.widget.TextView)(((android.app.Activity)owner).findViewById( 2131165360));
            owner.textView = (android.widget.TextView)(((android.app.Activity)owner).findViewById( 2131165359));
      }
    
    }

    所以我们只要调用bindView就能够找到该view了,这也是apt-sdk要做的事情。

    4、在apt-sdk中创建类,反射调用生成的类中的方法

    public class DataApi {
        public static void bindView(Activity activity) {
            Class clazz = activity.getClass();
            try {
                Class<?> bindViewClass = Class.forName(clazz.getName() + "_ViewBinding");
                Method method = bindViewClass.getMethod("bindView", activity.getClass());
                method.invoke(bindViewClass.newInstance(),activity);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
    }

    5、app的gradle中引入apt-sdk,然后代码调用DataApi的方法

    implementation project(':apt-annotation')
    annotationProcessor project(':apt-processor')
    implementation project(':apt-sdk')

    app的MainActivity中实现

    public class MainActivity extends AppCompatActivity {
        @BindView(R.id.tv)
        TextView textView;
    
        @BindView(R.id.tv_1)
        TextView tv;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            DataApi.bindView(this);
            tv.setText("a");
        }
    }

    这样就大功告成了

    精美原文:http://77blogs.com/?p=199

    源码地址:https://github.com/TZHANHONG/AptAutoBindView

  • 相关阅读:
    RT: TCP connection close
    RT: TCP REUSEADDR or REUSEPORT
    RT:How HTTP use TCP connection
    一些英语技巧
    [转] HBase的特征和优点
    [转] Java多线程发展简史
    [转] socket异步编程--libevent的使用
    连接Mysql提示Can’t connect to local MySQL server through socket的解决方法
    出现”/var/lib/mysql/mysql.sock“不存在的解决方法
    eclipse代码编辑器中按alt+/提示No Default Proposals 的解决方法
  • 原文地址:https://www.cnblogs.com/tangZH/p/12343786.html
Copyright © 2020-2023  润新知