ButterKnife是一个Android系统的View注入框架,它用到的是编译时的技术,在编译的时候生成新的class;
标致上注解,在java源文件转换成class文件的过程中,通过APT(注解处理工具)产生代码;
相对我上一篇IOC的博客,这里是在玩编译时,所以这里和上篇博客的反射不一样,这里是没有去用反射,这样会有性能提升;
你们以为我是要带着大家看看怎么使用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、创建了一个Mapmap集合,用于做分类;
为什么要分类?因为每个类元素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{
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{
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
*/
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();
}
}
}
谢谢大家观看到最后,大家辛苦了,有问题可以指出,大神勿喷,谢谢