本篇学习的目的是撸一个编译时注解框架:
1、本篇只是学习小结,需要详细的讲解可参考:
- Android注解快速入门和实用解析:http://www.jianshu.com/p/9ca78aa4ab4d
- Android 如何编写基于编译时注解的项目:http://blog.csdn.net/lmj623565791/article/details/51931859
2、基础小结
-
元注解:
元注解是由java提供的基础注解,负责注解其它注解。
元注解有:@Retention:注解保留的生命周期 @Target:注解对象的作用范围。 @Inherited:@Inherited标明所修饰的注解,在所作用的类上,是否可以被继承。 @Documented:如其名,javadoc的工具文档化,一般不关心。
-
@Retention:
Retention说标明了注解被生命周期,对应RetentionPolicy的枚举,表示注解在何时生效:SOURCE:只在源码中有效,编译时抛弃,如@Override。 CLASS:编译class文件时生效。 RUNTIME:运行时才生效。
-
@Target:
Target标明了注解的适用范围,对应ElementType枚举,明确了注解的有效范围。TYPE:类、接口、枚举、注解类型。 FIELD:类成员(构造方法、方法、成员变量)。 METHOD:方法。 PARAMETER:参数。 CONSTRUCTOR:构造器。 LOCAL_VARIABLE:局部变量。 ANNOTATION_TYPE:注解。 PACKAGE:包声明。 TYPE_PARAMETER:类型参数。 TYPE_USE:类型使用声明。
-
@Inherited
注解所作用的类,在继承时默认无法继承父类的注解。除非注解声明了 @Inherited。同时Inherited声明出来的注解,只对类有效,对方法/属性无效。
3、先撸一个运行时注解,直接上代码:
注解类:
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView {
int value() default -1;
}
运用例子:
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv)
private TextView mView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getAllAnnotationView();
mView.setText("注解成功");
}
/**
* 解析注解,获取控件
*/
private void getAllAnnotationView() {
//获得成员变量
Field[] fields = this.getClass().getDeclaredFields();
for (Field field : fields) {
try {
//判断注解
if (field.getAnnotations() != null) {
//确定注解类型
if (field.isAnnotationPresent(BindView.class)) {
//允许修改反射属性
field.setAccessible(true);
BindView getViewTo = field.getAnnotation(BindView.class);
//findViewById将注解的id,找到View注入成员变量中
field.set(this, findViewById(getViewTo.value()));
}
}
} catch (Exception e) {
}
}
}
}
4、编译时注解:
运行时注解大多使用反射,这会影响效率,BufferKnife不会这样做,BufferKnife使用的是编译时注解,在编译时生成对应的java代码,实现注入。下面就一步步撸一个编译时注解:
-
首先,准备三个Module:
bindView-annotation:用于存放注解等,Java模块(创建时选javalib) bindView-compiler:用于编写注解处理器,Java模块 bindView-annotation:用于给用户提供使用的API,Android模块
-
注解模块的实现(bindView-annotation):
@Target({ElementType.FIELD}) @Retention(RetentionPolicy.CLASS) public @interface BindView { int value() default -1; }
-
注解处理器的实现(bindView-compiler):
先添加依赖:compile 'com.google.auto.service:auto-service:1.0-rc2' -
然后创建类继承AbstractProcessor,找不到这个类的看Module是不是javalib:
public class BindViewProcessor extends AbstractProcessor { private Filer mFileUtils; private Elements mElementUtils; private Messager mMessager; /** * 初始化一些工具。Elements几个子类:VariableElement //一般代表成员变量 * ExecutableElement //一般代表类中的方法 * TypeElement //一般代表代表类 * PackageElement //一般代表Package * 固定的写法 */ @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); mFileUtils = processingEnvironment.getFiler(); //跟文件相关的辅助类,生成JavaSourceCode。 mElementUtils = processingEnvironment.getElementUtils(); //跟元素相关的辅助类,帮助我们去获取一些元素相关的信息。 mMessager = processingEnvironment.getMessager(); //跟日志相关的辅助类。 } /** * 返回注解类型, 固定的写法 */ @Override public Set<String> getSupportedAnnotationTypes(){ Set<String> annotationTypes = new LinkedHashSet<String>(); annotationTypes.add(BindView.class.getCanonicalName()); return annotationTypes; } /** * 返回支持的源码版本,固定的写法 */ @Override public SourceVersion getSupportedSourceVersion(){ return SourceVersion.latestSupported(); } /** * 核心的方法 */ @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { return false; } }
-
process先不写,先写一个代理类ProxyInfo:
/** * 用来保存每个类的所有注解信息 */ public class ProxyInfo { private String packageName; //包名 private String proxyClassName; //代理类名 private TypeElement typeElement; //代表被代理的类 public Map<Integer, VariableElement> injectVariables = new HashMap<>(); //存被注解的成员变量 public static final String PROXY = "OnBindView"; //接口名,对应API模块 public ProxyInfo(Elements elementUtils, TypeElement typeElement) { this.typeElement = typeElement; PackageElement packageElement = elementUtils.getPackageOf(typeElement); //通过元素工具从类元素中拿到包元素 String packageName = packageElement.getQualifiedName().toString(); //通过包元素拿到包名 int packageLen = packageName.length()+1; //包名长度加1 String className //getQualifiedName()拿到的是com.test.MainActivity,这一步就拿到MainActivity,后面的replace是内部类的情况 = typeElement.getQualifiedName().toString().substring(packageLen).replace(".","$"); this.packageName = packageName; this.proxyClassName = className + "$$" + PROXY; //代理类名 } /** * 写代理类 * @return */ public String generateJavaCode() { StringBuilder builder = new StringBuilder(); builder.append("// Generated code. Do not modify!\n"); builder.append("package ").append(packageName).append(";\n\n"); builder.append("import com.bindview.*;\n"); builder.append('\n'); builder.append("public class ").append(proxyClassName).append(" implements " + ProxyInfo.PROXY + "<" + typeElement.getQualifiedName() + ">"); builder.append(" {\n"); generateMethods(builder); builder.append('\n'); builder.append("}\n"); return builder.toString(); } /** * 写findviewbyID * @return */ private void generateMethods(StringBuilder builder) { builder.append("@Override\n "); builder.append("public void inject(" + typeElement.getQualifiedName() + " host, Object source ) {\n"); for (int id : injectVariables.keySet()) { VariableElement element = injectVariables.get(id); String name = element.getSimpleName().toString(); String type = element.asType().toString(); builder.append(" if(source instanceof android.app.Activity){\n"); builder.append("host." + name).append(" = "); builder.append("(" + type + ")(((android.app.Activity)source).findViewById( " + id + "));\n"); builder.append("\n}else{\n"); builder.append("host." + name).append(" = "); builder.append("(" + type + ")(((android.view.View)source).findViewById( " + id + "));\n"); builder.append("\n};"); } builder.append(" }\n"); } public String getProxyClassFullName() { return packageName + "." + proxyClassName; } public TypeElement getTypeElement() { return typeElement; } }
-
现在看process的代码:
private Map<String, ProxyInfo> mProxyMap = new HashMap<String, ProxyInfo>(); @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { //------------------------收集信息---------------------------- mProxyMap.clear(); Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class); //收集被注解的元素 for (Element element : elements) { checkAnnotationValid(element, BindView.class); //检查element类型,比如只能修饰成员变量 VariableElement variableElement = (VariableElement) element; //class type TypeElement classElement = (TypeElement) variableElement.getEnclosingElement(); //full class name String fqClassName = classElement.getQualifiedName().toString(); ProxyInfo proxyInfo = mProxyMap.get(fqClassName); if (proxyInfo == null) { proxyInfo = new ProxyInfo(mElementUtils, classElement); mProxyMap.put(fqClassName, proxyInfo); } BindView bindAnnotation = variableElement.getAnnotation(BindView.class); int id = bindAnnotation.value(); proxyInfo.injectVariables.put(id, variableElement); } //-------------写代理类-------------------- for (String key : mProxyMap.keySet()) { ProxyInfo proxyInfo = mProxyMap.get(key); try { JavaFileObject jfo = processingEnv.getFiler().createSourceFile( proxyInfo.getProxyClassFullName(), proxyInfo.getTypeElement()); Writer writer = jfo.openWriter(); writer.write(proxyInfo.generateJavaCode()); writer.flush(); writer.close(); } catch (IOException e) { error(proxyInfo.getTypeElement(), "Unable to write injector for type %s: %s", proxyInfo.getTypeElement(), e.getMessage()); } } return true; } private boolean checkAnnotationValid(Element annotatedElement, Class clazz) { if (annotatedElement.getKind() != ElementKind.FIELD) { error(annotatedElement, "%s must be declared on field.", clazz.getSimpleName()); return false; } if (annotatedElement.getModifiers().contains(Modifier.PRIVATE)) { error(annotatedElement, "%s() must can not be private.", annotatedElement.getSimpleName()); return false; } return true; } private void error(Element element, String message, Object... args) { if (args.length > 0) { message = String.format(message, args); } processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, message, element); }
-
compiler模块写完,轮到API模块了:
public interface OnBindView<T> { void inject(T t, Object source); } public class BindView { private static final String SUFFIX = "$$OnBindView"; public static void injectView(Activity activity) { OnBindView proxyActivity = findProxyActivity(activity); proxyActivity.inject(activity, activity); } public static void injectView(Object object, View view) { OnBindView proxyActivity = findProxyActivity(object); proxyActivity.inject(object, view); } private static OnBindView findProxyActivity(Object activity) { try { Class clazz = activity.getClass(); Class injectorClazz = Class.forName(clazz.getName() + SUFFIX); return (OnBindView) injectorClazz.newInstance(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } throw new RuntimeException(String.format("can not find %s , something when compiler.", activity.getClass().getSimpleName() + SUFFIX)); } }
-
最后,使用:
public class MainActivity extends AppCompatActivity { @Bind(R.id.id_textview) TextView mTv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_fragment); ViewInjector.injectView(this); }
网友评论