编译时技术二、使用APT编译时手写ButterKnife

一、原理简介

ButterKnife框架原理的是采用APT编译时技术,主要运用到注解+注解处理器的方式动态地为添加了BindView等注解的成员或方法生成类文件,开发者无需自己手写findViewById等等重复的代码,简化了开发者的工作量。

二、手写ButterKnife

想要完全理解ButterKnife底层的APT技术,手写实现ButterKnife可以帮助更好地吸收这种技术。

2.1准备工作

(1)创建Android工程,并且在此项目中新建一个java Module取名为annotataion,用于存放注解。
注意:必须新建java library而不能是Android lib,为什么只能新建java工程,不能是Android工程后面讲
在这里插入图片描述
(2)创建第二个java library取名为annotation_process,故名思议此module是存放注解处理器。在这里插入图片描述
(3)为了简单起见,此处就在annotaiton中创建两个注解BindView和OnClick,分别作用于编译期,因此定于为CLASS。

BindView作用:初始化View成员变量,替开发者实现findViewById。

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

OnClick作用:初始化Button等按钮,替用户实现setOnClickListener等等。

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface OnClick {
    int[] value();
}

(4)导入项目依赖
app module依赖annotation和annotation_process
在这里插入图片描述

anontation_process依赖annotation。从此处可以说明为什么annotation和annotation_process必须定义为java library,android工程可以依赖java工程,但反之不行。
在这里插入图片描述

(5)在annotation_process的build.grade中添加以下两句依赖用于实现注解处理器。

annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'
compileOnly 'com.google.auto.service:auto-service:1.0-rc3'

在这里插入图片描述

2.2 定义注解处理器

1、继承AbstractAnnotation,
2、在其类上添加@AutoService
3、实现process方法

@AutoService(Process.class)
public class AnnotationProcess extends AbstractProcessor {
    //通过io流动态生成代码
    private Filer filer;

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

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return super.getSupportedSourceVersion();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> set = new HashSet<>();
        set.add(BindView.class.getName());
        set.add(OnClick.class.getName());
        return set;
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //将Activity和其内部所有被OnCLick+BindView标记了的元素一一对应起来
        HashMap<TypeElement, ElementForType> hashMap = findAnnotationForActivity(roundEnvironment);
        //写文件
        if (hashMap.size() != 0) {
            Writer writer = null;
            Iterator<TypeElement> iterator = hashMap.keySet().iterator();
            while (iterator.hasNext()) {
                TypeElement element = iterator.next();
                ElementForType elementForType = hashMap.get(element);
                //获取Activity类名
                String className = element.getSimpleName().toString();
                //生成新类名
                String newClassName = className + "$$ButterKnife";
                //获取包名
                PackageElement packageElement = processingEnv.getElementUtils().getPackageOf(element);
                String packageName = packageElement.getQualifiedName().toString();

                JavaFileObject javaFileObject = null;
                try {
                    javaFileObject = filer.createSourceFile(packageName + "." + newClassName);
                    writer = javaFileObject.openWriter();
                    //将所有要生成的代码存到StringBuffer中
                    StringBuffer stringBuffer = getStringBuffer(packageName, newClassName, element, elementForType);

                } catch (IOException e) {
                    e.printStackTrace();
                }finally {
                    try {
                        writer.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return false;
    }


    public StringBuffer getStringBuffer(String packageName, String newClazzName,
                                        TypeElement typeElement, ElementForType elementForType) {
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append("package " + packageName + ";\n");
        stringBuffer.append("import android.view.View;\n");
        stringBuffer.append("public class " + newClazzName + "{\n");
        stringBuffer.append("public " + newClazzName + "(final " + typeElement.getQualifiedName() + " target){\n");
        if (elementForType != null && elementForType.getViewElements() != null && elementForType.getViewElements().size() > 0) {
            List<VariableElement> viewElements = elementForType.getViewElements();
            for (VariableElement viewElement : viewElements) {
                //获取到类型
                TypeMirror typeMirror = viewElement.asType();
                //获取到控件的名字
                Name simpleName = viewElement.getSimpleName();
                //获取到资源ID
                int resId = viewElement.getAnnotation(BindView.class).value();
                stringBuffer.append("target." + simpleName + " =(" + typeMirror + ")target.findViewById(" + resId + ");\n");
            }
        }

        if (elementForType != null && elementForType.getMethodElements() != null && elementForType.getMethodElements().size() > 0) {
            List<ExecutableElement> methodElements = elementForType.getMethodElements();
            for (ExecutableElement methodElement : methodElements) {
                int[] resIds = methodElement.getAnnotation(OnClick.class).value();
                String methodName = methodElement.getSimpleName().toString();
                for (int resId : resIds) {
                    stringBuffer.append("(target.findViewById(" + resId + ")).setOnClickListener(new View.OnClickListener() {\n");
                    stringBuffer.append("public void onClick(View p0) {\n");
                    stringBuffer.append("target." + methodName + "(p0);\n");
                    stringBuffer.append("}\n});\n");
                }
            }
        }
        stringBuffer.append("}\n}\n");
        return stringBuffer;
    }


    private HashMap<TypeElement, ElementForType> findAnnotationForActivity(RoundEnvironment roundEnvironment) {
        HashMap<TypeElement, ElementForType> hashMap = new HashMap<>();
        //获取到所有被BindView标记了的View元素
        Set<? extends Element> bindViewSet = roundEnvironment.getElementsAnnotatedWith(BindView.class);
        //获取所有被OnClick标记了的Method元素
        Set<? extends Element> onClickSet = roundEnvironment.getElementsAnnotatedWith(OnClick.class);
        for (Element element : bindViewSet) {
            VariableElement bindView = (VariableElement) element;
            //获取含有该BindView标记对应的Activity
            TypeElement typeElement = (TypeElement) element.getEnclosingElement();
            //获取该Activity中所有被注解标记的元素
            ElementForType elementForType = hashMap.get(typeElement);
            List<VariableElement> variableElementList;
            if (elementForType != null) {
                //获取该Activity中所有被BindView标记的View元素
                variableElementList = elementForType.getViewElements();
                if (variableElementList == null) {
                    variableElementList = new ArrayList<>();
                    elementForType.setViewElements(variableElementList);
                }
            } else {
                //若ElementForType不存在则创建
                elementForType = new ElementForType();
                variableElementList = new ArrayList<>();
                elementForType.setViewElements(variableElementList);
                //判断hashmap中是否保存了elementForType和typeElement
                if (!hashMap.containsKey(typeElement)) {
                    hashMap.put(typeElement, elementForType);
                }
            }
            variableElementList.add(bindView);
        }

        //onclick同理
        for (Element element : onClickSet) {
            ExecutableElement onClick = (ExecutableElement) element;
            //获取含有该OnClick标记对应的Activity
            TypeElement typeElement = (TypeElement) element.getEnclosingElement();
            //获取该Activity中所有被注解标记的元素
            ElementForType elementForType = hashMap.get(typeElement);
            List<ExecutableElement> executableElementList;
            if (elementForType != null) {
                //获取该Activity中所有被OnClick标记的Method元素
                executableElementList = elementForType.getMethodElements();
                if (executableElementList == null) {
                    executableElementList = new ArrayList<>();
                    elementForType.setMethodElements(executableElementList);
                }
            } else {
                //若ElementForType不存在则创建
                elementForType = new ElementForType();
                executableElementList = new ArrayList<>();
                elementForType.setMethodElements(executableElementList);
                //判断hashmap中是否保存了elementForType和typeElement
                if (!hashMap.containsKey(typeElement)) {
                    hashMap.put(typeElement, elementForType);
                }
            }
            executableElementList.add(onClick);
        }
        return hashMap;
    }
}

ButterKnife在编译阶段会为每个使用了BindView、OnClick等注解的类生成一个新类,因此创建了ElementForType用以封装同一个Activity中的所有被BindView+OnCLick等注解的元素。

/**
 * 此类用以封装同一个Activity中的所有被BindView+OnCLick注解的元素
 */

public class ElementForType {
    //此出的VariableElement代表的控件元素
    private List<VariableElement> viewElements;
    //此处的ExecutableElement代表的方法元素
    private List<ExecutableElement> methodElements;

    public ElementForType() {
    }

    public ElementForType(List<VariableElement> viewElements, List<ExecutableElement> methodElements) {
        this.viewElements = viewElements;
        this.methodElements = methodElements;
    }

    public List<VariableElement> getViewElements() {
        return viewElements;
    }

    public void setViewElements(List<VariableElement> viewElements) {
        this.viewElements = viewElements;
    }

    public List<ExecutableElement> getMethodElements() {
        return methodElements;
    }

    public void setMethodElements(List<ExecutableElement> methodElements) {
        this.methodElements = methodElements;
    }
}

2.3 定义ButterKnife

由于APT技术是在编译期替我们生成的一个新类,因此需要注意的是ButterKnife.java中的newClassName类名必须要和AnnotationProcess.java中的newClassName一致,否则无法生效。

public class ButterKnife {
    public static void bind(Context context){
        //获取类名
        String className = context.getClass().getName();
        //获取生成的类的构造器
        String newClassName = className + "$$ButterKnife";
        Constructor<?> constructor = null;
        try {
            constructor = Class.forName(newClassName).getConstructor();
            constructor.newInstance(context);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2.4使用Butterknife

使用和ButterKnife一样即可

public class MainActivity extends AppCompatActivity {
    @BindView(R.id.btn1)
    private Button btn1;
    @BindView(R.id.btn2)
    private Button btn2;
    @BindView(R.id.btn3)
    private Button btn3;
    @BindView(R.id.text)
    private EditText text;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        text.setText("xixi");
    }

    @OnClick({R.id.btn1,R.id.btn2,R.id.btn3})
    public void onClick(View view){
        switch (view.getId()){
            case R.id.btn1:
                Log.d("kkjj","按钮1");
                break;
            case R.id.btn2:
                Log.d("kkjj","按钮1");
                break;
            case R.id.btn3:
                Log.d("kkjj","按钮1");
                break;
        }
    }
}

2.5 结论

BUtterKnife通过注解+注解处理器的方式在编译期动态地生成XXX$$ButterKnife.java文件,在此文件中替开发者实现了findViewById、setOnClickListener等等操作。

所以ButterKnife等三方框架仍然通过findViewById、setOnClickListener操作实现的view绑定,监听注册。因此并没有绕过Android原生的开发规则。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值