Java

初识Android高级应用之 APT

2019-08-24  本文已影响2人  Cliper

APT 是什么?

可以理解添加注解就可以预编译生成代码的一种方式。

分别包括三种module

1.apt-annotation (java libarary):这里需要定义一些注解类 比如:@BindView
2.apt-library :这里需要借助反射来获取并绑定一些数据 如BufferKnife.bind(this);
3.apt-processor (java libarary): 这里通过定义的注解 在APP的generated生成代码。

他们的依赖关系:
apt-processor 因为需要获取注解 ,则需要依赖apt-annotation
APP ,则需要依赖apt-annotation ,apt-library,.apt-processor 三个。

这里对Element说明一下:
然后是分别处理我们自定义的三个注解,得到注解标致的value值也就是权限requestCode和声明的方法名,装载到mProxyMap集合中
代码中的roundEnv.getElementsAnnotatedWith返回的其实就是每个注解所标注的方法元素集合。
element有多个子集,介绍几个常见的
TypeElement------类型元素,注意枚举对应的时类,注解对应的接口
ExecutableElement------一般是指可执行的方法元素
VariableElement------一般指常量,变量 ,异常对象元素
pakeageElement-----一般指包元素,可以获得包信息
TypeParamterElement----这个也是比较常用的,一般指继承类实现的接口,或者泛型对象元素

下面以案例的方式来讲解:

1.apt-annotation

@Retention(RetentionPolicy.CLASS) //编译时
@Target(ElementType.FIELD)//注解的类型 字段
public @interface BindView {
    int value();
}

3.apt-processor

这里是核心 千万别出错,否则无法生成Java文件

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Processor;
@AutoService(Processor.class)
public class LoBindViewProcessor extends AbstractProcessor

@AutoService(Processor.class)
public class LoBindViewProcessor extends AbstractProcessor {
    private Elements elementUtils;
    private Messager environmentMessager;
    private HashMap<String , ClassCreatorProxy> mHashMapCache = new HashMap<>();
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        elementUtils = processingEnvironment.getElementUtils();
        environmentMessager = processingEnvironment.getMessager();
    }

    @Override
    public Set<String> getSupportedOptions() {
        HashSet hashSet = new LinkedHashSet();
        hashSet.add(BindView.class.getCanonicalName());//com.xxx.BindView.class
        return hashSet;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        mHashMapCache.clear();
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(BindView.class);
        for (Element element : elements) {
            //字段
            VariableElement variableElement = (VariableElement) element;
            //  获取成员变量所在的类。(父元素的全限定名)
            TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();
            // // 获取类的全限定名。com.xx.xx.MainActivity.class
            String fullClassName = classElement.getQualifiedName().toString();
            System.out.println(fullClassName);
            //elements的信息保存到mProxyMap中
            ClassCreatorProxy proxy = mHashMapCache.get(fullClassName);
            if (proxy == null) {
                proxy = new ClassCreatorProxy(elementUtils, classElement);
                mHashMapCache.put(fullClassName, proxy);//根据com.xx.xx.MainActivity.class 对应一个ClassCreatorProxy解析器
            }
            BindView bindAnnotation = element.getAnnotation(BindView.class);
            int id = bindAnnotation.value();
            // 将同一个类的注解的所有成员变量放到一个ClassInfo类中
            proxy.putElement(id, variableElement);
        }

        for (String key : mHashMapCache.keySet()) {
            ClassCreatorProxy proxyInfo = mHashMapCache.get(key);
            //通过square公司的库生成Java文件
            JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(), proxyInfo.generateJavaCode2()).build();
            try {
                // 生成文件
                javaFile.writeTo(processingEnv.getFiler());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        environmentMessager.printMessage(Diagnostic.Kind.NOTE, "process finish ...");
        return true;
    }
}

ClassCreatorProxy的实现如下:

public class ClassCreatorProxy {
    private String mBindingClassName;
    private String mPackageName;
    private TypeElement mTypeElement;
    private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>();

    public ClassCreatorProxy(Elements elementUtils, TypeElement classElement) {
        this.mTypeElement = classElement;
        PackageElement packageElement = elementUtils.getPackageOf(mTypeElement);
        String packageName = packageElement.getQualifiedName().toString();
        String className = mTypeElement.getSimpleName().toString();//MainActivity
        this.mPackageName = packageName;
        this.mBindingClassName = className + "_ViewBinding";
    }

    public void putElement(int id, VariableElement element) {
        mVariableElementMap.put(id, element);//3234,字段的修饰类型
    }

    /**
     * 创建Java代码
     *
     * @return
     */
    public String generateJavaCode() {
        StringBuilder builder = new StringBuilder();
        builder.append("package ").append(mPackageName).append(";\n\n");
        builder.append("import com.example.gavin.apt_library.*;\n");
        builder.append('\n');
        builder.append("public class ").append(mBindingClassName);
        builder.append(" {\n");

        generateMethods(builder);
        builder.append('\n');
        builder.append("}\n");
        return builder.toString();
    }

    /**
     * 加入Method
     * 手动拼接
     * @param builder
     */
    private void generateMethods(StringBuilder builder) {
        builder.append("public void bind(" + mTypeElement.getQualifiedName() + " host ) {\n");
        //  host.mButton = (android.widget.Button)(((android.app.Activity)host).findViewById( 2131165218));
        for (int id : mVariableElementMap.keySet()) {
            VariableElement element = mVariableElementMap.get(id);//字段限定
            String name = element.getSimpleName().toString();//mButton
            String type = element.asType().toString();//Button
            builder.append("host." + name).append(" = ");
            builder.append("(" + type + ")(((android.app.Activity)host).findViewById( " + id + "));\n");
        }
        builder.append("  }\n");
    }

    public String getProxyClassFullName() {
        return mPackageName + "." + mBindingClassName;
    }

    public TypeElement getTypeElement() {
        return mTypeElement;
    }

    //======================

    /**
     * 创建Java代码
     * javapoet
     *
     * @return
     */
    public TypeSpec generateJavaCode2() {
        TypeSpec bindingClass = TypeSpec.classBuilder(mBindingClassName)
                .addModifiers(Modifier.PUBLIC)
                .addMethod(generateMethods2())
                .build();
        return bindingClass;

    }

    /**
     * 加入Method
     * javapoet
     */
    private MethodSpec generateMethods2() {
        ClassName host = ClassName.bestGuess(mTypeElement.getQualifiedName().toString());
        MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
                .addModifiers(Modifier.PUBLIC)
                .returns(void.class)
                .addParameter(host, "host");//Activity

        for (int id : mVariableElementMap.keySet()) {
            VariableElement element = mVariableElementMap.get(id);
            String name = element.getSimpleName().toString();
            String type = element.asType().toString();
            //  host.mButton = (android.widget.Button)(((android.app.Activity)host).findViewById( 2131165218));
            methodBuilder.addCode("host." + name + " = " + "(" + type + ")(((android.app.Activity)host).findViewById( " + id + "));");
        }
        return methodBuilder.build();
    }


    public String getPackageName() {
        return mPackageName;
    }
}

apt-library

然后就可以通过反射进行调用赋值了:

//apt-library
public class BindViewTools {

    public static void bind(Activity activity) {

        Class clazz = activity.getClass();
        try {
            Class bindViewClass = Class.forName(clazz.getName() + "_ViewBinding");
            Method method = bindViewClass.getMethod("bind", activity.getClass());
            method.invoke(bindViewClass.newInstance(), activity);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}


APP中使用:
    @BindView(R.id.tv)
    TextView mTextView;
    @BindView(R.id.btn)
    Button mButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        BindViewTools.bind(this);
        mTextView.setText("bind TextView ");
        mButton.setText("bind Button ");
    }

最终生成的文件如下:

public class MainActivity_ViewBinding {
  public void bind(MainActivity host) {
    host.mButton = (android.widget.Button)(((android.app.Activity)host).findViewById( 2131165218));
    host.mTextView = (android.widget.TextView)(((android.app.Activity)host).findViewById( 2131165321));
    }
 }
   ---------------------------------------------------
    APP的依赖
    implementation project(':apt-annotation')
    implementation project(':apt-library')
    annotationProcessor project(':apt-processor')

    apt-processor的依赖
    implementation 'com.google.auto.service:auto-service:1.0-rc4'
    implementation 'com.squareup:javapoet:1.10.0'
    implementation project(':apt-annotation')

可在如下目录查看:


微信图片_20190824113926.png

最后补充说明一下:就是特别注意Androidstudio3.4.2的版本无法正常生成Java文件。如果确认代码没有问题,还是无法生成Java文件,建议降低一下gradle 和 Application 中 com.android.tools.build:gradle 的版本
我这里是
distributionUrl=https://services.gradle.org/distributions/gradle-4.4-all.zip
classpath 'com.android.tools.build:gradle:3.1.1'
我这里就被坑了好久,com.android.tools.build 从 3.4.2降到3.1.1就可以生成Java文件了。

上一篇 下一篇

猜你喜欢

热点阅读