導(dǎo)言
平常開發(fā)中經(jīng)常會(huì)使用到ButterKnife、EventBus這些有使用注解的第三方庫,直觀來看作用就是“明顯”,通過一個(gè)標(biāo)注說明當(dāng)前方法/屬性的意義,從而使得代碼的可讀性變強(qiáng),是一種不錯(cuò)的開發(fā)手段
注解基礎(chǔ)
1.定義一個(gè)注解
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface PageBackground {
}
@Retention:
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
SOURCE:當(dāng)前注解僅僅是聲明,只會(huì)在源代碼中留存,編譯的時(shí)候?qū)?huì)被刪除,這意味這無法在編譯期間和運(yùn)行時(shí)通過反射獲取到當(dāng)前注解的一些信息
CLASS:注解會(huì)在.class字節(jié)碼中,但是不需要由虛擬機(jī)在運(yùn)行時(shí)保留(注意實(shí)測(cè)通過華為P9,是可以在運(yùn)行時(shí)反射獲取的),這個(gè)也是注解的默認(rèn)行為
RUNTIME:注解會(huì)被保留到運(yùn)行時(shí),那么可以通過反射獲取
一般來說,推薦的使用模式為
SOURCE:單純閱讀使用
CLASS:單純編譯時(shí)使用
RUNTIME:運(yùn)行時(shí)需要反射使用
@Target:
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
}
有點(diǎn)多,看幾個(gè)平常可能會(huì)使用的
TYPE:作用在類/接口聲明上
@RequiresApi
public class AActivity extends Activity{
}
ANNOTATION_TYPE:作用在注解聲明上的,比方說TARGET自己就是這種類型的注解
@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();
}
FIELD:作用在屬性上面,比方說類中的某個(gè)參數(shù)
@SerializedName("sss")
private Button loginButton;
METHOD:作用在類中的方法上面
@PageBackground
public Map<String,String> demo(){
return null;
}
編譯期處理注解
1.創(chuàng)建一個(gè)android的module,用于定義注解等信息

2.創(chuàng)建一個(gè)java的module(需引入上述注解的module)
3.在java的module下定義類繼承AbstractProcessor
@SupportedAnnotationTypes("fanjh.mine.buriedpointannotation.PageBackground")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class AppBackgroundProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
messager = processingEnv.getMessager();
filer = processingEnv.getFiler();
types = processingEnv.getTypeUtils();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return false;
}
}
4.在java的module下面新建一個(gè)文件,用于注冊(cè)AbstractProcessor


5.在主項(xiàng)目的gradle中引入當(dāng)前module

到這一步配置就已經(jīng)完成,接下來要進(jìn)入到具體的AbstractProcessor的編寫
6.用途的思考:
編譯的時(shí)候如何使用注解?參考一些現(xiàn)成的庫
EventBus:在沒有改成@Subscribe注解之前,通過的是反射OnEvent起頭類似的方法名(不同線程不一樣)來處理,要去記各種方法名,而且還要求不能重名,給人的感覺就是太過死板。通過注解之后,可以方便的標(biāo)記運(yùn)行線程,方法名可以自定義,而且閱讀起來也方便很多,直觀很多
ButterKnife:直接通過注解替代大量的findViewByID等手動(dòng)的重復(fù)代碼,如果結(jié)合插件的話就更加方便了
7.實(shí)際操作:
上面其實(shí)提到了,通過繼承AbstractProcessor可以完成編譯期的操作,如果想要替代大量的人工操作,那么首先需要有一個(gè)【服務(wù)類】,也就是說編譯期的操作應(yīng)該是生成一些新的類
compile 'com.squareup:javapoet:1.10.0'
這里推薦square的javapoet庫,通過Builder模式可以快速寫出一個(gè)類,封裝了大量的拼接操作,使用起來非常方便
看一個(gè)簡單的例子:
/**
* @author fanjh
* @date 2018/2/9 10:23
* @description
* @note SupportedAnnotationTypes指定當(dāng)前獲取到的注解,相當(dāng)于一個(gè)過濾器
**/
@SupportedAnnotationTypes("fanjh.mine.buriedpointannotation.PageBackground")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class AppBackgroundProcessor extends AbstractProcessor {
private Messager messager;
private Filer filer;
/**
* 用于初始化一些工具
* 后續(xù)可以使用這些工具進(jìn)行操作
* @param processingEnv
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
//用于打印日志
messager = processingEnv.getMessager();
//用于寫出類文件
filer = processingEnv.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//可以通過當(dāng)前方法獲取指定的注解
Set<Element> set = (Set<Element>) roundEnv.getElementsAnnotatedWith(PageBackground.class);
if(null == set){
return false;
}
Map<String,String> caches = new HashMap<>();
//遍歷當(dāng)前代碼中所有的指定注解
for (Element element : set) {
//獲取當(dāng)前注解的作用對(duì)象
if (element.getKind() == ElementKind.METHOD) {
//這里實(shí)際上就是把有當(dāng)前注解的類名和方法名進(jìn)行緩存
ExecutableElement executableElement = (ExecutableElement) element;
TypeElement typeElement = (TypeElement) executableElement.getEnclosingElement();
String className = typeElement.getQualifiedName().toString();
log(className);
if(executableElement.getParameters().size() > 0){
throw new IllegalArgumentException("當(dāng)前注解標(biāo)記方法不能有參數(shù)!");
}
TypeMirror typeMirror = executableElement.getReturnType();
TypeKind typeKind = typeMirror.getKind();
if(TypeKind.DECLARED != typeKind || !"java.util.Map<java.lang.String,java.lang.String>".equals(typeMirror.toString())){
throw new IllegalArgumentException("當(dāng)前注解標(biāo)記方法返回值類型有誤!");
}
String methodName = executableElement.getSimpleName().toString();
caches.put(className,methodName);
}
}
//當(dāng)前有指定的注解
if(caches.size() > 0) {
//通過javapoet生成指定的類
FieldSpec fieldSpec = FieldSpec.builder(HashMap.class, "cache",
Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC).
initializer("new HashMap<String,String>()").
build();
CodeBlock.Builder staticBuilder = CodeBlock.builder();
for(Map.Entry<String,String> entry:caches.entrySet()){
staticBuilder.addStatement("cache.put($S,$S)", entry.getKey(), entry.getValue());
}
MethodSpec methodSpec = MethodSpec.methodBuilder("getMethod").
addModifiers(Modifier.PUBLIC).
addParameter(String.class,"className").
returns(String.class).
addStatement("return (String)cache.get(className)").
build();
TypeSpec typeSpec = TypeSpec.classBuilder(Const.APP_BACKGROUND_CLASSNAME).
addModifiers(Modifier.PUBLIC).
addStaticBlock(staticBuilder.build()).
addSuperinterface(IBuriedPointApt.class).
addField(fieldSpec).
addMethod(methodSpec).
build();
JavaFile javaFile = JavaFile.builder(Const.PACKAGE_NAME, typeSpec).build();
try {
javaFile.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
log("-----------------------------");
return false;
}
/**
* 打印日志
* @param content
*/
private void log(String content){
messager.printMessage(Diagnostic.Kind.NOTE, content);
}
}
當(dāng)代碼寫完之后,運(yùn)行程序,可以在指定的位置看到編譯期生成的類


思路很簡單:
1.定義一個(gè)接口,后期通過反射可以轉(zhuǎn)型為接口
2.通過注解生成一個(gè)緩存
3.運(yùn)行時(shí)通過接口的方法從緩存中獲取想要的數(shù)據(jù)即可
4.這個(gè)例子就是通過反射指定的類來調(diào)用指定的方法
比方說
public class AnnotationFinder {
private IBuriedPointApt iBuriedPointApt;
private boolean hasApt = true;
private String className;
private Class an;
public AnnotationFinder(String className,Class an) {
this.className = className;
this.an = an;
}
/**
* 編譯期已經(jīng)生成指定的索引
* 當(dāng)前通過索引來獲取參數(shù)
* @param activity 當(dāng)前活動(dòng)
* @return 指定的參數(shù)
*/
private HashMap<String,String> getAptParams(Activity activity){
String methodName = iBuriedPointApt.getMethod(activity.getClass().getCanonicalName());
if(null == methodName){
return null;
}
Method method = null;
try {
method = activity.getClass().getMethod(methodName);
return (HashMap<String, String>) method.invoke(activity);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
/**
* 從緩存中獲取,這個(gè)對(duì)應(yīng)編譯期生成的類
* @param activity 當(dāng)前活動(dòng)
* @return 指定的參數(shù)
*/
private HashMap<String,String> getParamsFromIndex(Activity activity){
//當(dāng)前編譯期沒有生成對(duì)應(yīng)的類
if(!hasApt){
return null;
}
//嘗試直接使用之前已經(jīng)反射出指定的輔助類
if(null != iBuriedPointApt){
return getAptParams(activity);
}
try {
//通過之前定義的規(guī)則來反射指定的類
Class cls = Class.forName(Const.PACKAGE_NAME + "." + className);
iBuriedPointApt = (IBuriedPointApt) cls.newInstance();
return getAptParams(activity);
//出現(xiàn)任何的異常都不允許再使用索引了
} catch (ClassNotFoundException e) {
hasApt = false;
e.printStackTrace();
} catch (IllegalAccessException e) {
hasApt = false;
e.printStackTrace();
} catch (InstantiationException e) {
hasApt = false;
e.printStackTrace();
}
return null;
}
/**
* 通過反射來獲取參數(shù)
* @param activity 當(dāng)前活動(dòng)
* @param c 注解
* @return 指定的參數(shù)
*/
private HashMap<String,String> getParamsFromReflect(Activity activity,Class c){
HashMap<String,String> params = new HashMap<>();
//獲取當(dāng)前類中定義的所有方法
Method[] methods = activity.getClass().getDeclaredMethods();
for(Method method:methods){
//嘗試從當(dāng)前方法獲取指定的注解
Annotation annotation = method.getAnnotation(c);
if(null != annotation){
try {
params = (HashMap<String, String>) method.invoke(activity);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
break;
}
}
return params;
}
public HashMap<String,String> getParams(Activity activity,boolean useIndex){
//是否使用索引,實(shí)際上就是緩存
if(useIndex){
return getParamsFromIndex(activity);
}else{
return getParamsFromReflect(activity,an);
}
}
}
實(shí)際上這里就是為了一個(gè)簡單的功能
@PageBackground
public Map<String,String> background(){
HashMap<String,String> params = new HashMap<>();
params.put("type","report");
params.put("background",getClass().getSimpleName());
return params;
}
通過指定的注解,來返回想要的參數(shù),這里的場(chǎng)景是埋點(diǎn)的時(shí)候,當(dāng)App進(jìn)入后臺(tái)的時(shí)候上報(bào)當(dāng)前頁面的一些數(shù)據(jù)
結(jié)語
通過注解的使用,確實(shí)可以方便一些特定場(chǎng)景的使用,更加成熟的應(yīng)用,可以去看EventBus、ButterKnife等庫的源碼