编译期注解学习四 简单的view注入框架

1 项目结构

本篇文章比较简单,如果熟悉apt的同学就不用看了,只是作为入门级功能。
经过前三篇的讲解,今天做一个简易版的View注入框架,功能类似黄油刀ButterKnife中的一小部分功能,Activity中view通过注解获取实例。
项目结构:
编译期注解学习四 简单的view注入框架_第1张图片
processor_lib是一个java lib 项目,实现processor。
annotationlib 注解。
injectlib 供用户调用的工具。
app 使用编译期注解,完成view自动注入。

2 功能实现

2.1 定义注解

编译期注解学习四 简单的view注入框架_第2张图片
编译期注解学习四 简单的view注入框架_第3张图片

2.2 processor_lib

编译期注解学习四 简单的view注入框架_第4张图片

import com.ldx.annotationlib.BindView;

import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Map;
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.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedOptions;
import javax.annotation.processing.SupportedSourceVersion;
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.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.JavaFileObject;

@SupportedOptions({"CLASSNAME"})
@SupportedAnnotationTypes("com.ldx.annotationlib.BindView")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class AptProcessor extends AbstractProcessor  {

    private Filer mFilerUtils;
    private Types mTypesUtils;
    private Elements mElementsUtils;
    private Messager mMessager;
    private Map<String,String> mOptionMap;
    private ArrayList<VariableElement> mElementList;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mFilerUtils = processingEnv.getFiler(); //生成java文件工具
        mTypesUtils = processingEnv.getTypeUtils(); //type 工具
        mElementsUtils = processingEnv.getElementUtils(); //element 工具,获取包名等
        mMessager = processingEnv.getMessager(); //打印log,rebuild 项目可见
        mOptionMap = processingEnv.getOptions(); //获取定义常量
        mElementList = new ArrayList<>(); //element 集合,存放一个activity中所有的注解元素集合
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (set != null && set.size() > 0){
            //获取所有使用了BindView的元素,为了简单此处只适用于一个activity,如果要应用于多个activity,需要根据获取的elements,进行分类,此处为了简单只用于一个activity,也就不需要利用hashMap进行分类了。
            Set<? extends Element>  elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
            for (Element element : elements) {
            //由于作用于field ,直接强转为VariableElement
                VariableElement variableElement = (VariableElement) element;
                mElementList.add(variableElement);
            }
			//生成文件
            createFile(mElementList);

            return true;
        }
        return false;
    }

    private void createFile(ArrayList<VariableElement> mElementList) {
    	//getEnclosingElement 获取parent,此处也就是activity
        TypeElement enclosingElement = (TypeElement) mElementList.get(0).getEnclosingElement();
        //全包名
        String pkName = mElementsUtils.getPackageOf(enclosingElement).getQualifiedName().toString();
        String packName = mElementsUtils.getPackageOf(enclosingElement).asType().toString();
        //文件名
        String className = enclosingElement.getSimpleName().toString();

        try {
            JavaFileObject jfo = mFilerUtils.createSourceFile(pkName + "."+className+ "$ViewBinding", new Element[]{});
            Writer writer = jfo.openWriter();
            writer.write(brewCode(className,pkName, mElementList));
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //拼接java文件代码
    private String brewCode(String className, String pkName, ArrayList<VariableElement> mElementList) {
        StringBuilder builder = new StringBuilder();
        builder.append("package " + pkName + ";\n\n");
        builder.append("public class " + className + "$ViewBinding implements com.ldx.injectlib.InjectIoc { \n\n");
        builder.append("@Override\n\n");
        builder.append("public void inject("+"Object"+" obj1"+"){ \n\n");
        builder.append("        "+pkName+"."+className+" obj" +" = "+"("+pkName+"."+className+")"+"obj1;\n\n");
        for (VariableElement element : mElementList){
            //3.获取注解的成员变量名
            String bindViewFiledName = element.getSimpleName().toString();
            //变量类型
            String bindViewFiledClassType = element.asType().toString();
            BindView bindAnnotation = element.getAnnotation(BindView.class);
            int id = bindAnnotation.value();
            String info = String.format("%s %s = findViewById(%d)", bindViewFiledClassType, bindViewFiledName, id);
            builder.append("        System.out.println(\"" + info + "\");\n\n");
            builder.append("        obj."+bindViewFiledName+" = "+"obj.findViewById("+id+");"+"\n\n");
        }

        builder.append("}\n\n");
        builder.append("}");
        return builder.toString();
    }

}

注册:
编译期注解学习四 简单的view注入框架_第5张图片

2.3 用户使用接口

编译期注解学习四 简单的view注入框架_第6张图片


public interface InjectIoc {
    void inject(Object obj);
}

反射调用:

import android.content.Context;

public class ViewInject {

    public static void inject(Context activity){
        try {
            Class<?> clazz = activity.getClass();
            String proxyClassFullName = clazz.getName()+"$ViewBinding";
            Class<?> proxyClazz = Class.forName(proxyClassFullName);
            InjectIoc injectIoc = (com.ldx.injectlib.InjectIoc)proxyClazz.newInstance();
            injectIoc.inject(activity);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

3 使用

添加依赖:
编译期注解学习四 简单的view注入框架_第7张图片

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

import com.ldx.annotationlib.BindView;
import com.ldx.canvasdrawdemo.R;
import com.ldx.injectlib.ViewInject;

public class BindViewActivity extends AppCompatActivity {
    @BindView(R.id.tv1)
    public TextView textView1;
    @BindView(R.id.tv2)
    public TextView textView2;
    @BindView(R.id.tv3)
    public TextView textView3;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_bind_view);
        ViewInject.inject(BindViewActivity.this);
        if (textView1 != null){
            System.out.println("=================="+textView1.toString());
            textView1.setText("apt 设置的Text");
        }else{
            System.out.println("==================没有绑定成功");
        }

    }
}

//布局文件:
<?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"
    android:orientation="vertical"
    tools:context=".activity.BindViewActivity">
    <TextView
        android:id="@+id/tv1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="text1"/>
    <TextView
        android:id="@+id/tv2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="text2"/>
    <TextView
        android:id="@+id/tv3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="text3"/>

</LinearLayout>

结果:
编译期注解学习四 简单的view注入框架_第8张图片

//生成的文件
package com.ldx.canvasdrawdemo.activity;

public class BindViewActivity$ViewBinding implements com.ldx.injectlib.InjectIoc { 

@Override

public void inject(Object obj1){ 

        com.ldx.canvasdrawdemo.activity.BindViewActivity obj = (com.ldx.canvasdrawdemo.activity.BindViewActivity)obj1;

        System.out.println("android.widget.TextView textView1 = findViewById(2131231154)");

        obj.textView1 = obj.findViewById(2131231154);

        System.out.println("android.widget.TextView textView2 = findViewById(2131231155)");

        obj.textView2 = obj.findViewById(2131231155);

        System.out.println("android.widget.TextView textView3 = findViewById(2131231156)");

        obj.textView3 = obj.findViewById(2131231156);

}

}

编译期注解学习四 简单的view注入框架_第9张图片
在这里插入图片描述

4 支持多个activity的思路

@SupportedOptions({"CLASSNAME"})
@SupportedAnnotationTypes("com.ldx.annotationlib.BindView")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class AptProcessor3 extends AbstractProcessor  {

    private Filer mFilerUtils;
    private Types mTypesUtils;
    private Elements mElementsUtils;
    private Messager mMessager;
    private Map<String,String> mOptionMap;
    private HashMap<TypeElement,ArrayList<VariableElement>> mEleMap = new HashMap<>();

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mFilerUtils = processingEnv.getFiler();
        mTypesUtils = processingEnv.getTypeUtils();
        mElementsUtils = processingEnv.getElementUtils();
        mMessager = processingEnv.getMessager();
        mOptionMap = processingEnv.getOptions();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        if (set != null && set.size() > 0){

            Set<? extends Element>  elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
            for (Element element : elements) {
                VariableElement variableElement = (VariableElement) element;
                TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
                if (mEleMap.containsKey(typeElement)){
                    mEleMap.get(typeElement).add(variableElement);
                }else{
                    ArrayList<VariableElement> list = new ArrayList<VariableElement>();
                    list.add(variableElement);
                    mEleMap.put(typeElement, list);
                }
            }

            createFile();

            return true;
        }
        return false;
    }

    private void createFile() {
        for (Map.Entry<TypeElement,ArrayList<VariableElement>> entry : mEleMap.entrySet()) {
            TypeElement typeElement = entry.getKey();
            ArrayList<VariableElement> list = entry.getValue();
            TypeElement enclosingElement = typeElement;
            String pkName = mElementsUtils.getPackageOf(enclosingElement).getQualifiedName().toString();

            String className = enclosingElement.getSimpleName().toString();
            try {
                JavaFileObject jfo = mFilerUtils.createSourceFile(pkName + "."+className+ "$ViewBinding", new Element[]{});
                Writer writer = jfo.openWriter();
                writer.write(brewCode(className,pkName, list));
                writer.flush();
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        
    }

    private String brewCode(String className, String pkName, ArrayList<VariableElement> mElementList) {
        StringBuilder builder = new StringBuilder();
        builder.append("package " + pkName + ";\n\n");
        builder.append("public class " + className + "$ViewBinding implements com.ldx.injectlib.InjectIoc { \n\n");
        builder.append("@Override\n\n");
        builder.append("public void inject("+"Object"+" obj1"+"){ \n\n");
        builder.append("        "+pkName+"."+className+" obj" +" = "+"("+pkName+"."+className+")"+"obj1;\n\n");
        for (VariableElement element : mElementList){
            //3.获取注解的成员变量名
            String bindViewFiledName = element.getSimpleName().toString();
            //变量类型
            String bindViewFiledClassType = element.asType().toString();
            BindView bindAnnotation = element.getAnnotation(BindView.class);
            int id = bindAnnotation.value();
            String info = String.format("%s %s = findViewById(%d)", bindViewFiledClassType, bindViewFiledName, id);
            builder.append("        System.out.println(\"" + info + "\");\n\n");
            builder.append("        obj."+bindViewFiledName+" = "+"obj.findViewById("+id+");"+"\n\n");
        }

        builder.append("}\n\n");
        builder.append("}");
        return builder.toString();
    }

}

编译时注解学习一之 Element元素
编译时注解学习二之 注解处理器初探AbstractProcessor
编译时注解学习三之 注解处理器AbstractProcessor工具和Element属性简述
编译期注解学习四 简单的view注入框架
编译期注解学习五 - ElementKind,TypeKind,不同Element类型判断
编译期注解学习六- 生成java文件javapoet
编译期注解学习七-如何进行调试
编译时注解学习八 -模板文件读取

你可能感兴趣的:(android,工具类,编译期注解学习,apt,processor,view,注入,入门)