Android公共技术点之一-Java注解

转:http://yeungeek.com/2016/04/25/Android公共技术点之一-Java注解/


基础是学习任何技术的必须,接下来会介绍一些Android上用到的一些公共技术点。

看了Trinea在codekk上的一些公共技术点,这些点不管在Java还是Android上,都是重要的基础点,所以准备学习之。

Annotation概念

注解是 Java5的一个新特性。注解是插入代码中的一种注释或者是一种元数据(meta data)。
官方的解释:

Annotations, a form of metadata, provide data about a program that is not part of the program itself. Annotations have no direct effect on the operation of the code they annotate.

作用:

  • 编写文档:通过代码里的元数据生成文档
  • 代码分析:通过代码里标识的元数据对代码进行分析
  • 编译检查:通过代码里标识的元数据让编译器实现基本的编译检查

元注解

元注解的作用就是注解其他注解。Java中定义了4个标准的meta-annotation类型,用以对其他的annotation类型做说明,分别是:

  1. @Target
  2. @Retention
  3. @Documented
  4. @Inherited

@Target

说明了Annotation所修饰的对象的作用:用户描述注解的使用范围
取值(ElementType):

  • CONSTRUCTOR: 描述构造器
  • FIELD:描述域
  • LOCAL_VARIABLE:描述局部变量
  • METHOD:描述方法
  • PACKAGE:描述包
  • PARAMETER:描述参数
  • TYPE:描述类、接口(包括注解类型) 或enum声明

如果没有声明,可以修饰所有

@Retention

表示需要在什么级别保存该注释信息,用于描述注解的生命周期
取值(RetentionPolicy):

  • SOURCE(源码时)
  • CLASS(编译时)
  • RUNTIME(运行时)

默认为CLASS

@Documented

标记注解,没有成员
用于描述其它类型的annotation应该被作为标注的程序成员的公共api,可以文档化

@Inherited

标记注解
用该注解修饰的注解,会被子类继承

Annotation自定义

自定义注解使用@interface声明一个注解,每一个方法就是声明一个配置参数,方法的名称就是参数的名称
返回的类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。
下面举个例子:

1
2
3
4
5
6
7
8
9
@Documented
@Retention(CLASS)
@Target(FIELD)
@Inherited
public @interface MyAnnotation {
    String name();
    int id() default 1;
    int[] value();
}

定义了MyAnnotation注解是编译时注解,用于修饰属性,可以被继承和文档化,有3个配置参数。

Annotation解析

主要是根据@Retention分类,下面主要介绍CLASSRUNTIME

运行时Annotation解析

运行时Annotation是指@Retention为RUNTIME的Annotation,解析Annotation的API:

1
2
3
4
T getAnnotation(Class annotationClass) //返回改程序上存在、指定类型的注解
Annotation[] getAnnotations()   //返回改程序元素上存在的所有注解
boolean is AnnotationPresent(Annotation)    //判断该程序元素上是否包含指定类型的注解
Annotation[] getDeclaredAnnotations()       //返回直接存在在改元素上的所有注解,不包含继承的注解

获取注解的信息:

1
2
3
4
5
6
7
8
9
10
private void processAnnotation(Class<?> clazz) {
    Field[] fields = clazz.getDeclaredFields();
    for (Field field : fields) {
        if (field.isAnnotationPresent(MyAnnotation.class)) {
            MyAnnotation myAnnotation = field.getAnnotation(MyAnnotation.class);
            Log.d("DEBUG", "### id:" + myAnnotation.id() + ", name:" + myAnnotation.name()
                    + ", value: " + myAnnotation.value());
        }
    }
}

编译时Annotation解析

编译时Annotation指@Retention为CLASS的Annotation,由编译器自动解析,基于APT注解处理工具。
apt:Annotation Processing Tool,官方说明

The command-line utility apt, annotation processing tool, finds and executes annotation processors based on the annotations present in the set of specified source files being examined. The annotation processors use a set of reflective APIs and supporting infrastructure to perform their processing of program annotations (JSR 175)

如何使用apt:

  1. 自定义类集成自 AbstractProcessor
  2. 重写其中的 process 函数

上文定义的MyAnnotation,使用apt,该如何进行解析:(在android studio中直接使用AbstractProcessor,会找不到这个类,具体的解决方法,请看知识点Annotation Processing Tool)

1
2
3
4
5
6
7
8
9
10
11
12
13
@SupportedAnnotationTypes({ "MyAnnotation" })
public class MyAnnotationProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
        for (TypeElement te : annotations) {
            for (Element element : env.getElementsAnnotatedWith(te)) {
                MyAnnotation myAnnotation = element.getAnnotation(MyAnnotation.class);
                ... //具体的处理逻辑
            }
        }
        return false;
    }
}

SupportedAnnotationTypes 表示这个 Processor 要处理的 Annotation 名字。
process 函数中参数 annotations 表示待处理的 Annotations,参数 env 表示当前或是之前的运行环境
优点:

  • 提高开发效率
  • 减少代码量
  • apt并不会影响性能

缺点:

  • 可读性较差
  • 生成一些辅助类,内存消耗变大
  • android的65535方法数问题

开源库实例讲解

现在很多第三方库运用注解来实现具体功能,看看它们之间的区别

Retrofit

Retrofit是Restful的httpClient,目前版本2.0.2。
看官网的例子

1
2
3
4
5
6
7
8
9
10
public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}
....
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .build();

GitHubService service = retrofit.create(GitHubService.class);

@GET定义:

1
2
3
4
5
6
@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface GET {
  String value() default "";
}

GET的Annotation定义是运行时的注解,只能修饰方法,有一个String属性。
在Retrofit初始化中可以看到原理,具体的实现在ServiceMethod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public Builder(Retrofit retrofit, Method method) {
  this.retrofit = retrofit;
  this.method = method;
  this.methodAnnotations = method.getAnnotations();
  this.parameterTypes = method.getGenericParameterTypes();
  this.parameterAnnotationsArray = method.getParameterAnnotations();
}
...
for (Annotation annotation : methodAnnotations) {
    parseMethodAnnotation(annotation);
}
...
private void parseMethodAnnotation(Annotation annotation) {
  if (annotation instanceof DELETE) {
    parseHttpMethodAndPath("DELETE", ((DELETE) annotation).value(), false);
  } else if (annotation instanceof GET) {
    parseHttpMethodAndPath("GET", ((GET) annotation).value(), false);
...    
}}}

上述代码会检查每个Annotation,看是否被rest method注解修饰,然后得到Annotation信息,在对接口进行动态代理时调用这些信息,完成具体的调用。
在Refrofit初始化create的时候,有动态代理行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
  eagerlyValidateMethods(service);
}
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
    new InvocationHandler() {
      private final Platform platform = Platform.get();

      @Override public Object invoke(Object proxy, Method method, Object... args)
          throws Throwable {
        // If the method is a method from Object then defer to normal invocation.
        if (method.getDeclaringClass() == Object.class) {
          return method.invoke(this, args);
        }
        if (platform.isDefaultMethod(method)) {
          return platform.invokeDefaultMethod(method, service, proxy, args);
        }
        ServiceMethod serviceMethod = loadServiceMethod(method);
        OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
        return serviceMethod.callAdapter.adapt(okHttpCall);
      }
    });
}

Butterknife

Butterknife,使用的是apt技术。
目前稳定版本8.0.0,与7.0相比,主要runtime和compiler分离成了两个,支持更多的配置属性。下面的例子基于7.0.1

1
2
@Bind(R.id.toolbar)
Toolbar toolbar;

@Bind定义:

1
2
3
4
5
@Retention(CLASS) @Target(FIELD)
public @interface Bind {
  /** View ID to which the field will be bound. */
  int[] value();
}

可以看出Bind注解是编译时注解,只能修饰属性,有个int数组属性。
具体的原理实现在ButterKnifeProcessor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
public boolean process(Set<? extends TypeElement> elements, RoundEnvironment env) {
    Map<TypeElement, BindingClass> targetClassMap = findAndParseTargets(env);

    for (Map.Entry<TypeElement, BindingClass> entry : targetClassMap.entrySet()) {
      TypeElement typeElement = entry.getKey();
      BindingClass bindingClass = entry.getValue();

      try {
        JavaFileObject jfo = filer.createSourceFile(bindingClass.getFqcn(), typeElement);
        Writer writer = jfo.openWriter();
        writer.write(bindingClass.brewJava());
        writer.flush();
        writer.close();
      } catch (IOException e) {
        error(typeElement, "Unable to write view binder for type %s: %s", typeElement,
            e.getMessage());
      }
    }

    return true;
}

process方法,编译时,过滤Binding注解到targetClassMap,会根据 targetClassMap 中元素生成不同的 class 文件到最终的 APK 中,
运行时调用 ButterKnife.bind方法会到之前编译生成的类中去找。

本来还要分析下Dagger的注解,不过Dagger这块目前还不是很熟悉,它主要也是依赖注入框架,后面会和依赖注入知识一起介绍。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值