龙空技术网

Android 架构师之路 APT (1)

zidea 32

前言:

目前看官们对“android javapoet”都比较讲究,看官们都需要剖析一些“android javapoet”的相关资讯。那么小编同时在网摘上网罗了一些关于“android javapoet””的相关内容,希望我们能喜欢,小伙伴们快快来了解一下吧!

今天我们先给出 demo 的代码,随后会就 APT 技术进行详细介绍

APT

目标通过自己视图绑定功能来了解如何 APT 帮助我们在编译期间自动生成代码。在网上收集一些资料自己实现一下。

目标使用 APT 实现类似 Butter Knife 库提供的通过注解实现视图绑定的功能

最近使用 binding 实现视图绑定功能。有点渐渐远离了 ButterKnife ,不过这个库还是给我留下美好的回忆,简洁明了的 API 让人印象深刻。

创建一个空 Android 项目,接下我们添加模块到项目,每一个模块在 APT 中实现不同功能。

创建模块创建 zi-annotation 注意这是一个 (java 模块),在这个模块中,可以创建注解 ZiViewBind

创建 zi-api (android 模块)提供用户可以调用 api ,这里实现 view 的绑定。

创建 zi-compiler(java 模块) 编写注解处理 。

Android Studio 在当我们创建好模块自动在 setting 文件中写入了这些模块

rootProject.name='zi application'include ':app'include ':zi-annotation'include ':zi-api'include ':zi-compiler'
处理依赖关系build.gradle(zi-compiler)
apply plugin: 'java-library'dependencies {    implementation fileTree(dir: 'libs', include: ['*.jar'])    implementation 'com.google.auto.service:auto-service:1.0-rc2'    implementation 'com.squareup:javapoet:1.10.0'    implementation project(':zi-annotation')}sourceCompatibility = "7"targetCompatibility = "7"
注意 zi-compiler 依赖于 zi-annotation 模块

implementation 'com.google.auto.service:auto-service:1.0-rc2'implementation 'com.squareup:javapoet:1.10.0'

在主项目中添加刚刚创建好的几个模块,值得注意 zi-compiler 为 annotationProcessor

dependencies {    ...    implementation project(':zi-annotation')    implementation project(':zi-api')    annotationProcessor project(':zi-compiler')}

到此我们已经完成模块创建、添加以及他们之间依赖关系,剩下的工作就是为这些模块添加代码。

zi-annotation 模块创建 ZiBindView 注解

@Retention(RetentionPolicy.CLASS)@Target(ElementType.FIELD)public @interface ZiBindView {}

@Target说明了Annotation所修饰的对象范围

zi-compiler 模块

创建 ZiBindViewProcessor 和 ClassCreatorProxy

在代码中不涉及的注解这里就不解释了

@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。

注解处理器需要继承于 AbstractProcessor ,其中部分代码写法基本是固定的。

public class ZiBindViewProcessor extends AbstractProcessor {    @Override    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {        return false;    }}

getSupportedAnnotationTypes() 返回支持的注解类型

getSupportedSourceVersion() 返回支持的源码版本

    @Override    public Set<String> getSupportedAnnotationTypes() {        HashSet<String> supportTypes = new LinkedHashSet<>();        //获取类名        supportTypes.add(ZiBindView.class.getCanonicalName());        return supportTypes;    }

这里,getName()返回的是虚拟机里面的class的表示,而getCanonicalName()返回的是更容易理解的表示。其实对于大部分class来说这两个方法没有什么不同的。但是对于array或内部类来说是有区别的。

另外,类加载(虚拟机加载)的时候需要类的名字是getName。

    @Override    public SourceVersion getSupportedSourceVersion() {        return SourceVersion.latestSupported();    }
收集信息

谓信息收集,就是根据我们声明,得到对应 Element,然后注解进行收集所需的信息,这些信息用于后期生产对象

生产代理类(在编译时,将文本生产为类的生产代理类 ClassCreatorProxy

针对每一个都会生产一个注解类

初始化

public interface ProcessingEnvironment {    Elements getElementUtils();    Types getTypeUtils();    Filer getFiler();    Locale getLocale();    Messager getMessager();    Map<String, String> getOptions();    SourceVersion getSourceVersion();    }
ZiBindViewProcessor 完整代码
@SupportedSourceVersion(SourceVersion.RELEASE_7)@SupportedAnnotationTypes({"zidea.example.com.zi_compiler.ZiBindViewProcessor"})@AutoService(Processor.class)public class ZiBindViewProcessor extends AbstractProcessor {    //跟日志相关的辅助类    private Messager mMessager;    //跟元素相关的辅助类,帮助我们去获取一些元素相关的信息。    private Elements mElementUtils;    private Map<String,ClassCreatorProxy> mProxyMap = new HashMap<>();    //利用 ProcessingEnvironment 对象获取 Elements    @Override    public synchronized void init(ProcessingEnvironment processingEnvironment) {        super.init(processingEnvironment);        mMessager = processingEnvironment.getMessager();        mElementUtils = processingEnvironment.getElementUtils();    }    @Override    public Set<String> getSupportedAnnotationTypes() {        HashSet<String> supportTypes = new LinkedHashSet<>();        //获取类名        supportTypes.add(ZiBindView.class.getCanonicalName());        return supportTypes;    }    @Override    public SourceVersion getSupportedSourceVersion() {        return SourceVersion.latestSupported();    }    // 收集信息    //  所谓信息收集,就是根据我们声明,得到对应 Element,然后注解进行收集所需的信息,这些信息用于后期生产对象    // 生产代理类(在编译时,将文本生产为类的生产代理类 ClassCreatorProxy,针对每一个都会生产一个注解类    @Override    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {        //输出信息        mMessager.printMessage(Diagnostic.Kind.NOTE,"processing...");        //根据注解获取所需信息        //用来获取,注解所修饰的Element对象,getElementsAnnotatedWith 用于获取注解 ZiBindView 的 Element 对象        Set<? extends Element> elements =roundEnvironment.getElementsAnnotatedWith(ZiBindView.class);        //遍历说有在类中,添加了 ZiBindView 类        for (Element element : elements){            VariableElement variableElement = (VariableElement) element;            TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();            String fullClassName = classElement.getQualifiedName().toString();            ClassCreatorProxy proxy = mProxyMap.get(fullClassName);            if(proxy == null){                proxy = new ClassCreatorProxy(mElementUtils,classElement);                mProxyMap.put(fullClassName,proxy);            }            ZiBindView bindAnnotation = variableElement.getAnnotation(ZiBindView.class);            int id = bindAnnotation.value();            proxy.putElement(id,variableElement);        }        for(String key:mProxyMap.keySet()){            ClassCreatorProxy proxyInfo = mProxyMap.get(key);            JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(),proxyInfo.generateJavaCode2()).build();            try {                javaFile.writeTo(processingEnv.getFiler());            }catch (IOException e){                e.printStackTrace();            }        }        mMessager.printMessage(Diagnostic.Kind.NOTE,"process finish ...");        return true;    }}
        for(String key:mProxyMap.keySet()){            ClassCreatorProxy proxyInfo = mProxyMap.get(key);            JavaFile javaFile = JavaFile.builder(proxyInfo.getPackageName(),proxyInfo.generateJavaCode2()).build();            try {                javaFile.writeTo(processingEnv.getFiler());            }catch (IOException e){                e.printStackTrace();            }        }
返回用来创建新的类的 filerClassCreatorProxy 完整代码
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();        this.mPackageName = packageName;        this.mBindingClassName = className + "_ViewBinding";    }    public void putElement(int id, VariableElement element){        mVariableElementMap.put(id,element);    }    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();    }    private void generateMethods(StringBuilder builder) {        builder.append("public void bind(" + mTypeElement.getQualifiedName() + " host ) {\n");        for (int id : mVariableElementMap.keySet()) {            VariableElement element = mVariableElementMap.get(id);            String name = element.getSimpleName().toString();            String type = element.asType().toString();            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;    }//JavaPoet是提供于自动生成java文件的构建工具类框架,使用该框架可以方便根据我们所注解的内容在编译时进行代码构建。    public TypeSpec generateJavaCode2() {        TypeSpec bindingClass = TypeSpec.classBuilder(mBindingClassName)                .addModifiers(Modifier.PUBLIC)                .addMethod(generateMethods2())                .build();        return bindingClass;    }    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");        for (int id : mVariableElementMap.keySet()) {            VariableElement element = mVariableElementMap.get(id);            String name = element.getSimpleName().toString();            String type = element.asType().toString();            methodBuilder.addCode("host." + name + " = " + "(" + type + ")(((android.app.Activity)host).findViewById( " + id + "));");        }        return methodBuilder.build();    }    public String getPackageName(){        return mPackageName;    }}
zi-api 模块
public class ZiBindViewTools {    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();        }    }}
Class bindViewClass = Class.forName(clazz.getName() + "_ViewBinding");Method method = bindViewClass.getMethod("bind", activity.getClass());method.invoke(bindViewClass.newInstance(), activity);

我们通过

### 使用 Api 我们已经完成 APT 部分代码,那么如何使用我们创建好 ZiBindView 呢?具体使用方法和 butterKnife 基本一样。```javapublic class MainActivity extends AppCompatActivity {    @ZiBindView(value = R.id.main_activity_textView)    TextView textView;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        ZiBindViewTools.bind(this);        textView.setText("Zidea");    }}
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference

但是当我们运行时候可能还无法生存代码,这是因为使用 gradle 版本过高,需要我们自己手动创建,

首先将目录从 Android 切换到 Project 视图在zi-compiler目录下创建如图结构resources/META-INF/services文件目录然后在目录下添加一个问题file 即可,在 file中添加如下内容

也就是我们刚刚创建 ZiBindViewProcessor文件包名加文件名

标签: #android javapoet