美文网首页Android APTAndroid Other
Android 学习(四):Android APT/Androi

Android 学习(四):Android APT/Androi

作者: 翟小乙 | 来源:发表于2021-09-27 17:39 被阅读0次

    上一篇 Android 学习(三):Java 注解

    Android APT学习

    1. 编译时技术作用生成模板代码
    2. 什么是编译时技术?


      6.png
    1.0 学习目标
    • 模仿Databing findViewById() 功能,对APT 注解有一个简单实用认识
    2.0 重点知识点
    • 谷歌注解处理器库 annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
    • 谷歌注解处理器库 compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
    • 抽象类 AbstractProcessor ,该类属于Java,所以建立module创建JavaLibrary
    • 注解处理器增加注解 @AutoService(Processor.class)
    3.0 上代码学习
    1. 创建android项目
    2. 项目内创建注解的module,选择javalibrary ,名称为annotations
      注解Moudle.jpg
    3. 项目内创建注解处理器的module,选择 javalibrary,名称为annotations_compiler
      注解处理器Module.jpg
    4. 在项目/app/build.gradle下引入这个俩个moudle

    正常引入module都为implementation因为annotations_compiler注解处理器所以改为 annotationProcessor

    引入Module.jpg
    1. 在Module annotations注解中创建BindView 注解,用来传递控件ID(R.id.xxx)
      BindView.jpg
    package com.zyj.annotations;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    /**
     * 定义注解
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.CLASS)
    public @interface BindView {
        int value();
    }
    
    1. 在Module annotations_compiler注解处理器 中创建AnnotationsCompiler,解析各个界面注解信息
      z5.jpg
    • 思路
    1. 初始化Filer ,作用:编译时获取到注解控件ID 写入文件就是把findViewById(R.id.xxx)写入文件。
    2. 设置注解处理器处理范围,只解读BindView 注解。
    3. 以下是process方法里面思路:
    4. 获取使用注解的所有Activity的节点。
      Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class)
    5. 把所有节点进行分类,Activity和其成员变量归为一类,存入map中。
      VariableElement Field 成员变量 、TypeElement class 类 、 PackageElement 包 、 等可查看Element 子类
    6. 分好类,存入map后,通过filer创建java文件,生成代码。
      **重点看注释
    package com.zyj.anntotations_compiler;
    
    import com.google.auto.service.AutoService;
    import com.zyj.annotations.BindView;
    
    import java.io.IOException;
    import java.io.Writer;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Set;
    
    import javax.annotation.processing.AbstractProcessor;
    import javax.annotation.processing.Filer;
    import javax.annotation.processing.Messager;
    import javax.annotation.processing.ProcessingEnvironment;
    import javax.annotation.processing.Processor;
    import javax.annotation.processing.RoundEnvironment;
    import javax.lang.model.SourceVersion;
    import javax.lang.model.element.Element;
    import javax.lang.model.element.PackageElement;
    import javax.lang.model.element.TypeElement;
    import javax.lang.model.element.VariableElement;
    import javax.tools.Diagnostic;
    import javax.tools.JavaFileObject;
    
    /**
     * 注解处理器   用来生成代码
     * <p>
     * 1. 注解处理器一定要继承一个抽象类  AbstractProcessor (AbstractProcessor 属于javax)
     * 2。需要依赖于谷歌服务库(这样注解就会在这边自动处理)
     * annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
     * compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
     * 3.依赖使用注解 @AutoService(Processor.class) 代表这个类就是一个注解处理器的类
     */
    @AutoService(Processor.class)
    public class AnnotationsCompiler extends AbstractProcessor  {
        //  注意: 我们调试的时候需要打断点或者打印日志,而在处理器里面不起作用,所以我们通过 Messager去操作
        Filer filer;
        Messager messager;
    
        @Override
        public synchronized void init(ProcessingEnvironment processingEnvironment) {
            super.init(processingEnvironment);
    
            filer = processingEnvironment.getFiler();// 文件
            messager = processingEnvironment.getMessager();// 日志
            messager.printMessage(Diagnostic.Kind.WARNING, "我们开始可以看日志了!日志类型是警告!");
    
        }
    
        /**
         * 因为我们注解处理器不需要都要去处理,
         * 所以这个方法是声明注解处理器要处理的注解
         *
         * @return
         */
        @Override
        public Set<String> getSupportedAnnotationTypes() {
            Set<String> types = new HashSet<>();
            // 1. String 类型里面可以添加包名类名,就能去筛选了
            // 2. 注解处理器模块需要依赖注解模块
            // 3. 这就说明这个注解处理器要处理的注解就是我们声明的这个注解BindView
            types.add(BindView.class.getCanonicalName());
            return types;
        }
        /**
         * 1. 方法一 :实现该方法
         * 2. 方法二:在该类处理器加注解  @SupportedSourceVersion()
         * 必须要有一个版本声明
         * 声明支持的java 版本
         *
         * @return
         */
        @Override
        public SourceVersion getSupportedSourceVersion() {
            return processingEnv.getSourceVersion();
        }
    
        // 该方法专门用来搜索注解的方法
        @Override
        public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
            //  生成代码
            // 去搜索出用到了BindView注解的节点
    
            // 如果有多个Activity 则会有多个Element 元素,可通过上下文 activity.findViewById()获取节点元素
            // VariableElement Field 成员变量    TypeElement class 类     PackageElement 包   等等都是节点
            Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);// 根据注解获取节点
            // 把每个Activity 和它里面的内容放到一起
            HashMap<String, List<VariableElement>> map = new HashMap<>();
            for (Element element : elements) {
                VariableElement variableElement = (VariableElement) element;
                // 获取这个成员变量所有在的类的类名
                TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();//获取成员变量的上一个节点===》就是这个类
                typeElement.getQualifiedName().toString();// 获取类名--- 带包名
                String className = typeElement.getSimpleName().toString();// 获取类名----不带包名
                List<VariableElement> variableElements = map.get(className);// 通过类名 获取 成员变量
                if (variableElements == null) {
                    variableElements = new ArrayList<>();
                    map.put(className, variableElements);
                }
                variableElements.add(variableElement);
            }
            // 生成代码
            if (map.size()>0){
                Writer writer = null;
                Iterator<String> iterator = map.keySet().iterator();
                while (iterator.hasNext()){
                    String className = iterator.next();
                    List<VariableElement> variableElements = map.get(className);
                    // 获取包名
                    String packName = getPackName(variableElements.get(0));
                    // 创建一个类名
                    String newName = className+"_ViewBinder";
                    try {
                        // 创建 java 文件
                        JavaFileObject sourceFile = filer.createSourceFile(packName + "." + newName);
                        writer = sourceFile.openWriter();
                        StringBuffer stringBuffer = new StringBuffer();
                        stringBuffer.append("package "+ packName+";\n");
                        stringBuffer.append("import android.view.View ;\n");
                        stringBuffer.append("public class "+ newName +" implements IButterKnifer<"+packName+"."+className+">{\n");
                        stringBuffer.append("public void bind("+packName+"."+className+" target){\n");
                        for (VariableElement variableElement :variableElements) {
                            // 遍历成员变量,每一个变量都生成findViewById代码
                            // 获取成员变量名字
                            String variableName = variableElement.getSimpleName().toString();
                            //  获取到上面的注解所持有的value redID
                            int resID = variableElement.getAnnotation(BindView.class).value();
                            stringBuffer.append("target."+variableName+"=target.findViewById("+resID+");\n");
                        }
                        stringBuffer.append("}\n}\n");
                        writer.write(stringBuffer.toString());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }finally {
                        try {
                            if (writer!=null){
                                writer.close();
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
            return false;
        }
    
        /**
         * 根据成员变量获取包名
         * @param variableElement
         * @return
         */
        private String getPackName(VariableElement variableElement) {
            Element enclosingElement = variableElement.getEnclosingElement();// 获取上一级元素
            PackageElement packageOf = processingEnv.getElementUtils().getPackageOf(enclosingElement);// 获取包节点
    
            String  packName = packageOf.getQualifiedName().toString();// 获取到包名
    
             return packName;
    
    
        }
    }
    
    
    1. annotations_compiler注解处理器引入注解module和谷歌库

    因为处理器annotations_compiler需要解读注解annotations,所以需要引入module,而引入谷歌库就是为了自动调用该注解处理器类


    引入库.jpg
    1. 在项目app中新建包为apt,在其中创建IButterKnifer接口,AptActivity和IButterKnife类
      apt包.jpg
    • IButterKnifer接口
    
    public interface IButterKnifer<T> {
        void bind(T target);
    }
    
    • IButterKnife类

    传递上下文获取当前Activity内控件ID

    
    public class IButterKnife {
        public static void bind(Object target){
            String name = target.getClass().getName()+"_ViewBinder";;
            try {
                Class<?> aClass = Class.forName(name);
                if (IButterKnifer.class.isAssignableFrom(aClass)){
                    // 判断 aClass 是不是 IButterKnifer类或子类
                    IButterKnifer iButterKnifer = (IButterKnifer) aClass.newInstance();
                    iButterKnifer.bind(target);
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }
    }
    
    • AptActivity
    import android.os.Bundle;
    import android.widget.TextView;
    import com.zyj.annotations.BindView;
    import com.zyj.obslove.R;
    public class AptActivity extends AppCompatActivity {
        @BindView(R.id.text1)
        TextView text1;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_apt);
            IButterKnife.bind(this);
            text1.setText("---------------------");
        }
    }
    
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".apt.AptActivity">
        <TextView
            android:id="@+id/text1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="text1"/>
    </LinearLayout>
    

    相关文章

      网友评论

        本文标题:Android 学习(四):Android APT/Androi

        本文链接:https://www.haomeiwen.com/subject/apszgltx.html