文档章节

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

我爱睡觉
 我爱睡觉
发布于 2017/06/24 17:39
字数 1803
阅读 4
收藏 0

转: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这块目前还不是很熟悉,它主要也是依赖注入框架,后面会和依赖注入知识一起介绍。


本文转载自:http://blog.csdn.net/zhangmiaoping23/article/details/52939946

我爱睡觉
粉丝 3
博文 2120
码字总数 0
作品 0
南昌
私信 提问
一份关于 Java、Kotlin 与 Android 的学习笔记

JavaKotlinAndroidLearn 这是一份关于 Java 、Kotlin 、Android 的学习笔记,既包含对基础知识点的介绍,也包含对一些重要知识点的源码解析,笔记的大纲如下所示: Java 重拾Java(0)-基础知...

叶应是叶
2018/08/08
0
0
Kotlin 能取代 Java 吗?

作者 | Paresh Sagar 译者 | 安翔 责编 | 伍杏玲 出品 | CSDN(ID:CSDNNews) 当谈到 Android 应用程序开发时,哪种编程语言会首先出现在你的脑海呢?我猜你会立即想到 Java,毕竟大多数的 ...

CSDN资讯
01/26
0
0
「Android」Android开发你需要知道的注解(Annotation)

本文来自尚妆Android团队路飞 发表于尚妆github博客,欢迎订阅! 一、什么是注解 1、注解的作用 2、注解都有哪些 二、自定义注解 1、RetentionPolicy.SOURCE 2、RetentionPolicy.RUNTIME 3、...

尚妆产品技术刊读
2017/06/15
0
0
【菜鸟入门】——一些Android学习资源和一点个人感受

转自:http://www.cainiaobbs.com/forum.php?mod=viewthread&tid=419&extra=page%3D1 不知不觉学习Android有大半年的时间了,虽然中途用了些时间去学习Java。 总的来说这半年里过得很充实,每...

莫侠
2012/12/03
678
0
Android NDK开发简介

最近由于项目的需要,使用到了Android的NDK技术,对项目核心算法跨平台的移植。简答而言,就是使用C对原来的算法进行了改进,并集成到原来的app项目里。 从前的项目一直没有使用NDK进行开发的...

zhiweiofli
2013/03/07
2.2K
0

没有更多内容

加载失败,请刷新页面

加载更多

Spring Cloud 笔记之Spring cloud config client

观察者模式它的数据的变化是被动的。 观察者模式在java中的实现: package com.hxq.springcloud.springcloudconfigclient;import org.springframework.context.ApplicationListener;i...

xiaoxiao_go
昨天
4
0
CentOS7.6中安装使用fcitx框架

内容目录 一、为什么要使用fcitx?二、安装fcitx框架三、安装搜狗输入法 一、为什么要使用fcitx? Gnome3桌面自带的输入法框架为ibus,而在使用ibus时会时不时出现卡顿无法输入的现象。 搜狗和...

技术训练营
昨天
4
0
《Designing.Data-Intensive.Applications》笔记 四

第九章 一致性与共识 分布式系统最重要的的抽象之一是共识(consensus):让所有的节点对某件事达成一致。 最终一致性(eventual consistency)只提供较弱的保证,需要探索更高的一致性保证(stro...

丰田破产标志
昨天
7
0
docker 使用mysql

1, 进入容器 比如 myslq1 里面进行操作 docker exec -it mysql1 /bin/bash 2. 退出 容器 交互: exit 3. mysql 启动在容器里面,并且 可以本地连接mysql docker run --name mysql1 --env MY...

之渊
昨天
7
0
python数据结构

1、字符串及其方法(案例来自Python-100-Days) def main(): str1 = 'hello, world!' # 通过len函数计算字符串的长度 print(len(str1)) # 13 # 获得字符串首字母大写的...

huijue
昨天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部