Android ButterKnife 注解与依赖注入框架 – 编译时注解(二)

Android ButterKnife 注解与依赖注入框架 – 编译时注解(二)

原文:https://ke.qq.com/course/130901

创建模块

 

建立依赖

以下配置可能出错,切换新旧配置方法。参考:这里

新方法

apply plugin: 'java-library'

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    // 注解处理器要起作用必须进行注册。自动注册(类似于四大组件)
    api 'com.google.auto.service:auto-service:1.0-rc3'
    compile project(path: ':butterknife_annotations')
}

// 控制生成文件的代码格式,可以省略
tasks.withType(JavaCompile){
    options.encoding="UTF-8"
}

// ViewBinder.java
/**
 * 用于绑定一个 Activity。
 * ButterKnife.bind(this);
 * 使用 ButterKnife 时,绑定 Activity 的功能
 */
public interface ViewBinder<T> {
    public void bind(T target);
}
// activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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=".MainActivity">

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

/**
 * 绑定控件的注解
 * @BindView(R.id.btn) Button btn;
 *
 * 注解的作用目标,按钮等(局部变量)
 * @Target(ElementType.FIELD)
 *
 * 选择其他的没有问题,为了节省资源,提高APP运行效率这里选择只在源码阶段有效
 * @Retention(RetentionPolicy.SOURCE)
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface BindView {
    int value(); // @BindView(R.id.btn) 括号中的值
}

// ButterKnifeProcess.java
import com.google.auto.service.AutoService;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
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.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.JavaFileObject;

import app.zowneo.butterknife_annotations.BindView;

/*
 *
 * 这个类就是 APT,可以存在多个。
 * .java 源码编译成为 .class 时,处理 .java 中的注解信息。
 *
 * 要具备注解处理器的功能,这个类必须继承
 *
 * 注册注解处理器
 * @AutoService(Processor.class)
 *
 * 初始化的工作也可以使用注解的方式完成
 */

@AutoService(Processor.class)
public class ButterKnifeProcess extends AbstractProcessor {
    //所有的注解处理器都要完成初始化工作

    /*
     * 1. 告诉APT,我们需要处理那些注解,
     * 这里需要处理的是@BindView(R.id.btn) 中的@BindView。
     * @Override 这样的不需要处理
     * 设置我们支持的注解的类型,返回的类型是注解类型的名字
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();// 使用其他的集合也可以
        types.add(BindView.class.getCanonicalName());//注解类型的名字
        /*
         * 也可以监听系统的注解
         * types.add(Override.class.getCanonicalName());
         */
        return types;
    }

    /*
     * 2. 设置支持的 JDK 的版本。
     * latestSupported 最新的版本。
     * */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    /*
     * 3. 设置一个生成(自动生成文件)源文件的对象并初始化。
     * processingEnvironment 其实就是注解处理器的上下文环境。
     * processingEnvironment.getFiler() 该环境变量除了在下面的方法中出现,
     * 还在 public abstract class AbstractProcessor implements javax.annotation.processing.Processor {
     *     protected javax.annotation.processing.ProcessingEnvironment processingEnv;
     *     private boolean initialized;
     *     ...}
     * 父类中出现,如果使用的过程中成员变量找不到,就使用父类中的。
     * */
    Filer filer;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        filer = processingEnvironment.getFiler();
    }

    /**
     * 在这个方法中,完成注解处理器要完成的工作
     *
     * @param set
     * @param roundEnvironment
     * @return
     */

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        /*
         * roundEnvironment:
         * 能拿到我们要处理注解的对应的文件如 MainActivity 在其变成 class 文件的过程中
         * 对里面的注解信息进行扫描,将下面注解提供的信息,
         * 以结构化文档的形式进行保存,如TypeELement、VariableElement、ExecuteableElement
         *     @BindView(R.id.btn)
         *     Button btn;
         *
         * 结构化文档:层次分明(java、html)
         * public class Snake {		    // TypeELement
         *     private int a;			// VariableElement
         *     private Snake other;	    // VariableElement
         *     public Snake() {}		// ExecuteableElement
         *     public void method() {}  // ExecuteableElement
         * }
         * */
        /*
         * 获取和 BindView 相关的信息。
         * 例如有十个按钮,就会保存十个这样的元素
         * (Activity(2个),Activity2(3个),Activity3(5个)
         * elements 是TypeELement等的父类,不区分类型,
         * 每个按钮存在哪一个 Activity 中,需要我们自己进行处理区分。
         * 使用一个Map:key 保存 Activity,value 保存每个按钮。
         */
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
        // 分类 key == 类名         value == 成员变量的集合
        Map<String, List<VariableElement>> cacheMap = new HashMap<>();
        // 开始分类
        for (Element element : elements) {
            VariableElement variableElement = (VariableElement) element;
            // key 保存的形式 app.zowneo.mybutterknife.MainActivity
            String activityName = getActivityName(variableElement);
            // 存放 key (Activity)的集合
            List<VariableElement> list = cacheMap.get(activityName);
            // 第一次获取时,里面是空的
            if (list == null) {
                list = new ArrayList<>();
                cacheMap.put(activityName, list);
            }
            list.add(variableElement);// 这里面放的就是成员变量
        }

        // 分类完成开始生成文件
        Iterator<String> iterator = cacheMap.keySet().iterator();
        while (iterator.hasNext()) {
            // 获取文件名(含包名)app.zowneo.mybutterknife.MainActivity
            String activity = iterator.next();
            // 文件中的成员变量,这里指的是 Button 等
            List<VariableElement> cacheElements = cacheMap.get(activity);
            String packageName = getPackageName(cacheElements.get(0));
            // 生成文件名 MainActivity_ViewBinder
            String newActivityBinder = activity + "_ViewBinder";
            String activitySimpleName = cacheElements.get(0).getEnclosingElement()
                    .getSimpleName().toString() + "_ViewBinder";
            // 生成文件
            Writer writer = null;
            try {
                // 默认生成为文件路径在 apt 路径下
                JavaFileObject sourceFile = filer.createSourceFile(newActivityBinder);
                writer = sourceFile.openWriter();// 打开文件流,开始写文件
                /*
                 * package app.zowneo.mybutterknife;
                 * import app.zowneo.mybutterknife.ViewBinder;
                 * public final class MainActivity_ViewBinder implements ViewBinder{
                 *     public void bind(app.zowneo.mybutterknife.MainActivity target) {
                 *         target.btn = (android.widget.Button)target.findViewById(2131165218);
                 *     }
                 *}
                 * */
                writer.write("package " + packageName + ';');
                writer.write("\n");
                writer.write("import " + packageName + ".ViewBinder;");
                writer.write("\n");
                writer.write("public final class " + activitySimpleName
                        + " implements " + "ViewBinder<" + activity + ">{");
                writer.write("\n");
                writer.write("    public void bind(" + activity + " target) {\n");
                // 可能有多个控件,需要使用循环来写
                for (VariableElement variableElement : cacheElements) {
                    BindView bindView = variableElement.getAnnotation(BindView.class);
                    // 获取成员变量名
                    String fieldName = variableElement.getSimpleName().toString();
                    // 获取成员变量的类型
                    TypeMirror typeMirror = variableElement.asType();
                    // 写入
                    writer.write("        target." + fieldName + " = (" + typeMirror.toString()
                            + ")target.findViewById(" + bindView.value() + ");");
                    writer.write("\n");
                }
                writer.write("    }\n}");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (writer != null) {
                    try {
                        writer.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        return false;
    }


    /*
     * 得到包名
     * */
    private String getPackageName(VariableElement variableElement) {
        /*
         * 得到被包裹元素的上一级元素,这里是如
         * @BindView(R.id.btn)
         * Button btn;
         * 的上一级元素,也就是类元素 MainActivity。
         * 更上一级的元素,也就是包元素。
         * */
        TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
        String packageName = processingEnv.getElementUtils().getPackageOf(typeElement)
                .toString();
        // app.zowneo.mybutterknife
        return packageName;
    }

    /*
     * 得到类名
     * */
    private String getActivityName(VariableElement variableElement) {
        String packageName = getPackageName(variableElement);
        TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
        String className = typeElement.getSimpleName().toString();
        // app.zowneo.mybutterknife.MainActivity
        return packageName + '.' + className;
    }
}

// ButterKnifer.java
/*
 * 给用户的接口
 * */
public class ButterKnifer {
    public static void bind(Activity activity) {
        String className = activity.getClass().getName() + "_ViewBinder";
        // 不能使用 new 的方式,因为这个文件还没有生成
        try {
            Class<?> viewBinderClass = Class.forName(className);
            ViewBinder viewBinder = (ViewBinder) viewBinderClass.newInstance();
            // 实际上是我们生成的实现类,完成的绑定
            viewBinder.bind(activity);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

发表评论

zh_CNChinese
zh_CNChinese