Android 架构师之路 APT (1)

作者 : 开心源码 本文共9490个字,预计阅读时间需要24分钟 发布时间: 2022-05-13 共148人阅读

Android-Developers.jpg

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

APT

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

  • 目标使用 APT 实现相似 Butter Knife 库提供的通过注解实现视图绑定的功能
    最近使用 binding 实现视图绑定功能。有点渐渐远离了 ButterKnife ,不过这个库还是给我留下美好的回忆,简洁明了的 API 让人印象深刻。

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

创立模块

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

    apt_create_java_module.JPG

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

    apt_create_android_module.JPG

  • 创立 zi-compiler(java 模块) 编写注解解决 。
No.模块名模块类型说明
1zi-annotationjava 模块在这个模块中,可以创立注解
2zi-apiAndroid 模块提供客户可以调用 api
3zi-compilerjava 模块编写注解解决

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')}

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

project_architeure.JPG

zi-annotation 模块

创立 ZiBindView 注解

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

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

说明
CONSTRUCTOR用于形容构造器
FIELD用于形容域
LOCAL_VARIABLE用于形容局部变量
METHOD用于形容方法
PACKAGE用于形容包
PARAMETER用于形容参数
TYPE用于形容类、接口(包括注解类型) 或者enum公告

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();            }        }
  • 返回用来创立新的类的 filer

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();        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中增加如下内容
    001.JPG
    也就是我们刚刚创立 ZiBindViewProcessor文件包名加文件名
    002.JPG
说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » Android 架构师之路 APT (1)

发表回复