Android 学习(四):Android APT/Android 编译时技术

上一篇 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>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容