Android注解小结

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

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);
      }
    
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容