运行时注解,反射+注解的形式实现
/**
* 定义了一个用在属性上的运行时注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView {
int value();
}
@Retention定义了该注解被保留的时间长短,有三种值可以选择
1、SOURCE,表示注解保留在源码层面,编译的后就会被擦除
2、CLASS,表示注解只保留到编译阶段
3、RUNTIME,表示注解保留到运行时,可以反射获取注解信息
@Target定义了注解用在什么地方
1、FIELD,属性
2、METHOD,方法函数
3、TYPE,类/接口
4、PARAMETER,参数
5、CONSTRUCTOR,构造方法
public class ButterKnife {
/**
* 调用bind方法时,使用反射的手段,设置view的值
*/
public static void bind(Activity activity) {
Class cls = activity.getClass();
// 获取Activity下的所有属性
Field[] fields = cls.getDeclaredFields();
for (Field field : fields) {
// 获取所有带BindView注解的属性
BindView bindView = field.getAnnotation(BindView.class);
if (bindView == null) continue;
field.setAccessible(true);
int viewId = bindView.value();
if (viewId != -1) {
View view = activity.findViewById(viewId);
try {
// 重新设置属性值
field.set(activity, view);
} catch (IllegalAccessException e) {
}
}
}
}
}
/**
* 使用就非常简单了
*/
@BindView(R.id.tv)
TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
mTextView.setText("测试运行时注解");
}
运行时注解实现起来非常简单,缺点也比较明显,因为在运行的时候,通过反射的形式,获取所有属性,所以非常损耗性能。
编译时注解就是为了解决这个问题而诞生。
基于APT的编译时注解
创建一个java lib,命名为butterknife_anaotation
/**
* 定义一个用于属性上的编译时注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
int value();
}
继续创建一个java lib(不是Android lib),命名为butterknife_compiler
定义一个ButterKnifeProcess类,继承于AbstractProcessor
/**
* 自定义注解处理器
*/
public class ButterKnifeProcess extends AbstractProcessor {
}
由于是自定义的注解处理器,所以需要注册到系统里,可以用Google提供的AutoService,但是发现有时候不太稳定,所以我
习惯手写注册。其实也很简单,就是在butterknife_compiler这个项目下的main下面新建一个resources/META-INF/services目录,目录下放置一个命名为javax.annotation.processing.Processor的文件,这些都是固定写法。
文件里,写上ButterKnifeProcess这个文件对应的路径即可。
com.zbh.butterknife_compiler.ButterKnifeProcess
接下来重点关注ButterKnifeProcess类里的内容
/**
* 自定义注解处理器
*/
public class ButterKnifeProcess extends AbstractProcessor {
private Filer filer;
private Elements mElements;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
// 生成文件时需要的Filer对象
filer = processingEnv.getFiler();
mElements = processingEnv.getElementUtils();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> supportTypes = new LinkedHashSet<>();
// 告知处理器,需要获取哪些注解
supportTypes.add(BindView.class.getCanonicalName());
return supportTypes;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 当我们编译时,注解处理器在处理时,这个方法就会被执行
Map<TypeElement, List<BindField>> map = collectInfo(roundEnvironment);
writeToFile(map);
return false;
}
/**
* 收集注解信息
*/
private Map<TypeElement, List<BindField>> collectInfo(RoundEnvironment roundEnvironment) {
// 获取所有带BindView注解的元素,不是只找某一个类的,而是声明了该注解处理器的项目里的所有类
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
Map<TypeElement, List<BindField>> map = new HashMap<>();
for (Element element : elements) {
// 判断这个注解是否是属性注解
if (element.getKind() == ElementKind.FIELD) {
// TypeElement表示注解属性所处的类文件元素
TypeElement typeElement = (TypeElement) element.getEnclosingElement();
List<BindField> list = map.get(typeElement);
if (list == null) {
list = new ArrayList<>();
map.put(typeElement, list);
}
// 获取注解的值
int viewId = element.getAnnotation(BindView.class).value();
// 获取属性的名字
String fieldName = element.getSimpleName().toString();
// 获取属性类型
TypeMirror type = element.asType();
list.add(new BindField(fieldName, viewId, type));
}
}
return map;
}
/**
* 借助javapoet生成文件
* 当然自己使用FileWrite去写入文件也是没问题的
*/
private void writeToFile(Map<TypeElement, List<BindField>> map) {
for (TypeElement typeElement : map.keySet()) {
// 包名
String packetName = mElements.getPackageOf(typeElement).getQualifiedName().toString();
// 类名
String className = typeElement.getSimpleName().toString();
// 定义一个构造方法,私有属性,有一个参数是final XXActivity target
MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder()
.addParameter(ClassName.bestGuess(className), "target", Modifier.FINAL)
.addModifiers(Modifier.PRIVATE);
// 一个类里的所有BindView注解都放在list里了
List<BindField> list = map.get(typeElement);
for (BindField field : list) {
ClassName typeName = ClassName.bestGuess(field.mType.toString());
// 构造方法里添加代码
constructorBuilder.addStatement("target.$L=($T)target.findViewById($L)", field.mFieldName, typeName, field.mViewId);
}
// 定义一个public的类,名字是className + "_$_ViewBinder"
TypeSpec typeSpec = TypeSpec.classBuilder(className + "_$_ViewBinder").addModifiers(Modifier.PUBLIC)
// .addField(fieldSpec)
.addMethod(constructorBuilder.build())
// .addMethod(methodBuilder.build())
.build();
try {
/**
* 生成文件,在各自module的build/generated/source/
* apt/debug(release)/使用BindView注解的类所处的包/className_$_ViewBinder
*/
JavaFile javaFile = JavaFile.builder(packetName, typeSpec).build();
javaFile.writeTo(filer);
} catch (Exception e) {
}
}
}
static class BindField {
String mFieldName;
int mViewId;
TypeMirror mType;
BindField(String fieldName, int viewId, TypeMirror type) {
this.mFieldName = fieldName;
this.mViewId = viewId;
this.mType = type;
}
}
}
我们再来看看通过上面javapoet生成的代码
public class MainActivity_$_ViewBinder {
private MainActivity_$_ViewBinder(final MainActivity target) {
target.mTv3=(TextView)target.findViewById(2131165325);
target.mTv4=(TextView)target.findViewById(2131165326);
}
}
最后一步,我们怎么调用上面javapoet生成的代码类呢,从而节省findViewById操作呢?答案是反射。
public class ButterKnife {
/**
* 静态bind方法,通过反射获取XXActivity_$_ViewBinder类的对象,因为findViewById都在其构造方法里
* 因此,只要创建了javapoet生成的代码类对象,就是相当于执行了findViewById操作。
*/
public static void bind(Activity target) {
try {
// 反射需要全类名
String binderName = target.getPackageName() + "." + target.getLocalClassName() + "_$_ViewBinder";
Class binderCls = Class.forName(binderName);
Constructor constructor = binderCls.getDeclaredConstructor(target.getClass());
constructor.setAccessible(true);
constructor.newInstance(target);
} catch (Exception e) {
}
}
}