标题有点长,相信用过xUtils和ButterKnife框架的都知道啥意思,他们都可以通过注解的方式省去繁琐的findViewById和setOnClickListener代码的编写。他们2者的实现原理不一样,前者用的是运行时注解,后者用的是编译时注解,对于不同的注解,会有不同的注解处理器, 针对运行时注解会采用反射机制来处理,针对编译时注解会采用AbstractProcessor来处理,相对来说后者的性能会比前者好点,今天就来介绍下编译时注解的解释器该如何编写。
先来看看注解的使用
public class MainActivity extends AppCompatActivity {
@InjectView(R.id.btn_title)
TextView mTitleBtn;
@InjectView(R.id.btn_desc)
TextView mDescBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//注入
ViewInjector.inject(this);
//注入后就可以使用了
mTitleBtn.setText("我是标题");
mDescBtn.setText("我是描述");
}
//设置点击事件
@InjectMethod({R.id.btn_title, R.id.btn_desc})
public void showToast(View v) {
switch (v.getId()) {
case R.id.btn_title:
Toast.makeText(this, "标题被点击了", Toast.LENGTH_SHORT).show();
break;
case R.id.btn_desc:
Toast.makeText(this, "描述被点击了", Toast.LENGTH_SHORT).show();
break;
}
}
}
编译工程后,会在项目的\build\generated\ap_generated_sources\debug\out目录下自动生成一份java代码,效果如下所示:
// Generated code from ViewInjector. Do not modify!
package blog.csdn.net.mchenys.essayjoke;
import mchenys.ViewInjector.ViewBinder;
import blog.csdn.net.mchenys.essayjoke.MainActivity;
import android.view.View;
public class MainActivityInjector implements ViewBinder<MainActivity> {
@Override
public void bind(MainActivity args){
args.mTitleBtn=(android.widget.TextView)args.findViewById(2131165219);
args.mDescBtn=(android.widget.TextView)args.findViewById(2131165218);
args.mTitleBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
args.showToast(v);
}
});
args.mDescBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
args.showToast(v);
}
});
}
}
效果图:
先从注解开始介绍,@InjectView和@InjectMethod定义如下:
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.CLASS)
public @interface InjectView {
int value();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface InjectMethod {
int[] value();
}
非常简单,@Target用于声明作用的范围,ElementType.FIELD表示只能用在成员变量上,ElementType.METHOD表示只能用在方法上,除了这里用到的2个,它还可以有以下类型:
ElementType.TYPE:只能用在类、接口或者枚举类型。
ElementType.PARAMETER:只能用在参数上。
ElementType.CONSTRUCTOR:只能用在构造方法上。
ElementType.LOCAL_VARIABLE:只能用在局部变量上。
ElementType.ANNOTATION_TYPE:只能用在注解上。
ElementType.PACKAGE:只能用在包上。
ElementType.TYPE_PARAMETER:只能用在类型参数上。
而@Retention表示注解的保留策略,它一共有3种:
RetentionPolicy.SOURCE:源码级注解,只会把注解信息保留在.java文件中,源码在编译时会被丢弃。
RetentionPolicy.CLASS:编译时注解,默认级别,注解信息会保留在.java和.class文件中,当运行java程序时,JVM会丢弃该注解信息。
RetentionPolicy.RUNTIME:运行时注解,当运行java程序时,JVM会保留改注解信息,可以通过反射获取该注解信息。
我这里用的就是编译时注解,下面开始介绍编译时注解处理器的开发步骤。
首先,你得新建一个java Library,注意是一定是java Library而不是android Library,否则会找不到AbstractProcessor类,我这里建的库的名字就叫processor,工程项目如下所示:
其中processor包内的ViewBindProcessor就是注解处理器类,它是继承至AbstractProcessor类的,继承后需要实现process抽象方法:
public abstract boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv);
该方法相当于每个处理器的主函数,在这里可以编写处理注解的代码,以及生成java文件,参数annotations表示需要给该处理器解释的注解的集合,roundEnv参数可以用来查找使用了特定注解的元素。
除此之外,还有3个方法会用到,分别是:
public synchronized void init(ProcessingEnvironment processingEnv) {}
public Set<String> getSupportedAnnotationTypes() {}
public SourceVersion getSupportedSourceVersion() {}
init:该方法是初始化的地方,我们可以通过ProcessingEnvironment获取到很多有用的工具类,比如Elements(提供了一些和元素相关的操作,如获取所在包的包名等)、Types(提供了和类型相关的一些操作,如获取父类、判断两个类是不是父子关系等)、Filer(用于文件操作,用它去创建生成的代码文件)、Messager(用来打印信息的,它会打印出Element所在的源代码)等等。
getSupportedAnnotationTypes:该方法必须指定,它是指定哪些注解需要被该处理器解释的,需要将要处理的注解的全名放到Set中返回。
getSupportedSourceVersion:这个方法用来指定支持的java版本,通常这里返回SourceVersion。latestSupported().
在java7之后,也可以使用注解来代替getSupportedAnnotationTypes和getSupportedSourceVersion方法。例如:
@SupportedAnnotationTypes({"mchenys.annotations.InjectView",
"mchenys.annotations.InjectMethod"})//指定这个注解处理器是给哪些注
@SupportedSourceVersion(SourceVersion.RELEASE_7)//设置java版本
public class ViewBindProcessor extends AbstractProcessor {
}
如何注册Processor
编写完我们的Processor之后需要将它注册给java编译器,为了能使用注解处理器,需要用一个服务文件来注册它,关于服务文件的创建有2种方式,先来介绍比较麻烦的一种,步骤如下:
1.首先,在processor库的src/main目录下创建resources/META-INF/services/javax.annotation.processing.Processor文件(即创建resources目录,在resources目录下创建META-INF目录,继续在META-INF目录下创建services目录,最后在services目录下创建javax.annotation.processing.Processor文件)
2.然后编辑javax.annotation.processing.Processor文件,写入自定义的Processor的全路径名称,如果有多个Processor的话,每一行写一个。
例如:mchenys.processor.ViewBindProcessor
如果你觉得这种方式太过繁琐,别急,我马上介绍一种比较简便的方式,使用Google开源的AutoService库来生成服务文件,这个需要在processor库的gradle文件中添加2行依赖:
implementation 'com.google.auto.service:auto-service:1.0-rc6'
//gradle 5.0会忽略annotationProcessor,因此需要手动添加
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
然后在自定义的注解处理器类上添加@AutoService(Processor.class)注解,这样就搞定了。编译processor库,你会发现在\build\classes目录下自动生成了服务文件:
ViewInjector工具类介绍
在正式介绍ViewBindProcessor之前,我先介绍下这个工具类,代码如下:
/**
* 注解注入工具类
*/
public class ViewInjector {
//定义注解生成类实现的接口
public interface ViewBinder<T> {
void bind(T t);
}
//存储已实例化过的注解生成类
static final Map<Class, ViewBinder> BINDERS = new LinkedHashMap<>();
//给注解生成类注入Activity
public static void inject(Object target) {
Class clazz = target.getClass();
ViewBinder viewBinder = BINDERS.get(clazz);
if (viewBinder == null) {
try {
viewBinder = (ViewBinder) Class.forName(clazz.getName() + ViewBindProcessor.GEN_CLASS_SUFFIX)
.newInstance();
BINDERS.put(clazz, viewBinder);
} catch (Exception e) {
e.printStackTrace();
}
}
if (null != viewBinder) {
viewBinder.bind(target); //执行绑定
}
}
}
代码也很简单,我的思路就是在调用inject的方法时候给说有实现了ViewBinder接口的类注入Activity的实例,拿到了Activity的实例,我就可以操作findViewById得到对应的View了,进而又可以给View设置点击监听了,至于ViewBinder接口的实现类从哪里来?当然是通过注解处理器动态生成啦,因此ViewBindProcessor的作用就是找到使用了指定注解类的元素,然后生成对应的代码。
ViewBindProcessor
直接贴代码,看注释也能看懂啥意思了
/**
* 自定义编译时注解解释器
*/
@AutoService(Processor.class) //自动的注册解释器
@SupportedAnnotationTypes({"mchenys.annotations.InjectView",
"mchenys.annotations.InjectMethod"})//指定这个注解处理器是给哪些注解使用的.
@SupportedSourceVersion(SourceVersion.RELEASE_7)//设置java版本
public class ViewBindProcessor extends AbstractProcessor {
public static final String GEN_CLASS_SUFFIX = "Injector";
private Types mTypeUtils;//提供了和类型相关的一些操作,如获取父类、判断两个类是不是父子关系等
private Elements mElementUtils;//提供了一些和元素相关的操作,如获取所在包的包名等
private Filer mFiler;//Filer用于文件操作,用它去创建生成的代码文件
private Messager mMessager;//用来打印信息的,它会打印出Element所在的源代码
//init方法是初始化的地方,我们可以通过ProcessingEnvironment获取到很多有用的工具类
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mTypeUtils = processingEnv.getTypeUtils();
mElementUtils = processingEnv.getElementUtils();
mFiler = processingEnv.getFiler();
mMessager = processingEnv.getMessager();
}
//该方法相当于处理器的main函数,在这里写你的扫描/评估和处理逻辑,以及生成java代码,RoundEnvironment可以查询包含
//特定注解的被注解元素
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//获取与InjectView注解相关的所有元素
Set<? extends Element> elements = parse2Set(annotations, roundEnv);
//获取分类后的集合
Map<TypeElement, List<Element>> elementMap = parse2Map(elements);
//遍历map,生成代码
for (Map.Entry<TypeElement, List<Element>> entry : elementMap.entrySet()) {
//生成注入代码
generateInjectorCode(entry.getKey(), entry.getValue());
}
return true;
}
/**
* 整合所有使用了注解的元素
*
* @param annotations
* @param roundEnv
* @return
*/
private Set<? extends Element> parse2Set(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set<Element> set = new LinkedHashSet<>();
/* for (TypeElement annotation : annotations) {
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(annotation);
set.addAll(elements);
}*/
//为保证字段先解析,这里手动添加
set.addAll(roundEnv.getElementsAnnotatedWith(InjectView.class));
set.addAll(roundEnv.getElementsAnnotatedWith(InjectMethod.class));
return set;
}
/**
* 将元素按TypeElement进行分类
*
* @param elements
* @return
*/
private Map<TypeElement, List<Element>> parse2Map(Set<? extends Element> elements) {
Map<TypeElement, List<Element>> elementMap = new LinkedHashMap<>();
//遍历所有被InjectView注释的元素
for (Element element : elements) {
//获取所在类的信息(一般指的是Activity/Fragment)
TypeElement clazz = (TypeElement) element.getEnclosingElement();
//按类存入map中,key相同会被覆盖,key代表的是元素所在的类信息,value是一个List
//这样就可以把某个类和该类上使用了特定注解所有元素进行关联
addElement(elementMap, clazz, element);
}
return elementMap;
}
//递归判断android.view.View是不是其父类
private boolean isView(TypeMirror type) {
//获取所有父类类型
List<? extends TypeMirror> supers = mTypeUtils.directSupertypes(type);
if (supers.size() == 0) {
return false;
}
for (TypeMirror superType : supers) {
if (superType.toString().equals("android.view.View") || isView(superType)) {
return true;
}
}
return false;
}
/**
* 添加元素到map集合中
*
* @param elementMap key=clazz value=List<Element>
* @param clazz 指定元素所在类的信息
* @param element 指定元素
*/
private void addElement(Map<TypeElement, List<Element>> elementMap,
TypeElement clazz, Element element) {
List<Element> list = elementMap.get(clazz);
if (list == null) {
list = new ArrayList<>();
elementMap.put(clazz, list);
}
list.add(element);
}
/**
* 生成注入代码
*
* @param typeElement 元素所在类的Element
* @param elements 需要注入的元素
*/
private void generateInjectorCode(TypeElement typeElement, List<Element> elements) {
//取出所在的类名
String className = typeElement.getSimpleName().toString();
String qualifiedName = typeElement.getQualifiedName().toString();
//该类所在的包名
String packageName = mElementUtils.getPackageOf(typeElement).asType().toString();
//存储所有的字段名
Map<Integer, String> fieldMap = new HashMap<>();
//开始编写java类
StringBuilder builder = new StringBuilder();
builder.append("// Generated code from ViewInjector. Do not modify!\n");
builder.append("package " + packageName + ";\n"); //声明包
builder.append("import mchenys.ViewInjector.ViewBinder;\n");//导包
builder.append("import " + qualifiedName + ";\n");//导包
builder.append("import android.view.View;\n");//导包
builder.append("public class " + className + GEN_CLASS_SUFFIX + " implements ViewBinder<" + className + "> {\n");//声明类实现ViewBinder接口
builder.append("\t@Override\n");
builder.append("\tpublic void bind(" + className + " args" + "){\n");//定义方法
//解析注解
for (Element element : elements) {
if (element.getKind() == ElementKind.FIELD) {
//如果不是View的子类则报错
if (!isView(element.asType())) {
mMessager.printMessage(Diagnostic.Kind.ERROR, "is not a View", element);
}
//处理字段的注入
//获取变量类型
String type = element.asType().toString();
//获取变量名
String fieldName = element.getSimpleName().toString();
//id
int resourceId = element.getAnnotation(InjectView.class).value();
//将id和字段名关联存储
fieldMap.put(resourceId, fieldName);
//开始findViewById并赋值
builder.append("\t\targs." + fieldName + "=(" + type + ")args.findViewById(" + resourceId + ");\n");
} else if (element.getKind() == ElementKind.METHOD) {
//处理方法
int[] ids = element.getAnnotation(InjectMethod.class).value();
String methodName = element.getSimpleName().toString();
for (int id : ids) {
//获取对应id的字段名
String fieldName = fieldMap.get(id);
if (null != fieldName) {
builder.append("\t\targs." + fieldName + ".setOnClickListener(new View.OnClickListener() {\n");
} else {
builder.append("\t\targs.findViewById(" + id + ").setOnClickListener(new View.OnClickListener() {\n");
}
builder.append("\t\t\t@Override\n")
.append("\t\t\tpublic void onClick(View v) {\n")
.append("\t\t\t\targs." + methodName + "(v);\n")
.append("\t\t\t}\n")
.append("\t\t});\n");
}
} else {
mMessager.printMessage(Diagnostic.Kind.ERROR, "is not a FIELD or METHOD ", element);
}
}
//添加结尾
builder.append("\t}\n").append("}");
//生成代码
generateCode(className + GEN_CLASS_SUFFIX, builder.toString());
}
/**
* 生成代码
*
* @param className java文件名
* @param code java代码
*/
private void generateCode(String className, String code) {
try {
JavaFileObject file = mFiler.createSourceFile(className);
Writer writer = file.openWriter();//拿到输出流
writer.write(code);
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
上面类中出现了很多Element接口,在process方法中使用getElementsAnnotatedWith获取到的都是Element接口,其实我们用Element.getKind获取到类型之后可以将他们强转成对应的子接口,这些子接口提供了一些针对性的操作,这些子接口有:
TypeElement:表示一个类或接口元素。
PackageElement:表示一个包元素。
VariableElement:表示一个属性、enum 常量、方法或构造方法参数、局部变量或异常参数。
ExecutableElement:表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注释类型元素。
ok,注解处理器弄好了,该如何使用呢?在Android项目的app的gradle文件中添加下依赖:
annotationProcessor project(path: ':processor')
implementation project(path: ':processor')
1
2
很简单,简单说一下这里为什么要用同时使用annotationProcessor 和implementation 来添加依赖,前者是专门用来添加注解处理器的,后者的添加是因为,我的ViewInjector工具类以及InjectMethod和InjectView注解都放在了processor库中,所以还需要单独引进来,你也可以把工具类和注解类单独放到一个java Library,这样Android项目和processor库都需要添加一下注解所在的库。
ButterKnife 7.0.1之后的版本就是把注解和处理器单独分开为独立的库,使用的时候需要同时引入2者,也就是这样:
dependencies {
implementation 'com.jakewharton:butterknife:10.2.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.1'
}
这里有个坑,顺带提一下,在很多文章,你会看到引入处理器库的时候是使用android-apt插件的,这个插件在gradle2.2之后的版本被annotationProcessor替代了,官方已经宣布不再维护android-apt这个插件了,如果你的gradle版本比较低,那么引入的步骤就比较麻烦了,大致有3个步骤如下:
1.android工程(project)的build.gradle下的dependencies下添加:
classpath ‘com.neenbedankt.gradle.plugins:android-apt:1.8’
2.android工程(app)的build.gradle的dependencies中以apt的方式引入注解处理器,例如:
apt project(’:processor’)
3.android工程((app)的build.gradle文件顶部添加:
apply plugin: ‘com.neenbedankt.android-apt’
支持Fragment的使用
先来看看使用效果:
在MainActivity的内部添加了一个Fragment,代码如下:
public class MainActivity extends AppCompatActivity {
@InjectView(R.id.btn_title)
TextView mTitleBtn;
@InjectView(R.id.btn_desc)
TextView mDescBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//注入
ViewInjector.inject(this);
//注入后就可以使用了
mTitleBtn.setText("我是标题");
mDescBtn.setText("我是描述");
getSupportFragmentManager().beginTransaction()
.replace(R.id.fl_container, new MainFragment())
.commitAllowingStateLoss();
}
//设置点击事件
@InjectMethod({R.id.btn_title, R.id.btn_desc})
public void showToast(View v) {
switch (v.getId()) {
case R.id.btn_title:
Toast.makeText(this, "标题被点击了", Toast.LENGTH_SHORT).show();
break;
case R.id.btn_desc:
Toast.makeText(this, "描述被点击了", Toast.LENGTH_SHORT).show();
break;
}
}
//内部的Fragment
public static class MainFragment extends Fragment {
private View rootView;
@InjectView(R.id.tv_info)
TextView mInfoTv;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
if (rootView == null) {
rootView = inflater.inflate(R.layout.fragment_main, container, false);
//注入
ViewInjector.inject(this, rootView);
}else{
ViewParent parent = rootView.getParent();
if (null != parent) {
((ViewGroup) parent).removeView(rootView);
}
}
return rootView;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mInfoTv.setText("我是Fragment");
}
@InjectMethod(R.id.tv_info)
public void onClick(View view) {
Toast.makeText(getActivity(), "fragment被点击了", Toast.LENGTH_SHORT).show();
}
}
}
在Fragment注入的地方是用的是ViewInjector.inject(this, rootView);进行注入,很显然,这里需要对ViewInjector以及注解处理器做相应的修改
修改ViewInjector
/**
* 注解注入工具类
*/
public class ViewInjector {
//定义注解生成类实现的接口
public interface ViewBinder<T> {
void bind(T t, Object source);
}
//存储已实例化过的注解生成类
static final Map<Class, ViewBinder> BINDERS = new LinkedHashMap<>();
/**
* 注入Activity时用这个
*
* @param activity
*/
public static void inject(Object activity) {
inject(activity, activity);
}
/**
* 给注解生成类注入值
*
* @param target Activity/Fragment
* @param source target=Activity时,是Activity,如果是target=Fragment时,就传根Fragment的根View
*/
public static void inject(Object target, Object source) {
Class clazz = target.getClass();
ViewBinder viewBinder = BINDERS.get(clazz);
if (viewBinder == null) {
try {
String packageName = clazz.getPackage().getName();
String className = clazz.getSimpleName();
String qualifiedName = packageName + "." + className + ViewBindProcessor.GEN_CLASS_SUFFIX;
viewBinder = (ViewBinder) Class.forName(qualifiedName).newInstance();
BINDERS.put(clazz, viewBinder);
} catch (Exception e) {
e.printStackTrace();
}
}
if (null != viewBinder) {
viewBinder.bind(target, source); //执行绑定
}
}
}
修改ViewBindProcessor
主要是generateInjectorCode方法的修改,如下所示:
/**
* 生成注入代码
*
* @param typeElement 元素所在类的Element
* @param elements 需要注入的元素
*/
private void generateInjectorCode(TypeElement typeElement, List<Element> elements) {
//取出所在的类名
String className = typeElement.getSimpleName().toString();
String qualifiedName = typeElement.getQualifiedName().toString();
//该类所在的包名
String packageName = mElementUtils.getPackageOf(typeElement).asType().toString();
//存储所有的字段名
Map<Integer, String> fieldMap = new HashMap<>();
//开始编写java类
StringBuilder builder = new StringBuilder();
builder.append("// Generated code from ViewInjector. Do not modify!\n");
builder.append("package " + packageName + ";\n"); //声明包
builder.append("import mchenys.ViewInjector.ViewBinder;\n");//导包
builder.append("import " + qualifiedName + ";\n");//导包
builder.append("import android.view.*;\n");//导包
builder.append("public class " + className + GEN_CLASS_SUFFIX + " implements ViewBinder<" + className + "> {\n");//声明类实现ViewBinder接口
builder.append("\t@Override\n");
builder.append("\tpublic void bind(" + className + " target, Object source){\n");//定义方法
//判断是否是Fragment
boolean isFragment = isFragment(typeElement.asType());
if (isFragment) {
builder.append("\t\tViewGroup rootView = (ViewGroup)source;\n");
}
//解析注解
for (Element element : elements) {
if (element.getKind() == ElementKind.FIELD) {
//如果不是View的子类则报错
if (!isView(element.asType())) {
mMessager.printMessage(Diagnostic.Kind.ERROR, "is not a View", element);
}
//处理字段的注入
//获取变量类型
String type = element.asType().toString();
//获取变量名
String fieldName = element.getSimpleName().toString();
//id
int resourceId = element.getAnnotation(InjectView.class).value();
//将id和字段名关联存储
fieldMap.put(resourceId, fieldName);
//开始findViewById并赋值
if (isFragment) {
builder.append("\t\ttarget." + fieldName + "=(" + type + ")rootView.findViewById(" + resourceId + ");\n");
} else {
builder.append("\t\ttarget." + fieldName + "=(" + type + ")target.findViewById(" + resourceId + ");\n");
}
} else if (element.getKind() == ElementKind.METHOD) {
//处理方法
int[] ids = element.getAnnotation(InjectMethod.class).value();
//得到方法名
String methodName = element.getSimpleName().toString();
for (int id : ids) {
//获取对应id的字段名
String fieldName = fieldMap.get(id);
if (null != fieldName) {
builder.append("\t\ttarget." + fieldName + ".setOnClickListener(new View.OnClickListener() {\n");
} else {
if (isFragment) {
builder.append("\t\trootView");
}else{
builder.append("\t\ttarget");
}
builder.append(".findViewById(" + id + ").setOnClickListener(new View.OnClickListener() {\n");
}
builder.append("\t\t\t@Override\n")
.append("\t\t\tpublic void onClick(View v) {\n")
.append("\t\t\t\ttarget." + methodName + "(v);\n")
.append("\t\t\t}\n")
.append("\t\t});\n");
}
} else {
mMessager.printMessage(Diagnostic.Kind.ERROR, "is not a FIELD or METHOD ", element);
}
}
//添加结尾
builder.append("\t}\n").append("}");
//生成代码
generateCode(className + GEN_CLASS_SUFFIX, builder.toString());
}
以及添加了一个判断当前类是否是Fragment的方法
//递归判断是否是Fragment
private boolean isFragment(TypeMirror type) {
//获取所有父类类型
List<? extends TypeMirror> supers = mTypeUtils.directSupertypes(type);
if (supers.size() == 0) {
return false;
}
for (TypeMirror superType : supers) {
if (superType.toString().equals("android.support.v4.app.Fragment")
|| isFragment(superType)) {
return true;
}
}
return false;
}
搞定,编译android项目的时候,会看到项目的\build\generated\ap_generated_sources\debug\out目录下会多了2个自动生成的java文件:
其中MainFragmentInjector的代码如下:
// Generated code from ViewInjector. Do not modify!
package blog.csdn.net.mchenys.essayjoke;
import mchenys.ViewInjector.ViewBinder;
import blog.csdn.net.mchenys.essayjoke.MainActivity.MainFragment;
import android.view.*;
public class MainFragmentInjector implements ViewBinder<MainFragment> {
@Override
public void bind(MainFragment target, Object source){
ViewGroup rootView = (ViewGroup)source;
target.mInfoTv=(android.widget.TextView)rootView.findViewById(2131165328);
target.mInfoTv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
target.onClick(v);
}
});
}
}
内容就是在注解处理器的generateInjectorCode的生成结果。
扩展
对于InjectMethod声明的方法,如果想使用空参数的方法,亦或者执行方法的时候还有可能会有异常抛出,而我不想在APP上直接看到crash的弹弹窗,这些都是可以实现的,修改处理器的generateInjectorCode方法,针对方法处理的逻辑修改如下:
else if (element.getKind() == ElementKind.METHOD) {
//处理方法
int[] ids = element.getAnnotation(InjectMethod.class).value();
//得到方法名
String methodName = element.getSimpleName().toString();
//判断方法是否有参数,如果有的话参数类型必须是View的子类,且最多只能有一个参数
ExecutableElement executableElement = (ExecutableElement) element;
List<? extends VariableElement> parameters = executableElement.getParameters();
boolean hasParams = !parameters.isEmpty();
if (hasParams) {
VariableElement variableElement = parameters.get(0);
if (!isView(variableElement.asType())) {
mMessager.printMessage(Diagnostic.Kind.ERROR, variableElement.asType().toString()
+" is not a View parameter", variableElement);
}
if (parameters.size() != 1) {
mMessager.printMessage(Diagnostic.Kind.ERROR, "can be at most one parameter", executableElement);
}
}
for (int id : ids) {
//获取对应id的字段名
String fieldName = fieldMap.get(id);
//设置点击事件
if (null != fieldName) {
builder.append("\t\ttarget." + fieldName + ".setOnClickListener(new View.OnClickListener() {\n");
} else {
if (isFragment) {
builder.append("\t\trootView");
} else {
builder.append("\t\ttarget");
}
builder.append(".findViewById(" + id + ").setOnClickListener(new View.OnClickListener() {\n");
}
builder.append("\t\t\t@Override\n")
.append("\t\t\tpublic void onClick(View v) {\n")
.append("\t\t\t\ttry {\n"); //添加try cache
if (hasParams) {
builder.append("\t\t\t\t\ttarget." + methodName + "(v);\n"); //执行带参方法
}else{
builder.append("\t\t\t\t\ttarget." + methodName + "();\n");//执行空参方法
}
builder.append("\t\t\t\t} catch (Exception ex) {\n")
.append("\t\t\t\t\tex.printStackTrace();\n")
.append("\t\t\t\t}\n")
.append("\t\t\t}\n")
.append("\t\t});\n");
}