美文网首页
Android注解小结

Android注解小结

作者: Dane_404 | 来源:发表于2019-03-20 08:37 被阅读0次

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

    1、本篇只是学习小结,需要详细的讲解可参考:

    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);
        }
      

    相关文章

      网友评论

          本文标题:Android注解小结

          本文链接:https://www.haomeiwen.com/subject/juaomqtx.html