编译时注解
运行时注解是通过反射来实现的,这种方式的效率会受到一定的影响,因此现在大多数的开源注解框架都是采用编译时注解的方式实现的,这种方式是在编译的时候生成所需的代码,不会影响运行的效率,下面会通过仿照ButterKnife的框架来分别对这两种注解方式作出相应解释
@interface定义两个注解:BindView、OnClick
@Target(ElementType.FIELD) //设置注解使用范围是在变量中
@Retention(RetentionPolicy.RUNTIME) //当前注解生命时长
public @interface BindView {
int value();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OnClick {
int value();
}
定义ViewModelKnife提供调用
public class ViewModelKnife {
public static void bind(Activity activity) {
try {
//针对BindView注解
bindView(activity);
//针对OnClick注解
bindOnClick(activity);
} catch (Exception e) {
}
}
........
}
//相当于是对BindView注解的“解释”
private static void bindView(Activity activity) throws IllegalAccessException {
//1、获取字节码
Class extends Activity> aClass = activity.getClass();
//2、获取Activity中变量
Field[] declaredField = aClass.getDeclaredFields();
for (Field field : declaredField) {
//设置允许暴力反射
field.setAccessible(true);
//3、获取变量上的注释
BindView annotation = field.getAnnotation(BindView.class);
if (annotation != null) {
//4、获取注释上的值
int id = annotation.value();
//5、通过ID获取控件
View view = activity.findViewById(id);
//6、控件赋值给变量
field.set(activity, view);
}
}
}
//对OnClick注解的“解释”
private static void bindOnClick(final Activity activity) {
//1、获取字节码对象
final Class extends Activity> aClass = activity.getClass();
//2、获取所有的方法
Method[] declaredMethods = aClass.getDeclaredMethods();
//3、遍历所有的方法
for (final Method method : declaredMethods){
method.setAccessible(true);
//4、获取方法上的注释
OnClick annotation = method.getAnnotation(OnClick.class);
if (annotation != null) {
int id = annotation.value();
View view = activity.findViewById(id);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
//执行注解的方法
method.invoke(activity,null);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
});
}
}
}
客户端调用
public class ClientActivity extends AppCompatActivity {
//绑定资源
@BindView(R.id.textView)
TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_client);
//调用
ViewModelKnife.bind(this);
}
//点击事件
@OnClick(R.id.textView)
public void textOnClick(){
Toast.makeText(this,"点击",Toast.LENGTH_SHORT).show();
}
}
可以看到运行时注解是比较简单的,其核心就是利用反射,查找注解的资源,进而有下一步的操作,但是凡涉及反射的内容总会影响效率,因此现在大多采用编译时注解,这种注解的核心原理是在编译的时候,对每一个注解对象所在的环境生成一个对应的类去处理注解,所有的绑定资源之类的工作是在编译时期完成的,是不会影响效率的
此类框架,一般需要建立多个module,本例如下:
定义注解: viewmodel-annotation
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
int value();
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface OnClick {
int[] value();
}
定义注解处理器:viewmodel-compiler
注解处理器其实只有两个工作:
注意:编译时注解处理器,借助APT包管理工具实现,在编译时期扫描和处理注解信息。为了使自定义的注解处理器生效,让java编译器或能够找到自定义的注解处理器我们需要对其进行注册和打包,有如下两种方式:
dependencies {
//注册该注解处理器
compile 'com.google.auto.service:auto-service:1.0-rc2'
compile project (':viewmodel-annotation')
}
基本代码:
@AutoService(Processor.class)
public class BindViewModelProcessor extends AbstractProcessor {
/**
* 文件相关的辅助类
*/
private Filer mFiler;
/**
* 元素相关的辅助类
*/
private Elements mElementUtils;
/**
* 日志相关的辅助类
*/
private Messager mMessager;
/**
* 针对每一个类生成一个代理类,例如MainActivity回生成MainActivity$$ViewInjector,则有如下
* 1、一个类对象,代表具体某个类的代理类生成的全部信息,本例中为AnnotatedClass
* 2、一个集合,存放上述类对象(用作遍历生成代理类),即是Map
*/
private Map mAnnotatedClassMap = new HashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
mElementUtils = processingEnv.getElementUtils();
mMessager = processingEnv.getMessager();
mFiler = processingEnv.getFiler();
}
@Override
public Set getSupportedAnnotationTypes() {
Set types = new LinkedHashSet<>();
types.add(BindView.class.getCanonicalName());
//返回该注解处理器支持的注解集合
return types;
}
@Override
public SourceVersion getSupportedSourceVersion() {
//返回支持的源码办法
return SourceVersion.latestSupported();
}
/**
* 1、收集信息
* 2、生成代理类,就是编译时生成的类
*/
@Override
public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnv) {
//清理一下,防止因为process的多次调用,生成重复的代理类
mAnnotatedClassMap.clear();
try {
//收集注解对象的信息
processBindView(roundEnv);
processOnClick(roundEnv);
} catch (IllegalArgumentException e) {
error(e.getMessage());
return true;
}
try {
//所有的信息是保存到了AnnotatedClass 中,所以生成类的核心也是在AnnotatedClass中
for (AnnotatedClass annotatedClass : mAnnotatedClassMap.values()) {
info("generating file for %s", annotatedClass.getFullClassName());
//生成代理类
annotatedClass.generateFinder().writeTo(mFiler);
}
} catch (Exception e) {
e.printStackTrace();
error("Generate file failed,reason:%s", e.getMessage());
}
return true;
}
}
在注解处理类中process是处理的核心方法,至于getSupportedSourceVersion()和getSupportedAnnotationTypes和init()方法都是固定的可以不用过多考虑,关于Element之类的可详见常用接口介绍,以processBindView为例来说明收集信息
private void processBindView(RoundEnvironment roundEnv) {
//遍历BindView注解的元素
for (Element element : roundEnv.getElementsAnnotatedWith(BindView.class)){
AnnotatedClass annotatedClass = getAnnotatedClass(element);
//将信息保存到一个被BindView注解标记的字段模型类中
BindViewField field = new BindViewField(element);
//保存信息到annotatedClass中
annotatedClass.addField(field);
System.out.print("p_element=" + element.getSimpleName() + ",p_set=" +
element.getModifiers());
}
}
BindViewField:
public class BindViewField {
//代表成员变量
private VariableElement mFieldElement;
private int mResId;
public BindViewField(Element element) {
if (element.getKind() != ElementKind.FIELD) {
throw new IllegalArgumentException(String.format("Only field can be annotated with
@%s",BindView.class.getSimpleName()));
}
mFieldElement = (VariableElement) element;
BindView bindView = mFieldElement.getAnnotation(BindView.class);
mResId = bindView.value();
if (mResId < 0) {
throw new IllegalArgumentException(String.format("value() in %s for field %s is not
valid",BindView.class.getSimpleName(),mFieldElement.getSimpleName()));
}
}
public Name getFileName() {
return mFieldElement.getSimpleName();
}
public int getResId() {
return mResId;
}
//TypeMirror 指的是java中的对应的类型
public TypeMirror getFieldType() {
return mFieldElement.asType();
}
}
生成代理类:
上述保存完成信息之后,就可以生成代理类了,其方式可以自己写,不过很繁琐,容易出错,因此可以使用第三方库javapoet生成,添加依赖
implementation 'com.squareup:javapoet:1.9.0'
public class AnnotatedClass {
/**
* 类名
*/
public TypeElement mClassElement;
/**
* 成员变量
*/
public List<BindViewField> mFiled;
/**
* 方法
*/
public List<OnClickMethod> mMethod;
/**
* 元素辅助类
*/
public Elements mElementUtils;
public AnnotatedClass(TypeElement mClassElement, Elements mElementUtils) {
this.mClassElement = mClassElement;
this.mElementUtils = mElementUtils;
this.mFiled = new ArrayList<>();
this.mMethod = new ArrayList<>();
}
//返回此类型元素的完全限定名称
public String getFullClassName() {
return mClassElement.getQualifiedName().toString();
}
public void addField(BindViewField field) {
mFiled.add(field);
}
public void addMethod(OnClickMethod method) {
mMethod.add(method);
}
public JavaFile generateFinder() {
/**
* 构建方法
*/
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("inject")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.addParameter(TypeName.get(mClassElement.asType()), "host", Modifier.FINAL)
.addParameter(TypeName.OBJECT, "source")
.addParameter(TypeUtil.FINDER, "finder");
/**
* 遍历添加类成员
*/
for (BindViewField field : mFiled) {
methodBuilder.addStatement("host.$N=($T)finder.findView(source,$L)"
, field.getFileName(), ClassName.get(field.getFieldType()), field.getResId());
}
/**
* 声明Listener
*/
if (mMethod.size() > 0) {
methodBuilder.addStatement("$T listener", TypeUtil.ONCLICK_LISTENER);
}
for (OnClickMethod method : mMethod) {
TypeSpec listener = TypeSpec.anonymousClassBuilder("")
.addSuperinterface(TypeUtil.ONCLICK_LISTENER)
.addMethod(MethodSpec.methodBuilder("onClick")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(TypeName.VOID)
.addParameter(TypeUtil.ANDROID_VIEW, "view")
.addStatement("host.$N()", method.getMethodName())
.build())
.build();
methodBuilder.addStatement("listener = $L ",listener);
for (int id : method.ids) {
methodBuilder.addStatement("finder.findView(source,$L)
.setOnClickListener(listener)", id);
}
}
String packageName = getPackageName(mClassElement);
String className = getClassName(mClassElement,packageName);
ClassName bindClassName = ClassName.get(packageName,className);
/**
* 构建类
*/
TypeSpec finderClass = TypeSpec.classBuilder(bindClassName.simpleName()+"$$Injector")
.addModifiers(Modifier.PUBLIC)
.addSuperinterface(ParameterizedTypeName.get(TypeUtil.INJECTOR,TypeName
.get(mClassElement.asType())))
.addMethod(methodBuilder.build())
.build();
return JavaFile.builder(packageName,finderClass).build();
}
private String getClassName(TypeElement type, String packageName) {
int packageLen = packageName.length() + 1;
return type.getQualifiedName().toString().substring(packageLen).replace(".","$");
}
private String getPackageName(TypeElement type) {
return mElementUtils.getPackageOf(type).getQualifiedName().toString();
}
}
定义API供用户使用:viewmodel-api
public class ViewModelKnife {
public ViewModelKnife() {
throw new AssertionError("No .instances");
}
private static final ActivityFinder ACTIVITY_FINDER = new ActivityFinder();
private static final ViewFinder VIEW_FINDER = new ViewFinder();
private static Map FINDER_MAP = new HashMap<>();
//绑定三种不同的上下文环境
public static void bind(Activity activity) {
bind(activity, activity, ACTIVITY_FINDER);
}
public static void bind(View view) {
bind(view, view);
}
public static void bind(Object host, View view) {
bind(host, view, VIEW_FINDER);
}
/**
* 获取目标类
* @param host
* @param source
* @param finder
*/
public static void bind(Object host, Object source, Finder finder) {
String className = host.getClass().getName();
try {
Injector injector = FINDER_MAP.get(className);
if (injector == null) {
/**找到对应的代理类,若是没有就生成一个,“$$Injector”只是一个标识,在生成代理类时,后面拼接的字
符,无实际意义*/
Class> finderClass = Class.forName(className + "$$Injector");
injector = (Injector) finderClass.newInstance();
FINDER_MAP.put(className, injector);
}
//代理类是实现的Injector接口,这里去调用代理类的inject方法
injector.inject(host, source, finder);
} catch (Exception e) {
e.printStackTrace();
}
}
}
至此编译时注解总体的内容介绍完了,使用方法和Butterknife是完全一样的,具体代码可见源码