Android-ButterKnife(手写)

简介

ButterKnife是一个Android系统的View注入框架,它用到的是编译时的技术,在编译的时候生成新的class;

标致上注解,在java源文件转换成class文件的过程中,通过APT(注解处理工具)产生代码;

相对我上一篇IOC的博客,这里是在玩编译时,所以这里和上篇博客的反射不一样,这里是没有去用反射,这样会有性能提升;

你们以为我是要带着大家看看怎么使用ButterKnife吗?不,我相信怎么使用大家很多人都用过了…

手写ButterKnife(核心)

相信大家都知道ButterKnife里面是编译时生成java类;
那么这里就会用到注解、APT(注解处理器)

创建项目

我们先创建项目;新增加两个java Library(一个注解的Module、一个用来处理注解的APT的Module)
在这里插入图片描述

添加依赖关系

主工程app是需要使用到注解吧?需要生成java文件吧?
注解处理器是需要拿到注解吧?
那么
主工程就需要依赖:注解模块、注解处理器模块;
注解处理器就需要一类:注解模块;

app的build.gradle文件中添加

    //因为它是注解处理器  所以需要用annotationProcessor
    annotationProcessor project(path: ':annotation_compiler')
    implementation project(path: ':annotations')

注解处理器annotation_compiler的build.gradle文件中添加

//依赖的注解模块
implementation project(path: ':annotations')
    //注册我们的注解处理器
//    implementation 'com.google.auto.service:auto-service:1.0-rc3'
    // As-3.4.1 + gradle5.1.1-all + auto-service:1.0-rc4
    //当前模块成为注解处理器
    annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'
    //注册我们的注解处理器
    compileOnly'com.google.auto.service:auto-service:1.0-rc4'

上面依赖都添加好之后,他们的依赖关系就添加好了;

定义我们的注解

在注解模块中定义我们的注解
相信大家这个自定义注解应该没什么问题吧
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

使用注解

这里和Butterknife一样的使用
注解作用域的地方,注意看上面的自定义注解定义的作用域

package com.lk.ym_butterknife;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.lk.annotations.BindLayout;
import com.lk.annotations.BindView;
import com.lk.annotations.OnClick;

@BindLayout(R.layout.activity_main)
public class MainActivity extends Activity {

    @BindView(R.id.tv_text)
    Button tvText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        MyButterKnife.bind(this);
        tvText.setText("注入成功了");
    }
    @OnClick({R.id.tv_text,R.id.btn_click})
    public void myOnClick(View view){
        Log.i("myOnClick","myOnClick:"+view.getId());
        switch (view.getId()){
            case R.id.tv_text:
                Intent intent = new Intent(MainActivity.this,TestActivity.class);
                startActivity(intent);
                break;
            case R.id.btn_click:
                Toast.makeText(MainActivity.this,"点击了Button",Toast.LENGTH_LONG).show();
                break;
        }
    }

    @OnClick(R.id.btn_click3)
    public void clickTest(View view){
        Toast.makeText(MainActivity.this,"我是第三个按钮",Toast.LENGTH_LONG).show();
    }
}

以上步骤完成后,注解还是没有生效,因为我们还没有去编写我们的注解处理器;

注解处理器(核心)

核心:编写我们的注解处理器,让其生效

注解处理器AbstractProcessor,我们需要去继承这个类;
实现里面的process方法;
在这个方法中去**为所欲为**,哈哈

看了上面的使用方法,我们再来看看最终生成的java类,我这里两个Activity用到了自己的ButterKnife,来看看
在这里插入图片描述

package com.lk.ym_butterknife;

import com.lk.ym_butterknife.IBinder;
import android.view.View;


public class MainActivity_ViewBinding implements IBinder<com.lk.ym_butterknife.MainActivity>{

@Override
public void bind(final com.lk.ym_butterknife.MainActivity target){
target.setContentView(2131296284);
target.tvText=(android.widget.Button)target.findViewById(2131165328);
target.tvText.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
      target.myOnClick(v);
   }
});
target.findViewById(2131165218).setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
      target.myOnClick(v);
   }
});
target.findViewById(2131165219).setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
      target.clickTest(v);
   }
});

}
}
package com.lk.ym_butterknife;

import com.lk.ym_butterknife.IBinder;
import android.view.View;


public class TestActivity_ViewBinding implements IBinder<com.lk.ym_butterknife.TestActivity>{

@Override
public void bind(final com.lk.ym_butterknife.TestActivity target){
target.tvCeshi=(android.widget.TextView)target.findViewById(2131165327);
target.tvCeshi.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View v) {
      target.ceShiOnClick(v);
   }
});

}
}

忽略上面的格式,哈哈…

我们来看看这个生成的MainActivity_ViewBinding类

1、我们看到MainActivity_ViewBinding是实现了一个泛型接口IBinder,实现了这个接口的bind方法;参数是MainActivity,这是接口泛型类型;
2、target.setContentView(2131296284); 调用了Activity的setContentView方法,传入了个布局id;
3、调用了Activity的findViewById(2131165328)这个方法获取到view对象,并赋值给了activity的tvText变量中;
4、给tvText这个变量订阅了点击事件setOnClickListener;
5、在这个点击事件的会调用,调用了Activity的myOnClick方法,传入了回调的view参数;
6、还有两个控件是直接findViewById获取到后没有赋值到变量中,而是直接订阅了点击事件,并在各自的事件会调用调用了Activity的 myOnClick方法和clickTest方法

我们结合在Activity中使用的注解来看的话
1、target.setContentView(2131296284);
在这里插入图片描述
2、target.setContentView(2131296284);
在这里插入图片描述
3、订阅点击事件,并调用activity中的方法
在这里插入图片描述
接下来我们来看看生成这样的java类的代码

1、首先继承了AbstractProcessor 这个注解处理器的类;
2、实现了四个方法init、getSupportedAnnotationTypes、getSupportedSourceVersion、process;
init:做一些初始化工作;
getSupportedAnnotationTypes:需要确定当前APT处理所有模块中的哪些注解;
getSupportedSourceVersion:支持的JDK的版本;
process:是核心处理的方法;

如果有写过注解处理器的朋友,应该不会陌生

package com.lk.annotation_compiler;

import com.google.auto.service.AutoService;
import com.lk.annotations.BindLayout;
import com.lk.annotations.BindView;
import com.lk.annotations.OnClick;

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.Map;
import java.util.Set;


/**
 * 这个类就是我们的APT(注解处理器)
 * 这个类就可以在java文件转成class文件中做我们想要做的事情了
 */
@AutoService(Processor.class)   //注册注解处理器
public class AnnotationCompiler extends AbstractProcessor {

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

    //2、需要确定当前APT处理所有模块中的哪些注解
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new HashSet<>();
        //添加我们需要处理的注解名字
        types.add(BindLayout.class.getCanonicalName());
        types.add(BindView.class.getCanonicalName());
        types.add(OnClick.class.getCanonicalName());
        return types;
    }

    //3、支持的JDK的版本
    @Override
    public SourceVersion getSupportedSourceVersion() {
        //支持到最新版本
        return SourceVersion.latestSupported();
    }

    /**
     * 在这个方法中,我们去生成IBinder的实现类
     * @param set
     * @param roundEnvironment
     * @return
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    
        return false;
    }
}

1、先来看看我们在init方法中做的初始化工作

1、初始化获取到了一个消息对象Messager,用于后续打印日志
2、初始化获取到了一个生成文件的对象Filer
3、确保是否执行了这个方法我们打印了一条日志

    //1、定义一个用于生成文件的对象
    Filer filer;
    Messager mMessager;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mMessager = processingEnvironment.getMessager();
        //初始化生成文件的对象
        filer = processingEnvironment.getFiler();
        mMessager.printMessage(Diagnostic.Kind.NOTE, "init------start");
    }

2、我们再来看看getSupportedAnnotationTypes这个方法的实现

这个方法返回一个Set集合
1、我们创建了一个集合;
2、添加了需要处理的注解的名字,这里添加所有的注解,包括系统的,我们这里只处理自己的;
3、将这个集合return出去了;

  //2、需要确定当前APT处理所有模块中的哪些注解
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new HashSet<>();
        //添加我们需要处理的注解名字
        types.add(BindLayout.class.getCanonicalName());
        types.add(BindView.class.getCanonicalName());
        types.add(OnClick.class.getCanonicalName());
        return types;
    }

3、来看看getSupportedSourceVersion这个方法;

    //3、支持的JDK的版本
    @Override
    public SourceVersion getSupportedSourceVersion() {
        //支持到最新版本
        return SourceVersion.latestSupported();
    }

4、核心方法process的编写

这个方法中能获取到元素:类元素、属性元素、可执行元素;
类元素:理解为就是类;
属性元素:理解为就是属性字段;
可执行元素:理解为就是可执行的方法;

在这个核心方法中,我们要做的事情是:1、生成对应的java类、2、得到文件输入流;3、通过这个文件输入流编写文件中的内容;

1、我这里先获取了这三个元素集合;
为什么是集合了?因为会有多个,这里是获取所有标有这些注解的所有类元素、所有属性元素、所有可执行元素
2、创建了一个Map<String,MyElement> map集合,用于做分类;
为什么要分类?因为每个类元素Activity都有自己的属性元素集合、可执行元素集合,所以为了生成不同的类,我们这里做个分类;
3、对类元素、属性元素、可执行元素进行集合分类,添加到map中;map的key对应的是Activity的名字;value是我自己定义的MyElement类对象;
4、如果这个map有数据,我们就进行遍历;创建各自类的java类,编写各自类里面的内容;
因为有些Activity中没有用BindLayout这个注解来设置布局,所以我们考虑到@BindLayout这个注解里面的value值不等于-1的时候,我们在添加target.setContentView这个内容;
一个类中可能存在多个@BindView这个注解,所以我们需要遍历所有的属性元素,记住这里只会拿到标有@BindView这个注解的属性字段,因为我们上面定义了拿BindView这个注解的属性字段。
:写入内容的方式我们用拼写…ButterKnife里面也是这样的方式一行一行的进行拼写… 有兴趣的可以去看看它的源码
5、在拼写的过程中,值得注意的一个地方,就是点击事件,还记得MainActivity里面的点击事件吗?我特意写了两个@OnClick注解的方法,以及有个别的控件特意没有用@BindView去注入,这个拼写的过程中我考虑进去了;
:通过比对@BindView注解的所有id,如果没有注入的控件,但是在@OnClick注解中写了,我们就需要考虑到在订阅点击事件之前先findViewById一下;如果已经注入过的控件,我们直接拼写订阅点击事件;还有需要把事件传递到可执行的元素里面去

 /**
     * 在这个方法中,我们去生成IBinder的实现类
     * @param set
     * @param roundEnvironment
     * @return
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        mMessager.printMessage(Diagnostic.Kind.NOTE, "process------start");
        //得到程序中所有写了BindView注解的元素的集合
        //1、类元素(TypeElement) 2、可执行元素也就是方法(ExecutableElement)3、属性元素(VariableElement)
        // 这些元素的父类都是Element
        Set<? extends Element> variableElementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindView.class);
        Set<? extends Element> executableElementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(OnClick.class);
        Set<? extends Element> typeElementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindLayout.class);
        //定义一个Map来进行分类
        //因为getElementsAnnotatedWith获取到的会是所有标有BindView注解的属性元素
        //这些元素可能会存在不同的Activity中,所以我们来进行分类
        //key是Activity名字   value是自定义的对象 里面存放了属性元素集合、可执行元素集合
        Map<String,MyElement> map = new HashMap<>();

        //分类存入到Map中-------------start-------------
        //类元素分类
        typeElementSort(typeElementsAnnotatedWith, map);
        //属性元素分类
        variableElementSort(variableElementsAnnotatedWith, map);
        //可执行元素分类
        executableElementSort(executableElementsAnnotatedWith, map);
        //分类存入到Map中-------------end-------------


        //生成接口文件
        if(map.size()>0){
            //文件写入流
            Writer writer = null;
            //每一个Activity都要生成一个对应的文件
            //key 对应的是activity  所以获取key的迭代器
            Iterator<String> iterator = map.keySet().iterator();
            //遍历迭代器
            while (iterator.hasNext()){
                //拿到Activity的名字
                String activityName = iterator.next();
                //拿到key对应的MyElement对象
                MyElement myElement = map.get(activityName);
                //接下来取出包名
                String packageName = myElement.getPackageName();

                try {
                    //生成文件 java文件createSourceFile
                    //生成一个类似  包名.MainActivity_ViewBinding这样一个文件
                    JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + activityName + "_ViewBinding");
                    //获取这和文件写入流
                    writer = sourceFile.openWriter();
                    //第一行   package com.lk.ym_butterknife;
                    writer.write("package "+packageName+";\n\n");
                    //第二行   import com.lk.ym_butterknife.IBinder;
                    writer.write("import "+packageName+".IBinder;\n");
                    writer.write("import android.view.View;\n\n\n");
                    //第三行   public class MainActivity_ViewBinding implements IBinder<com.lk.ym_butterknife.MainActivity>{
                    writer.write("public class "+activityName+"_ViewBinding implements IBinder<"+packageName+"."+activityName+">{\n\n");
                    //第四行   @Override
                    writer.write("@Override\n");
                    //第五行   public void bind(com.lk.ym_butterknife.MainActivity target) {
                    writer.write("public void bind(final "+packageName+"."+activityName+" target){\n");
                    //第六行 setContentView   这里需要考虑是否有设置BindLayout这个注解
                    if(myElement.getContentView()!=-1){
                        writer.write("target.setContentView("+myElement.getContentView()+");\n");
                    }
                    //下面就是写这个方法里面的赋值了(可能会有多个)
                    // target.tvText=(android.widget.TextView)target.findViewById(2131161231);
                    for (VariableElement variableElement :  myElement.getValidationEvents()) {
                        //获取控件的名字
                        String variableName = variableElement.getSimpleName().toString();
                        //获取id  因为这个id是写在注解里面的   例如   @BindView(R.id.tv_text)
                        int id = variableElement.getAnnotation(BindView.class).value();
                        //获取属性元素的类型  variableElement.asType()
                        TypeMirror typeMirror = variableElement.asType();
                        writer.write("target."+variableName+"=("+typeMirror+")target.findViewById("+id+");\n");
                    }
                    //下面我们来写这个方法里面的控件订阅点击事件
                    for (ExecutableElement executableElement :  myElement.getExecutableElements()) {
                        //获取可执行元素的名字
                        String executableName = executableElement.getSimpleName().toString();
                        //获取id数组  因为这个id数组是卸载注解里面的   例如   @OnClick({R.id.tv_text,R.id.btn_click})
                        int[] ids = executableElement.getAnnotation(OnClick.class).value();
                        //遍历注解里面的id数组
                        for (int mId : ids) {
                            //是否存在(也就是  是否已经findViewById进行赋值过了)
                            boolean isExistence = false;
                            String variableName="";
                            for (VariableElement variableElement :  myElement.getValidationEvents()) {
                                //获取控件的名字
                                variableName= variableElement.getSimpleName().toString();
                                //获取id  因为这个id是写在注解里面的   例如   @BindView(R.id.tv_text)
                                int id = variableElement.getAnnotation(BindView.class).value();
                                if(id==mId){
                                    isExistence = true;
                                    continue;
                                }
                            }
                            if(isExistence){
                                //已经赋值过属性元素(控件),我们就这样拼写
                                writer.write("target."+variableName+".setOnClickListener(new View.OnClickListener() {\n");
                            }else{
                                //如果没有赋值过属性元素(控件),我们就需要先findViewById一下
                                writer.write("target.findViewById("+mId+").setOnClickListener(new View.OnClickListener() {\n");
                            }
                            writer.write("   @Override\n");
                            writer.write("   public void onClick(View v) {\n");
                            writer.write("      target."+executableName+"(v);\n");
                            writer.write("   }\n");
                            writer.write("});\n");
                        }
                    }
                    //最后一行
                    writer.write("\n}\n}");

                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    if(null!=writer) {
                        try {
                            writer.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }

        return false;
    }

来看看这个我自己定义的MyElement类

用来存储属性元素集合、可执行元素集合、包名、布局id

package com.lk.annotation_compiler;

import java.util.List;

import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;

/**
 * 自定义实体类  用来存储元素集合、包名、布局id
 */
public class MyElement {
    //属性元素集合
    private List<VariableElement> validationEvents;
    //可执行元素集合
    private List<ExecutableElement> executableElements;
    //包名
    private String packageName;
    //布局id
    private int contentView = -1;


    public List<VariableElement> getValidationEvents() {
        return validationEvents;
    }

    public void setValidationEvents(List<VariableElement> validationEvents) {
        this.validationEvents = validationEvents;
    }

    public List<ExecutableElement> getExecutableElements() {
        return executableElements;
    }

    public void setExecutableElements(List<ExecutableElement> executableElements) {
        this.executableElements = executableElements;
    }

    public String getPackageName() {
        return packageName;
    }

    public void setPackageName(String packageName) {
        this.packageName = packageName;
    }

    public int getContentView() {
        return contentView;
    }

    public void setContentView(int contentView) {
        this.contentView = contentView;
    }
}

下面来看看这个注解处理器的完整代码

package com.lk.annotation_compiler;

import com.google.auto.service.AutoService;
import com.lk.annotations.BindLayout;
import com.lk.annotations.BindView;
import com.lk.annotations.OnClick;

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.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.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;
import javax.xml.bind.ValidationEvent;

/**
 * 这个类就是我们的APT(注解处理器)
 * 这个类就可以在java文件转成class文件中做我们想要做的事情了
 */
@AutoService(Processor.class)   //注册注解处理器
public class AnnotationCompiler extends AbstractProcessor {

    //1、定义一个用于生成文件的对象
    Filer filer;
    Messager mMessager;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mMessager = processingEnvironment.getMessager();
        //初始化生成文件的对象
        filer = processingEnvironment.getFiler();
        mMessager.printMessage(Diagnostic.Kind.NOTE, "init------start");
    }

    //2、需要确定当前APT处理所有模块中的哪些注解
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new HashSet<>();
        //添加我们需要处理的注解名字
        types.add(BindLayout.class.getCanonicalName());
        types.add(BindView.class.getCanonicalName());
        types.add(OnClick.class.getCanonicalName());
        return types;
    }

    //3、支持的JDK的版本
    @Override
    public SourceVersion getSupportedSourceVersion() {
        //支持到最新版本
        return SourceVersion.latestSupported();
    }

    /**
     * 在这个方法中,我们去生成IBinder的实现类
     * @param set
     * @param roundEnvironment
     * @return
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        mMessager.printMessage(Diagnostic.Kind.NOTE, "process------start");
        //得到程序中所有写了BindView注解的元素的集合
        //1、类元素(TypeElement) 2、可执行元素也就是方法(ExecutableElement)3、属性元素(VariableElement)
        // 这些元素的父类都是Element
        Set<? extends Element> variableElementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindView.class);
        Set<? extends Element> executableElementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(OnClick.class);
        Set<? extends Element> typeElementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindLayout.class);
        //定义一个Map来进行分类
        //因为getElementsAnnotatedWith获取到的会是所有标有BindView注解的属性元素
        //这些元素可能会存在不同的Activity中,所以我们来进行分类
        //key是Activity名字   value是自定义的对象 里面存放了属性元素集合、可执行元素集合
        Map<String,MyElement> map = new HashMap<>();

        //分类存入到Map中-------------start-------------
        //类元素分类
        typeElementSort(typeElementsAnnotatedWith, map);
        //属性元素分类
        variableElementSort(variableElementsAnnotatedWith, map);
        //可执行元素分类
        executableElementSort(executableElementsAnnotatedWith, map);
        //分类存入到Map中-------------end-------------


        //生成接口文件
        if(map.size()>0){
            //文件写入流
            Writer writer = null;
            //每一个Activity都要生成一个对应的文件
            //key 对应的是activity  所以获取key的迭代器
            Iterator<String> iterator = map.keySet().iterator();
            //遍历迭代器
            while (iterator.hasNext()){
                //拿到Activity的名字
                String activityName = iterator.next();
                //拿到key对应的MyElement对象
                MyElement myElement = map.get(activityName);
                //接下来取出包名
                String packageName = myElement.getPackageName();

                try {
                    //生成文件 java文件createSourceFile
                    //生成一个类似  包名.MainActivity_ViewBinding这样一个文件
                    JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + activityName + "_ViewBinding");
                    //获取这和文件写入流
                    writer = sourceFile.openWriter();
                    //第一行   package com.lk.ym_butterknife;
                    writer.write("package "+packageName+";\n\n");
                    //第二行   import com.lk.ym_butterknife.IBinder;
                    writer.write("import "+packageName+".IBinder;\n");
                    writer.write("import android.view.View;\n\n\n");
                    //第三行   public class MainActivity_ViewBinding implements IBinder<com.lk.ym_butterknife.MainActivity>{
                    writer.write("public class "+activityName+"_ViewBinding implements IBinder<"+packageName+"."+activityName+">{\n\n");
                    //第四行   @Override
                    writer.write("@Override\n");
                    //第五行   public void bind(com.lk.ym_butterknife.MainActivity target) {
                    writer.write("public void bind(final "+packageName+"."+activityName+" target){\n");
                    //第六行 setContentView   这里需要考虑是否有设置BindLayout这个注解
                    if(myElement.getContentView()!=-1){
                            writer.write("target.setContentView("+myElement.getContentView()+");\n");
                    }
                    //下面就是写这个方法里面的赋值了(可能会有多个)
                    // target.tvText=(android.widget.TextView)target.findViewById(2131161231);
                    for (VariableElement variableElement :  myElement.getValidationEvents()) {
                        //获取控件的名字
                        String variableName = variableElement.getSimpleName().toString();
                        //获取id  因为这个id是写在注解里面的   例如   @BindView(R.id.tv_text)
                        int id = variableElement.getAnnotation(BindView.class).value();
                        //获取属性元素的类型  variableElement.asType()
                        TypeMirror typeMirror = variableElement.asType();
                        writer.write("target."+variableName+"=("+typeMirror+")target.findViewById("+id+");\n");
                    }
                    //下面我们来写这个方法里面的控件订阅点击事件
                    for (ExecutableElement executableElement :  myElement.getExecutableElements()) {
                        //获取可执行元素的名字
                        String executableName = executableElement.getSimpleName().toString();
                        //获取id数组  因为这个id数组是卸载注解里面的   例如   @OnClick({R.id.tv_text,R.id.btn_click})
                        int[] ids = executableElement.getAnnotation(OnClick.class).value();
                        //遍历注解里面的id数组
                        for (int mId : ids) {
                            //是否存在(也就是  是否已经findViewById进行赋值过了)
                            boolean isExistence = false;
                            String variableName="";
                            for (VariableElement variableElement :  myElement.getValidationEvents()) {
                                //获取控件的名字
                                variableName= variableElement.getSimpleName().toString();
                                //获取id  因为这个id是写在注解里面的   例如   @BindView(R.id.tv_text)
                                int id = variableElement.getAnnotation(BindView.class).value();
                                if(id==mId){
                                    isExistence = true;
                                    continue;
                                }
                            }
                            if(isExistence){
                                //已经赋值过属性元素(控件),我们就这样拼写
                                writer.write("target."+variableName+".setOnClickListener(new View.OnClickListener() {\n");
                            }else{
                                //如果没有赋值过属性元素(控件),我们就需要先findViewById一下
                                writer.write("target.findViewById("+mId+").setOnClickListener(new View.OnClickListener() {\n");
                            }
                            writer.write("   @Override\n");
                            writer.write("   public void onClick(View v) {\n");
                            writer.write("      target."+executableName+"(v);\n");
                            writer.write("   }\n");
                            writer.write("});\n");
                        }
                    }
                    //最后一行
                    writer.write("\n}\n}");

                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    if(null!=writer) {
                        try {
                            writer.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }

        return false;
    }

    /**
     * 可执行元素的分类
     * @param onClickElementsAnnotatedWith  所有可执行元素
     * @param map
     */
    private void executableElementSort(Set<? extends Element> onClickElementsAnnotatedWith, Map<String, MyElement> map) {
        for (Element element : onClickElementsAnnotatedWith) {
            //强转成 可执行元素
            ExecutableElement executableElement = (ExecutableElement) element;
            String activityName = executableElement.getEnclosingElement().getSimpleName().toString();
            MyElement myElement = map.get(activityName);
            if(null == myElement) {//如果不存在的话(也就是这个activity分类还没有存在)
                myElement = new MyElement();
                //接下来获取包名 并存入到对象中
                //getEnclosingElement 是获取到包裹这个属性元素的元素 所以我们这里获取到的是类元素(TypeElement)
                TypeElement enclosingElement = (TypeElement) executableElement.getEnclosingElement();
                //获取包名processingEnv.getElementUtils()  这个工具是处理器里面定义好的
                String packageName = processingEnv.getElementUtils().getPackageOf(enclosingElement).toString();
                myElement.setPackageName(packageName);
                List<ExecutableElement> executableElements = new ArrayList<>();
                myElement.setExecutableElements(executableElements);
                map.put(activityName,myElement);
            }else{
                if(null==myElement.getExecutableElements()){
                    List<ExecutableElement> executableElements = new ArrayList<>();
                    myElement.setExecutableElements(executableElements);
                }
            }
            //MyElement对象中的的可执行元素集合添加该可执行元素
            myElement.getExecutableElements().add(executableElement);
        }
    }

    /**
     * 属性元素的分类
     * @param elementsAnnotatedWith 所有的属性元素
     * @param map
     */
    private void variableElementSort(Set<? extends Element> elementsAnnotatedWith, Map<String, MyElement> map) {
        for (Element element : elementsAnnotatedWith) {
            //因为BindView注解是标注在属性字段上面的,所以我们拿到的会是属性元素,这里我们强转一下
            VariableElement variableElement = (VariableElement) element;
            //获取Acivity的名字   这个Activity名字要怎么获取了?
            //getEnclosingElement 是获取到包裹这个属性元素的元素 这里就像是 这个属性字段是在类元素里面的
            //所以这里获取到的是这个属性元素所在的类元素
            String activityName = variableElement.getEnclosingElement().getSimpleName().toString();
            //先根据activity的名字去获取一下map中的value(MyElement对象)
            MyElement myElement = map.get(activityName);
            if(null == myElement){//如果对象不存在的话
                //我们创建该对象
                myElement = new MyElement();
                //接下来获取包名 并存入到对象中
                //getEnclosingElement 是获取到包裹这个属性元素的元素 所以我们这里获取到的是类元素(TypeElement)
                TypeElement enclosingElement = (TypeElement) variableElement.getEnclosingElement();
                //获取包名processingEnv.getElementUtils()  这个工具是处理器里面定义好的
                String packageName = processingEnv.getElementUtils().getPackageOf(enclosingElement).toString();
                myElement.setPackageName(packageName);
                //同时创建属性元素的集合
                List<VariableElement> validationEvents = new ArrayList<>();
                //设置到对象里面去
                myElement.setValidationEvents(validationEvents);
                //添加进map
                map.put(activityName,myElement);
            }else{
                if(null==myElement.getValidationEvents()){
                    List<VariableElement> validationEvents = new ArrayList<>();
                    myElement.setValidationEvents(validationEvents);
                }
            }
            //MyElement对象中的的属性元素集合添加该属性元素
            myElement.getValidationEvents().add(variableElement);
        }
    }

    /**
     * 类元素分类
     * @param layoutElementsAnnotatedWith   所有的类元素
     * @param map
     */
    private void typeElementSort(Set<? extends Element> layoutElementsAnnotatedWith, Map<String, MyElement> map) {
        for (Element element : layoutElementsAnnotatedWith) {
            //强转类元素
            TypeElement typeElement = (TypeElement) element;
            int layout = typeElement.getAnnotation(BindLayout.class).value();

            //获取Activity的名字
            String activityName = typeElement.getSimpleName().toString();
            MyElement myElement = map.get(activityName);
            if(null==myElement){
                myElement = new MyElement();
                //获取包名processingEnv.getElementUtils()  这个工具是处理器里面定义好的
                String packageName = processingEnv.getElementUtils().getPackageOf(typeElement).toString();
                //添加包名
                myElement.setPackageName(packageName);
                //添加布局
                myElement.setContentView(layout);
            }
            //添加进map
            map.put(activityName,myElement);
        }
    }
}

最后我们在来看看MainActivity怎么和生成的java类bind调用

看到这个接口了吗?答案很明确了,接口调用,传递进来了Activity的上下文,通过上下文才能去调用findViewById、setContent这些方法,记得生成的文件中实现的接口吗?就是这个

package com.lk.ym_butterknife;

/**
 * 用来绑定Activity
 * @param <T>
 */
public interface IBinder<T> {
    //绑定Activity
    void bind(T targer);
}

在MainActivity中调用了MyButterKnife.bind(this);方法

我们来看看这个MyButterKnife类;
这里有同学可能就要问了,为什么这里是用到了反射?累不是有吗?直接用不就好了吗?
因为这个类是在编译时生成的,我们一开始可能还没生成文件,所以这里通过类名去反射拿到这个类,再调用newInstance()方法去吧这个对象创建出来,强转成了IBinder接口,调用bind方法,这时候调用的是他的实现类。

package com.lk.ym_butterknife;

import android.app.Activity;

/**
 * 我们去使用apt生成的java文件
 */
public class MyButterKnife {

    public static void bind(Activity activity){

        //因为用户用的时候,可能这个文件还没生成,所以这里使用反射
        String name = activity.getClass().getName()+"_ViewBinding";
        try {
            //根据类名去反射拿到这个类
            Class<?> aClass = Class.forName(name);

            IBinder iBinder = (IBinder) aClass.newInstance();
            //执行IBinder接口里面的bind方法,这里就会调用实现类MainActivity_ViewBinding里面的bind方法;
            iBinder.bind(activity);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

谢谢大家观看到最后,大家辛苦了,有问题可以指出,大神勿喷,谢谢

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值