- 首先理解反射(很必要)
- 注解分为两种类型
核心类
@Target
表明当前注解可以使用在哪种元素上。
ElementType有以下几种:
- CONSTRUCTOR 构造器声明
- FIELD 域声明(包括enum实例)
- LOCAL_VARIABLE 局部变量声明
- METHOD 方法声明
- PACKAGE 包声明
- PARAMETER 参数声明
- TYPE 类、接口、注解类型、enum类型
@Retention
表示需要在什么级别保存该注解信息。
-
SOURCE源码级别,注解将被编译器丢弃,只存在源码中,其功能是与编译器交互,用于代码检测,如@Override,@SuppressWarings,许多框架如Dragger就是使用这个级别的注解,这个级别的框架额外效率损耗发生在编译时。 -
CLASS字节码级别,注解存在源码与字节码文件中,主要用于编译时生成而外的文件,如XML,Java文件等,这个级别需要添加JVM加载时候的代理(javaagent),使用代理来动态修改字节码文件(由于Android虚拟机并不支持所以本专题不会再做介绍,在Android中可以使用aspectJ来实现类似这个级别的功能)。 -
RUNTIME运行时级别,注解存在源码,字节码与Java虚拟机中,主要用于运行时反射获取相关信息,许多框架如OrmLite就是使用这个级别的注解,这个级别的框架额外的效率损耗发生在程序运行时。 - 运行时注解详解
运行时注解,定义好注解,在源代码中通过反射拿到注解信息,解析即可
- 定义注解
@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface BanDragBack {
boolean ban() default false;
}2. 将注解加在类上
@BanDragBack(ban = true)
class SampleActivity 3. 运行时解析注解
BanDragBack banDragBack = (BanDragBack) activity.getAnnotation(BanDragBack.class);if(banDragBack.ban())以上是一个运行时注解的简单例子
- 编译时注解详解
参考网图,apt流程
以butterknife为例,给大家做介绍
编译时注解的实现需要以下两个库支持。
1、com.squareup:javapoet 提供注解处理器API
2、com.google.auto.service:auto-service javapoet,auto-service提供自动注册处理器的功能。
若要自动生成代码,需要先创建一个注解管理器。
继承AbstractProcessor 有四个方法需要重载。
@Override void init(ProcessingEnvironment)
获取处理注解的上下文信息,一般用于获取文件句柄,自定义参数等逻辑。
@Override void getSupportedSourceVersion()
直接return SourceVersion.latestSupported();,固定套路。
@Override Set getSupportedAnnotationTypes()
告诉注解处理器支持哪些注解。
@Override boolean process(Set<? extends TypeElement>, RoundEnvironment)
第一个参数Set即为扫描出来的被标记注解的类/字段的集合,在这个方法中根据这些信息生成代码。
这个方法的返回值和事件分发的意义比较类似。如果return true,表示这些注解扫描结果被『我这个』注解处理器『消化』掉了,其他处理器不需要再处理。
> 如果多个Module中的代码都标记了注解,那么每扫描一个Module中的代码,注解处理器就会被回调一次。
以butterKnife为例,大家都在使用,便于理解
- 在annotation中写上注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
int value();
}创建类processor继承AbstractProcessor
@AutoService(Processor.class)
public class ButterKnifeProcessor extends AbstractProcessor {
private Elements elementUtils;
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
elementUtils = processingEnvironment.getElementUtils();
filer = processingEnvironment.getFiler();
}
// 指定SourceVersion
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}指定我们所需要的annotation
// 指定processortype
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
Set<Class<? extends Annotation>> supportedAnnotations = getSupportedAnnotations();
for (Class<? extends Annotation> supportedAnnotation : supportedAnnotations) {
types.add(supportedAnnotation.getCanonicalName());
}
return types;
}
private Set<Class<? extends Annotation>> getSupportedAnnotations() {
Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
annotations.add(BindView.class);
return annotations;
}在process中处理annotation
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
// 将获取到的bindview细分到每个class
Map<Element, List<Element>> elementMap = new LinkedHashMap<>();
for (Element element : elements) {
// 返回activity
Element enclosingElement = element.getEnclosingElement();
List<Element> bindViewElements = elementMap.get(enclosingElement);
if (bindViewElements == null) {
bindViewElements = new ArrayList<>();
elementMap.put(enclosingElement, bindViewElements);
}
bindViewElements.add(element);
}生成所需的代码
// 生成代码
for (Map.Entry<Element, List<Element>> entrySet : elementMap.entrySet()) {
Element enclosingElement = entrySet.getKey();
List<Element> bindViewElements = entrySet.getValue();
// public final class xxxActivity_ViewBinding implements Unbinder
// 获取activity的类名
String activityClassNameStr = enclosingElement.getSimpleName().toString();
System.out.println("------------->" + activityClassNameStr);
ClassName activityClassName = ClassName.bestGuess(activityClassNameStr);
ClassName unBinderClassName = ClassName.get("com.fastaoe.butterknife", "Unbinder");
TypeSpec.Builder classBuilder =
TypeSpec.classBuilder(activityClassNameStr + "_ViewBinding")
.addModifiers(Modifier.FINAL, Modifier.PUBLIC)
.addSuperinterface(unBinderClassName)
// 添加属性 private MainActivity target;
.addField(activityClassName, "target", Modifier.PRIVATE);
// unbind()
ClassName callSuperClassName = ClassName.get("android.support.annotation", "CallSuper");
MethodSpec.Builder unbindMethodBuilder = MethodSpec.methodBuilder("unbind")
.addAnnotation(Override.class)
.addAnnotation(callSuperClassName)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL);
// 构造函数
MethodSpec.Builder constructorMethodBuilder = MethodSpec.constructorBuilder()
.addParameter(activityClassName, "target")
.addModifiers(Modifier.PUBLIC)
// this.target = target
.addStatement("this.target = target");
for (Element bindViewElement : bindViewElements) {
// textview
String fieldName = bindViewElement.getSimpleName().toString();
// Utils
ClassName utilsClassName = ClassName.get("com.fastaoe.butterknife", "Utils");
// R.id.textview
int resourceId = bindViewElement.getAnnotation(BindView.class).value();
// target.textview = Utils.findViewById(target, R.id.textview)
constructorMethodBuilder.addStatement("target.$L = $T.findViewById(target, $L)", fieldName, utilsClassName, resourceId);
// target.textview = null
unbindMethodBuilder.addStatement("target.$L = null", fieldName);
}
classBuilder.addMethod(unbindMethodBuilder.build())
.addMethod(constructorMethodBuilder.build());
// 获取包名
String packageName = elementUtils.getPackageOf(enclosingElement).getQualifiedName().toString();
try {
JavaFile.builder(packageName, classBuilder.build())
.addFileComment("自己写的ButterKnife生成的代码,不要修改!!!")
.build().writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}在butterknife中编写注入代码
public class ButterKnife {
public static Unbinder bind(Activity activity) {
try {
Class<? extends Unbinder> bindClazz = (Class<? extends Unbinder>)
Class.forName(activity.getClass().getName() + "_ViewBinding");
// 构造函数
Constructor<? extends Unbinder> bindConstructor = bindClazz.getDeclaredConstructor(activity.getClass());
Unbinder unbinder = bindConstructor.newInstance(activity);
return unbinder;
} catch (Exception e) {
e.printStackTrace();
}
return Unbinder.EMPTY;
}
}最后使用
public class MainActivity extends AppCompatActivity {
@BindView(R.id.textview)
TextView textview;
private Unbinder bind;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bind = ButterKnife.bind(this);
textview.setText("修改后的文字");
}
@Override
protected void onDestroy() {
bind.unbind();
super.onDestroy();
}
}以上是butterknife的原理
下面在给大家介绍在跳转协议框架中的使用,根据注解自动生成跳转字典 ,其实就是解藕模块调用,让组件间、模块间是实现完全的独立。ARouter是:阿里巴巴自研路由框架,主要解决组件间、模块间的 界面跳转 问题。这部分具体可参考阿里巴巴的跳转协议框架Arouter,下面是一个简易版本的介绍
使用介绍
1、注册跳转协议(@MyRoute)
代码示例:
@MyRoute(
pageType = "main",
des = "首页"
)
public class CenterActivity {
···
}
对支持跳转协议的落地页(Activity)加上@MyRoute,参数pageType为必填,值为该落地页对应的pageType。参数des为选填,值为该落地页说明。
MyRouter会在编译时扫描@WbuRoute,然后生成跳转目录代码。(此部分代码为自动生成,每次编译时可生成一遍)
MyRouter生成的目录代码:
public class LibRouteCatalog {
public static Map<String, String> ROUTE = new HashMap<String, String>();
static {
//"首页"
ROUTE.put("main", "com.sample.CenterActivity");
//...
}
}在我们的代码中调用生成的代码即可
String targetLib = LibRouteCatalog.ROUTE.get(pagetype);那么为了实现此功能,我们需要做哪些工作呢
首先,我们需要自定义注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)public
@interface MyRoute
{
String pageType();
String des() default "";
}接下来编写注解处理器
public class MyRouteProcessor extends AbstractProcessor {
private Builder mRouteMapFieldSpec;
private String mModuleName;
private String mOutputDir;public MyRouteProcessor() {}public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment); //这里是配置生成文件的目录
this.mModuleName = (String)processingEnvironment.getOptions().get("module_name");
this.mOutputDir = (String)processingEnvironment.getOptions().get("route_catalog_output_path");
this.mOutputDir = this.mOutputDir + "/src/main/java";}}
public SourceVersion getSupportedSourceVersion() //固定套路
{
return SourceVersion.latestSupported();
}public Set<String> getSupportedAnnotationTypes() { //告诉注解器支持哪些注解,这里是我们的Myroute注解
Set<String> annotations = new HashSet();
annotations.add(MyRoute.class.getCanonicalName());
return annotations;
}
//重点来了,,,自动生成我们需要的类信息
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//从当前环境中拿到带有注解信息的元素
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(MyRoute.class);
if (elements.isEmpty())
{
return false;
}
else {
ClassName mapClassName = ClassName.get("java.util", "Map", new String[0]);
ClassName stringClassName = ClassName.get("java.lang", "String", new String[0]);
TypeName mapTypeName = ParameterizedTypeName.get(mapClassName, new TypeName[]{stringClassName, stringClassName});
if (this.mRouteMapFieldSpec == null) {
ClassName hashMapClassName = ClassName.get("java.util", "HashMap", new String[0]);
TypeName hashMapTypeName = ParameterizedTypeName.get(hashMapClassName, new TypeName[]{stringClassName, stringClassName});
this.mRouteMapFieldSpec = FieldSpec.builder(mapTypeName, "ROUTE", new Modifier[0]);
this.mRouteMapFieldSpec.addModifiers(new Modifier[]{Modifier.PUBLIC, Modifier.STATIC}).initializer("new $T()", new Object[]{hashMapTypeName}).build();
}
com.squareup.javapoet.CodeBlock.Builder mapDataCodeBlock = CodeBlock.builder();
Iterator var17 = elements.iterator();
while(var17.hasNext()) {
Element e = (Element)var17.next();
if (e instanceof TypeElement) {
TypeElement typeElement = (TypeElement)e;
String className = typeElement.getQualifiedName().toString();
MyRoute myRoute = (MyRoute)e.getAnnotation(MyRoute.class);
String pageType = myRoute.pageType();
String des = myRoute.des();
mapDataCodeBlock.add("//$S\nROUTE.put($S, $S);\n", new Object[]{des, pageType, className});
}
}
TypeSpec classType = TypeSpec.classBuilder(this.mModuleName + "RouteCatalog")
.addModifiers(new Modifier[]{Modifier.PUBLIC})
.addField(this.mRouteMapFieldSpec.build())
.addStaticBlock(mapDataCodeBlock.build()).build();
JavaFile javaFile = JavaFile.builder("com.example.route", classType).build();
File file = new File(this.mOutputDir);
try {
javaFile.writeTo(file);
} catch (IOException var15) {
var15.printStackTrace();
}
return true;
}}总结
编译时注解给项目带来很大的想象空间,可以将一些具有机械化,可以合并同类项等特征的代码通过注解自动生成。提升开发效率,增加代码容错率。在享受编译时注解带来的便利的同时,也会一定程度上拖累本地编译速度。不过这就是一个取舍的问题了。