Android注解小结

本篇学习的目的是撸一个编译时注解框架:

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 getSupportedAnnotationTypes(){
            Set annotationTypes = new LinkedHashSet();
            annotationTypes.add(BindView.class.getCanonicalName());
            return annotationTypes;
        }
        /**
         *  返回支持的源码版本,固定的写法
         */
        @Override
        public SourceVersion getSupportedSourceVersion(){
           return SourceVersion.latestSupported();
        }
    
        /**
         *  核心的方法
         */
        @Override
        public boolean process(Set set, RoundEnvironment roundEnvironment) {
            return false;
        }
     }
    
  • process先不写,先写一个代理类ProxyInfo:

    /**
     * 用来保存每个类的所有注解信息
     */
    public class ProxyInfo {
    
        private String packageName;       //包名
        private String proxyClassName;    //代理类名
        private TypeElement typeElement;    //代表被代理的类
    
        public Map 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 mProxyMap = new HashMap();
    
        @Override
        public boolean process(Set set, RoundEnvironment roundEnvironment) {
      //------------------------收集信息----------------------------
            mProxyMap.clear();
            Set 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 {
          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);
      }
    

你可能感兴趣的:(Android注解小结)