使用注解处理器(Annotation Processor)像butterKnife一样,实现简单的控件初始化

时间:2022-09-10 20:36:20

现在很多Android的库都是采用注解的方式,来完成某些功能。

今天,我们就仿照butterKnife,使用注解来完成一个简单的控件初始化。完成这个功能我们就需要知道一个东西,注解处理器

什么是注解处理器?

注解处理器是一个在javac中的,用来编译时扫描和处理的注解的工具。我们可以用自己定义的注解和注册注解处理器来做一些事情。

这里我们就需要自定义一个处理器,用来处理i注解并生成一个java文件,让这个文件跟其他普通手写的java文件一样被javac编译。

抽象处理器 AbstractProcessor

我们自定义的处理器需要继承这个抽象处理器。
一般我们会用到它的四个方法。

  • init(ProcessingEnvironment processingEnvironment)
    里面提供了Filer等工具类。注解处理器可以用Filer类创建新文件(源文件、类文件、辅助资源文件)。由此方法创建的源文件和类文件将由管理它们的工具(javac)处理。

  • getSupportedSourceVersion()
    支持JDK的版本,通常返回SourceVersion.latestSupported()。

  • public Set getSupportedAnnotationTypes()
    注解处理器是注册给哪些注解使用的。

  • public boolean process(annotationsannotations, RoundEnvironment roundEnv)

    核心方法,在这里你可以扫描和处理注解,并生成java文件。


实现的思路:

  • 1,定义自定义的注解annotation
    用来给控件添加注解。

    @XXBindView(R.id.xx) Button mBtn;
  • 2,定义一个ViewBinder接口
    用来让,我们自定义的注解处理器生成的类 来实现这个接口。

    public interface XXViewBinder<T>{
        void bind(T target);
    }
  • 3,定义一个自定义processor注解处理器。
    用来写一个activity的内部类,并实现ViewBinder的接口。并在bind方法里面实现控件的初始化
    生成的样子类似下面

    public void XXActivity$ViewBinder implements XXViewBinder<XXActivity>{
        public void bind(XXActivity target){
            target.mBtn=(Button).findViewById(R.id.xx);
    
        }
    }   
    
  • 4,定义一个SimpleButterKnife绑定activity。
    反射processor写入的内部类XXActivity$ViewBinder,并调用它的bind方法,并把activity传进去,实现初始化。

    public class SimpleButterKnife{
        public static void bind(Activity activity){
            //反射获取XXActivity$ViewBinder内部类
            //调用XXViewBinder.bind(activity)方法,把activity传进去
            //这样就会调用上面写的activity内部类的bind方法,完成初始化
        }
    }
    
    ```  
    

使用注解处理器(Annotation Processor)像butterKnife一样,实现简单的控件初始化

流程:跟ButterKnife一样,在Activity的里调用SimpleButterKinife.bind(activity)。
在bind()方法里,反射调用我们自定义processor注解处理器写入的内部类(XXActivity$ViewBinder)的bind()方法,并把activity传进去,来完成控件的初始化。


开始编写。

定义两个java library的module(不用android library)
一个是存放注解的module。(simple-butterknife-annotation)
一个是解析注解的module。(simple-butterknife-compiler)

添加依赖:

app module 添加:

    implementation project(':simple-butterknife-annotation')
    annotationProcessor project(':simple-butterknife-compiler')

simple-butterknife-compiler 添加:

    implementation project(':simple-butterknife-annotation')

编写注解文件(绑定控件):

/** * 只处理绑定控件,只存在源码期 */

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface SimpleBindView {
    int value();
}

创建ViewBinder(最后要构造成activity的内部类)

public interface SimpleViewBinder<T> {
    void bind(T target);
}

创建我们自己的处理器

@AutoService(Processor.class)
public class SimpleButterKnifeProcessor extends AbstractProcessor {
    /** * 用于生成java文件 */
    private Filer filer;

    /** * 被注解处理工具调用. */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        //提供了Element,Filer,Messager等工具
        filer = processingEnv.getFiler();
    }

    /** * 支持的JDK版本 */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    /** * 确认我们处理的注解 */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new HashSet<>();
        types.add(SimpleBindView.class.getCanonicalName());
        return types;
    }

    /** * 核心类,在每个activity生成内部类,并初始化控件 */
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        //获取所有使用SimpleBindView的集合
        Set<? extends Element> elementSet = roundEnv.getElementsAnnotatedWith(SimpleBindView.class);
        //key是activity的名字
        //value是这个activity里面所有使用SimpleBindView注解的集合
        Map<String, List<VariableElement>> elementMap = handleElementIntoMap(elementSet);

        //开始准备写XXActivity$ViewBinder内部类的java文件
        Iterator<String> iterator = elementMap.keySet().iterator();
        while (iterator.hasNext()) {
            String activityName = iterator.next();
            //获取activity的所有元素
            List<VariableElement> variableElements = elementMap.get(activityName);
            //获取包名
            String packageName = getPackageName(variableElements.get(0));
            //获取我们要写的内部类java文件名
            String newActivityName = activityName + "$" + getInnerClassName();

            //用流来写内部类的文件
            createInnerClassFile(activityName, packageName, newActivityName, variableElements);
        }

        return false;
    }

    /** * 按照格式写内部类。 * package xxx; * import xxx.SimpleViewBinder; * public class XXXActivity$SimpleViewBinder<T extends XXXActivity> implements SimpleViewBinder<T> { * public void bind(XXXActivity target){ * //初始化控件 * target.xxx = (xxx)target.findViewById(id); * ... * } * } */
    private void createInnerClassFile(String activityName, String packageName, String newActivityName, List<VariableElement> variableElements) {
        Writer writer;
        try {
            String simpleActivityName = variableElements.get(0).getEnclosingElement().getSimpleName().toString()+"$"+getInnerClassName();
            JavaFileObject sourceFile = filer.createSourceFile(newActivityName, variableElements.get(0).getEnclosingElement());
            writer = sourceFile.openWriter();
            writer.write("package " + packageName + " ;");
            writer.write("\n");
            writer.write("import com.liu.simple_butterknife_annotation." + getInnerClassName() + " ;");
            writer.write("\n");
            writer.write("public class " + simpleActivityName + " implements " + getInnerClassName() + "<" + activityName + ">{");
            writer.write("\n");
            writer.write("public void bind(" + activityName + " target){");
            writer.write("\n");

            //初始化控件
            for (VariableElement variableElement : variableElements) {
                SimpleBindView simpleBindView = variableElement.getAnnotation(SimpleBindView.class);
                //获取控件的名字
                String fileName = variableElement.getSimpleName().toString();
                //获取控件的类型(Button,TextView)
                TypeMirror typeMirror = variableElement.asType();
                writer.write("target."+fileName+" = ("+typeMirror+")target.findViewById("+simpleBindView.value()+");");
                writer.write("\n");
            }


            writer.write("}");
            writer.write("\n");
            writer.write("}");
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /** * 内部类的名字 */
    private String getInnerClassName() {
        return "SimpleViewBinder";
    }

    /** * 把所有的Element存放到map里 */
    private Map<String, List<VariableElement>> handleElementIntoMap(Set<? extends Element> elementSet) {
        Map<String, List<VariableElement>> elementMap = new HashMap<>();
        for (Element element : elementSet) {
            VariableElement variableElement = (VariableElement) element;
            //获取activity名字
            String activityName = getActivityName(variableElement);
            //通过activity名字,获取集合
            List<VariableElement> elementList = elementMap.get(activityName);
            if (elementList == null) {
                elementList = new ArrayList<>();
                elementMap.put(activityName, elementList);
            }
            elementList.add(variableElement);
        }
        return elementMap;
    }

    /** * 通过VariableElement获取包名 */
    private String getPackageName(VariableElement element) {
        TypeElement typeElement = (TypeElement) element.getEnclosingElement();
        return processingEnv.getElementUtils().getPackageOf(typeElement).getQualifiedName().toString();
    }

    /** * 通过VariableElement获取所在的activity名字 */
    private String getActivityName(VariableElement element) {
        String packageName = getPackageName(element);
        TypeElement typeElement = (TypeElement) element.getEnclosingElement();
        return packageName + "." + typeElement.getSimpleName().toString();
    }
}

注册注解处理器

使用google提供的注册注解处理器
1.在compiler module 的build.gradle下添加

api 'com.google.auto.service:auto-service:1.0-rc3'

2.在compiler module 的build.gradle下添加

//防止出现“编码GBK的不可映射字符”
tasks.withType(JavaCompile){
    options.encoding = "UTF-8"
}

3.在proccessor添加注解

@AutoService(Processor.class)
public class SimpleButterKnifeProcessor extends AbstractProcessor {

创建类来绑定activity

public class SimpleButterKnife {
    public static void bind(Activity activity) {
        //获取编写的内部类
        String innerClassName = activity.getClass().getName() + "$SimpleViewBinder";
        try {
            Class<?> aClass = Class.forName(innerClassName);
            SimpleViewBinder viewBinder = (SimpleViewBinder) aClass.newInstance();
            //绑定activity
            viewBinder.bind(activity);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }
    }
}

Demo地址

https://github.com/ecliujianbo/SimpleButterKnife