文章目录
利用Android的编译时注解轻松实现一个线程切换的库
前言
相信大家经常都使用到注解,如果使用过AndroidAnnotations,Dagger2,EventBus,RxJava,BufferKnife等开源项目,对注解应该更为深刻,这些项目的原理基本都是基于编译时注解动态生成代码,效果等同于手写代码,因此相对反射来说性能基本无影响。
另外,已经实现了注解轻松实现线程切换开源项目,欢迎fork&star.
了解注解
注解的概念
注解(Annotation),也叫元数据(Metadata),是Java5的新特性,JDK5引入了Metadata很容易的就能够调用Annotations。注解与类、接口、枚举在同一个层次,并可以应用于包、类型、构造方法、方法、成员变量、参数、本地变量的声明中,用来对这些元素进行说明注释。
注解的语法与定义
- 以@interface关键字定义
- 注解可以包含成员,成员以无参数的方法的形式被声明,其方法名和返回值定义了该成员的名字和类型。
- 成员赋值是通过@Annotation(name=value)的形式。
- 注解需要标明注解的生命周期,注解的修饰目标等信息,这些信息是通过元注解实现。
以 java.lang.annotation 中定义的 Target 注解为例:
@Retention(value = RetentionPolicy.RUNTIME)
@Target(value = { ElementType.ANNOTATION_TYPE } )
public @interface Target {
ElementType[] value();
}
源码分析如下:
第一:元注解@Retention,成员value的值为RetentionPolicy.RUNTIME。
第二:元注解@Target,成员value是个数组,用{}形式赋值,值为ElementType.ANNOTATION_TYPE
第三:成员名称为value,类型为ElementType[]
另外,需要注意一下,如果成员名称是value,在赋值过程中可以简写。如果成员类型为数组,但是只赋值一个元素,则也可以简写。如上面的简写形式为:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
注解的分类
1 基本内置注解,是指Java自带的几个Annotation,如@Override、Deprecated、@SuppressWarnings等;
2 元注解(meta-annotation),是指负责注解其他注解的注解,JDK 1.5及以后版本定义了4个标准的元注解类型,如下:
@Target
@Retention
@Documented
@Inherited
3 自定义注解,根据需要可以自定义注解,自定义注解需要用到上面的meta-annotation
元注解
- Java定义了4个标准的元注解( java8之后新增了 @Repeatable 和 @Native元注解):
- @Documented:标记注解,注解表明这个注解应该被 javadoc工具记录. 默认情况下,javadoc是不包括注解的. 但如果声明注解时指定了 @Documented,则它会被 javadoc 之类的工具处理, 所以注解类型信息也会被包括在生成的文档中。
/**
* Indicates that annotations with a type are to be documented by javadoc
* and similar tools by default. This type should be used to annotate the
* declarations of types whose annotations affect the use of annotated
* elements by their clients. If a type declaration is annotated with
* Documented, its annotations become part of the public API
* of the annotated elements.
*
* @author Joshua Bloch
* @since 1.5
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
- @Inherited:标记注解,允许子类继承父类的注解,此注解理解有难度,可以参考这里,总的来说就是子类在继承父类时如果父类上的注解有此@Inherited标记,那么子类就能把父类的这个注解继承下来,如果没有@Inherited标记,那么子类在继承父类之后并没有继承父类的注解(不知道有没有说明白了,不明白就还是点进链接去看下吧)。
/**
* Indicates that an annotation type is automatically inherited. If
* an Inherited meta-annotation is present on an annotation type
* declaration, and the user queries the annotation type on a class
* declaration, and the class declaration has no annotation for this type,
* then the class's superclass will automatically be queried for the
* annotation type. This process will be repeated until an annotation for this
* type is found, or the top of the class hierarchy (Object)
* is reached. If no superclass has an annotation for this type, then
* the query will indicate that the class in question has no such annotation.
*
* <p>Note that this meta-annotation type has no effect if the annotated
* type is used to annotate anything other than a class. Note also
* that this meta-annotation only causes annotations to be inherited
* from superclasses; annotations on implemented interfaces have no
* effect.
*
* @author Joshua Bloch
* @since 1.5
* @jls 9.6.3.3 @Inherited
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
- @Retention:指Annotation被保留的时间长短,标明注解的生命周期
/**
* Indicates how long annotations with the annotated type are to
* be retained. If no Retention annotation is present on
* an annotation type declaration, the retention policy defaults to
* {@code RetentionPolicy.CLASS}.
*
* <p>A Retention meta-annotation has effect only if the
* meta-annotated type is used directly for annotation. It has no
* effect if the meta-annotated type is used as a member type in
* another annotation type.
*
* @author Joshua Bloch
* @since 1.5
* @jls 9.6.3.2 @Retention
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
注解需要标明注解的生命周期,这些信息是通过元注解 @Retention 实现,注解的值是 enum 类型的 RetentionPolicy,包括以下几种情况:
public enum RetentionPolicy {
/**
* 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃.
* 这意味着注解仅存在于编译器处理期间,编译器处理完之后,该注解就没用了,在class文件找不到了
*/
SOURCE,
/**
* 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期.
* 简单来说就是你在class文件中还能看到注解
*/
CLASS,
/**
* 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在,
* 保存到class对象中,可以通过反射来获取
*/
RUNTIME
}
- @Target:标明注解的修饰目标( java8为ElementType枚举增加了TYPE_PARAMETER、TYPE_USE两个枚举值)
/**
* Indicates the contexts in which an annotation type is applicable. The
* declaration contexts and type contexts in which an annotation type may be
* applicable are specified in JLS 9.6.4.1, and denoted in source code by enum
* constants of {@link ElementType java.lang.annotation.ElementType}.
*
* <p>If an {@code @Target} meta-annotation is not present on an annotation type
* {@code T} , then an annotation of type {@code T} may be written as a
* modifier for any declaration except a type parameter declaration.
*
* <p>If an {@code @Target} meta-annotation is present, the compiler will enforce
* the usage restrictions indicated by {@code ElementType}
* enum constants, in line with JLS 9.7.4.
*
* <p>For example, this {@code @Target} meta-annotation indicates that the
* declared type is itself a meta-annotation type. It can only be used on
* annotation type declarations:
* <pre>
* @Target(ElementType.ANNOTATION_TYPE)
* public @interface MetaAnnotationType {
* ...
* }
* </pre>
*
* <p>This {@code @Target} meta-annotation indicates that the declared type is
* intended solely for use as a member type in complex annotation type
* declarations. It cannot be used to annotate anything directly:
* <pre>
* @Target({})
* public @interface MemberType {
* ...
* }
* </pre>
*
* <p>It is a compile-time error for a single {@code ElementType} constant to
* appear more than once in an {@code @Target} annotation. For example, the
* following {@code @Target} meta-annotation is illegal:
* <pre>
* @Target({ElementType.FIELD, ElementType.METHOD, ElementType.FIELD})
* public @interface Bogus {
* ...
* }
* </pre>
*
* @since 1.5
* @jls 9.6.4.1 @Target
* @jls 9.7.4 Where Annotations May Appear
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
/**
* The constants of this enumerated type provide a simple classification of the
* syntactic locations where annotations may appear in a Java program. These
* constants are used in {@link Target java.lang.annotation.Target}
* meta-annotations to specify where it is legal to write annotations of a
* given type.
*
* <p>The syntactic locations where annotations may appear are split into
* <em>declaration contexts</em> , where annotations apply to declarations, and
* <em>type contexts</em> , where annotations apply to types used in
* declarations and expressions.
*
* <p>The constants {@link #ANNOTATION_TYPE} , {@link #CONSTRUCTOR} , {@link
* #FIELD} , {@link #LOCAL_VARIABLE} , {@link #METHOD} , {@link #PACKAGE} ,
* {@link #PARAMETER} , {@link #TYPE} , and {@link #TYPE_PARAMETER} correspond
* to the declaration contexts in JLS 9.6.4.1.
*
* <p>For example, an annotation whose type is meta-annotated with
* {@code @Target(ElementType.FIELD)} may only be written as a modifier for a
* field declaration.
*
* <p>The constant {@link #TYPE_USE} corresponds to the 15 type contexts in JLS
* 4.11, as well as to two declaration contexts: type declarations (including
* annotation type declarations) and type parameter declarations.
*
* <p>For example, an annotation whose type is meta-annotated with
* {@code @Target(ElementType.TYPE_USE)} may be written on the type of a field
* (or within the type of the field, if it is a nested, parameterized, or array
* type), and may also appear as a modifier for, say, a class declaration.
*
* <p>The {@code TYPE_USE} constant includes type declarations and type
* parameter declarations as a convenience for designers of type checkers which
* give semantics to annotation types. For example, if the annotation type
* {@code NonNull} is meta-annotated with
* {@code @Target(ElementType.TYPE_USE)}, then {@code @NonNull}
* {@code class C {...}} could be treated by a type checker as indicating that
* all variables of class {@code C} are non-null, while still allowing
* variables of other classes to be non-null or not non-null based on whether
* {@code @NonNull} appears at the variable's declaration.
*
* @author Joshua Bloch
* @since 1.5
* @jls 9.6.4.1 @Target
* @jls 4.1 The Kinds of Types and Values
*/
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
- @Repeatable : 可对同一个注解多次使用
/**
* The annotation type {@code java.lang.annotation.Repeatable} is
* used to indicate that the annotation type whose declaration it
* (meta-)annotates is <em>repeatable</em>. The value of
* {@code @Repeatable} indicates the <em>containing annotation
* type</em> for the repeatable annotation type.
*
* @since 1.8
* @jls 9.6 Annotation Types
* @jls 9.7 Annotations
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
/**
* Indicates the <em>containing annotation type</em> for the
* repeatable annotation type.
* @return the containing annotation type
*/
Class<? extends Annotation> value();
}
- 例如,一个人有多重身份
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Persons {
Person[] value();
}
@Repeatable(Persons.class)
public @interface Person{
String role() default "";
}
@Person(role="CEO")
@Person(role="husband")
@Person(role="father")
@Person(role="son")
public class Man {
}
- @Native
/**
* Indicates that a field defining a constant value may be referenced
* from native code.
*
* The annotation may be used as a hint by tools that generate native
* header files to determine whether a header file is required, and
* if so, what declarations it should contain.
*
* @since 1.8
*/
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Native {
}
注解处理器(Annotation Processor)
概述
注解处理器是javac的一个工具,它用来在编译时扫描和处理注解(Annotation)。你可以自定义注解,并注册到相应的注解处理器,由注解处理器来处理你的注解。一个注解的注解处理器,以Java代码(或者编译过的字节码)作为输入,生成文件(通常是.java文件)作为输出。这些生成的Java代码是在生成的.java文件中,所以你不能修改已经存在的Java类,例如向已有的类中添加方法。这些生成的Java文件,会同其他普通的手动编写的Java源代码一样被javac编译。
简单来说,在源代码编译阶段,通过注解处理器,将标记了注解的类、方法等作为输入内容,经过注解处理器进行处理,产生需要的java代码。
Android Gradle插件2.2版本发布后,Android 官方提供了annotationProcessor来代替android-apt,annotationProcessor支持 javac 和 jack 编译方式,而android-apt只支持 javac 编译方式。
使用
- 直接在Module中使用,比之前Android-apt使用方式更加简单。
dependencies {
compile 'com.github.huweijian5:AwesomeTool:latest_version'
annotationProcessor 'com.github.huweijian5:AwesomeTool-compiler:latest_version'
}
实例说明
接下来以本人写的一个注解实现线程切换的项目为例,讲解下编译时注解的编码过程。
项目结构
本项目主要分为三个Module,分别为lib_api,lib_annotation,lib_compiler。
其中lib_api主要存放供外界使用的接口,是对外开放的
lib_annotation里指定了自定义注解的定义
lib_compiler里实现注解处理器,是本项目的核心
-
项目目录结构如下图:
-
依赖关系图如下:
值得注意的是,lib_annotation和lib_compiler都是java工程(apply plugin: ‘java’),而lib_api是android工程(apply plugin: ‘com.android.library’)
lib_annotation
此Module主要实现自定义的注解定义
/**
* 注入对象实例
* Created by HWJ on 2017/3/12.
*/
@Documented
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface InjectObject {
/**
* 线程优先级-20~19,-20代表优先级最高,详见android.os.Process,默认为THREAD_PRIORITY_DEFAULT(0)
* @return
*/
int priority() default 0;
}
/**
* 后台线程注解
* Created by HWJ on 2017/3/12.
*/
@Documented
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface WorkInBackground {
}
/**
1. UI线程注解
2. Created by HWJ on 2017/3/12.
*/
@Documented
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface WorkInMainThread {
}
lib_api
- 定义接口
public interface IObjectInjector<T> {
void inject(T t);
}
- 利用反射进行注入实例
public static void inject(Object target) {
//获取生成类全称
Class<?> clazz = target.getClass();
String proxyClassFullName = clazz.getName() + ConstantValue.SUFFIX;
Class<?> proxyClazz = null;
try {
//反射生成类实例对象并进行注入
proxyClazz = Class.forName(proxyClassFullName);
IObjectInjector objectInjector = (IObjectInjector) proxyClazz.newInstance();
objectInjector.inject(target);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("注入失败:"+e.getMessage());
}
}
lib_compiler
lib_compiler中实现注解处理器,是项目的核心,本项目是使用Handler和HandlerThread实现线程切换,对于HandlerThread线程切换的使用网络文章已经有很多,本文不再赘述。
1 添加以下依赖:
dependencies {
...
//auto-service库可以帮我们去生成META-INF等信息
compile 'com.google.auto.service:auto-service:1.0-rc4'
//用于生成源代码
compile 'com.squareup:javapoet:1.9.0'
}
//只有android N支持java8,如果你写1.8之后,强制要你使用buildToolsVersion为24.0.0
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
...
注意此Module为java工程,而不是android工程,如果弄错了就会报找不到类AbstractProcessor的错误
2 继承AbstractProcessor,并实现方法init,getSupportedAnnotationTypes,getSupportedSourceVersion,process四个方法即可,参考代码如下:
@AutoService(Processor.class)
public class AwesomeToolProcessor extends AbstractProcessor {
private static final String TAG = "AwesomeToolProcessor";
private Filer mFileUtils;//跟文件相关的辅助类,生成JavaSourceCode
private Elements elementUtils;//跟元素相关的辅助类,帮助我们去获取一些元素相关的信息
private Messager messager;//跟日志相关的辅助类
private Map<String, AwesomeToolProxyInfo> proxyInfoMap = new HashMap<String, AwesomeToolProxyInfo>();//key为注解所在类的全名
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
messager = processingEnv.getMessager();
elementUtils = processingEnv.getElementUtils();
mFileUtils=processingEnv.getFiler();
}
/**
* 支持的注解类型
*
* @return
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(InjectObject.class.getCanonicalName());
supportTypes.add(WorkInBackground.class.getCanonicalName());
supportTypes.add(WorkInMainThread.class.getCanonicalName());
return supportTypes;
}
/**
* 注解处理器支持到的JAVA版本
* @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
printMessage("SupportedSourceVersion=%s",SourceVersion.latestSupported().name());
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
printMessage("process:annotations size=%d", annotations.size());
proxyInfoMap.clear();
handleInjectObjectAnnotation(roundEnv);
handleWorkInBackgroundAnnotation(roundEnv);
handleWorkInMainThreadAnnotation(roundEnv);
printMessage("AwesomeToolProxyInfo Map size=%d", proxyInfoMap.size());
generateSourceFiles();
return false;//如果返回true,当有两个注解作用在同一方法上,那么第一个处理完了之后就不会再处理第二个
}
注解@AutoService(Processor.class)可以自动帮我们处理一些工作,简化代码
init中可以获得Messager用来打印信息,打印的信息会显示在AndroidStudio的Gradle Console窗口
同时也可以获得Elements,用来获取元素的相关信息,还有Filer,可以用来生成代码getSupportedAnnotationTypes里需要返回支持的注解类型,就是lib_annotation中定义的注解
getSupportedSourceVersion为注解处理器支持到的java版本
process里处理注解元素作用的类方法等,根据自己的业务逻辑处理并生成相应代码
3 处理注解
通过RoundEnvironment.getElementsAnnotatedWith()可以获得注解所在的方法类等,如下
Set<? extends Element> elesWithBind = roundEnv.getElementsAnnotatedWith(WorkInMainThread.class);
- 其中Element的类型及说明如下:
类型 | 说明 |
---|---|
ExecutableElement | Represents a method, constructor, or initializer (static or instance) of a class or interface, including annotation type elements. |
VariableElement | Represents a field, {@code enum} constant, method or constructor parameter, local variable, resource variable, or exception parameter. |
PackageElement | Represents a package program element. Provides access to information about the package and its members. |
- 获取方法参数,参考代码如下:
for (VariableElement variableElement : executableElement.getParameters()) {
System.out.println("参数类型及名称:" + variableElement.asType() + "," + variableElement.getSimpleName());
}
4 生成代码
生成代码的方式可以通过手动拼接字符串,也可以通过开源库javapoet实现。
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());
}
至此已经走完了编译时注解的整个流程,最后贴下生成的代码:
//Generated code. Do not modify!
//自动生成代码,请勿修改!
package com.junmeng.aad;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import com.junmeng.api.inter.IObjectInjector;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
public class BlankFragmentHelper implements IObjectInjector<BlankFragment> {
public static final int MESSAGE_NEEDWORKINTHREAD = 1;
public static final int MESSAGE_NEEDWORKINMAINTHREAD = 2;
private Handler mainHandler;
private Handler workHandler;
private HandlerThread handlerThread;
private WeakReference<BlankFragment> target;
@Override
public void inject(final BlankFragment target) {
if (target.blankFragmentHelper != null) {
target.blankFragmentHelper.quit();
}
target.blankFragmentHelper = new BlankFragmentHelper();
target.blankFragmentHelper.init(target);
}
public void init(final BlankFragment target) {
this.target = new WeakReference<BlankFragment>(target);
handlerThread = new HandlerThread("thread_BlankFragmentHelper", -16);
handlerThread.start();
mainHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
List<Object> params;
switch (msg.what) {
case MESSAGE_NEEDWORKINMAINTHREAD:
params = (List<Object>) msg.obj;
target.needWorkInMainThread();
break;
}
}
};
workHandler = new Handler(handlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
List<Object> params;
switch (msg.what) {
case MESSAGE_NEEDWORKINTHREAD:
params = (List<Object>) msg.obj;
target.needWorkInThread((java.lang.String) params.get(0), (int) params.get(1), (double) params.get(2), (com.junmeng.aad.Test) params.get(3));
break;
}
}
};
}
public void needWorkInThread(java.lang.String str, int i, double d, com.junmeng.aad.Test test) {
List<Object> params = new ArrayList<>();
params.add(str);
params.add(i);
params.add(d);
params.add(test);
workHandler.sendMessage(workHandler.obtainMessage(MESSAGE_NEEDWORKINTHREAD, params));
}
public void needWorkInMainThread() {
List<Object> params = new ArrayList<>();
mainHandler.sendMessage(mainHandler.obtainMessage(MESSAGE_NEEDWORKINMAINTHREAD, params));
}
/**
* 在不用时务必调用此方法,防止内存泄漏
*/
public void quit() {
if (handlerThread != null && handlerThread.isAlive()) {
handlerThread.quitSafely();
}
}
}
另外,说明下google的auto-service实际上会帮助我们生成jar包并添加META-INF信息,如下图
如有错误之处请指正,谢谢。
待解决的关键问题
- 按照上面的实现只能在build的时候才能生成代码,有没有即时生成的技术呢?还没找到答案。
参考:
- http://blog.csdn.net/wzgiceman/article/details/54580745
- http://blog.csdn.net/lmj623565791/article/details/43452969
- http://blog.csdn.net/github_35180164/article/details/52107204
- https://www.jianshu.com/p/28edf5352b63
- Java注解——Repeatable - ljcgit的博客 - CSDN博客
https://blog.csdn.net/ljcgit/article/details/81708679