淺談Android下的註解

寫在開頭:最近在翻讀一些開源庫的時候,發現大多使用了註解,因而不得不來仔細瞭解一下Android下的註解知識java

什麼是註解

java.lang.annotation,接口 Annotation,在JDK5.0及之後版本引入。android

註解是代碼裏的特殊標記,這些標記能夠在編譯類加載運行時被讀取,並執行相應的處理。經過使用Annotation,開發人員能夠在不改變原有邏輯的狀況下,在源文件中嵌入一些補充的信息。代碼分析工具、開發工具和部署工具能夠經過這些補充信息進行驗證、處理或者進行部署。git

Annotation不能運行,它只有成員變量,沒有方法。Annotation跟public、final等修飾符的地位同樣,都是程序元素的一部分,Annotation不能做爲一個程序元素使用。github

註解的做用

註解將一些原本重複性的工做,變成程序自動完成,簡化和自動化該過程。好比用於生成Java doc,好比編譯時進行格式檢查,好比自動生成代碼等,用於提高軟件的質量和提升軟件的生產效率。json

常見的註解

Android已經定義好的註解大體分爲4種,稱之爲4大元註解api

@Retention:定義該Annotation被保留的時間長度app

  • RetentionPoicy.SOURCE:註解只保留在源文件,當Java文件編譯成class文件的時候,註解被遺棄;用於作一些檢查性的操做,好比 @Override@SuppressWarnings
  • RetentionPoicy.CLASS:註解被保留到class文件,但jvm加載class文件時候被遺棄,這是默認的生命週期;用於在編譯時進行一些預處理操做,好比生成一些輔助代碼(如 ButterKnife
  • RetentionPoicy.RUNTIME:註解不只被保存到class文件中,jvm加載class文件以後,仍然存在;用於在運行時去動態獲取註解信息。這個註解大都會與反射一塊兒使用

@Target:定義了Annotation所修飾的對象範圍框架

  • ElementType.CONSTRUCTOR:用於描述構造器
  • ElementType.FIELD:用於描述域
  • ElementType.LOCAL_VARIABLE:用於描述局部變量
  • ElementType.METHOD:用於描述方法
  • ElementType.PACKAGE:用於描述包
  • ElementType.PARAMETER:用於描述參數
  • ElementType.TYPE:用於描述類、接口(包括註解類型) 或enum聲明

未標註則表示可修飾全部jvm

@Inherited:是否容許子類繼承父類的註解,默認是falseide

@Documented 是否會保存到 Javadoc 文檔中

自定義註解

自定義註解中使用到較多的是運行時註解編譯時註解

運行時註解

下面經過一個簡單的動態綁定控件的例子來講明

首先定義一個簡單的自定義註解,

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView {
    int value() default  -1;
}
複製代碼

而後在app運行時,經過反射將findViewbyId()獲得的控件,注入到咱們須要的變量中。

public class AnnotationActivity extends AppCompatActivity {

    @BindView(R.id.annotation_tv)
    private TextView mTv;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_annotation);

        getAllAnnotationView();

        mTv.setText("Annotation");
    }

    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 bindView = field.getAnnotation(BindView.class);
                        //findViewById將註解的id,找到View注入成員變量中
                        field.set(this, findViewById(bindView.value()));
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
複製代碼

最後mTv上顯示的就是咱們想要的「Annotation」文字,這看起來是否是有點像ButterKnife,可是要注意反射是很消耗性能的,

因此咱們經常使用的控件綁定庫ButterKnife並非採用運行時註解,而是採用的編譯時註解.

編譯時註解

定義

在說編譯時註解以前,咱們得先提一提註解處理器AbstractProcessor
它是javac的一個工具,用來在編譯時掃描和處理註解Annotation,你能夠自定義註解,並註冊到相應的註解處理器,由註解處理器來處理你的註解。

一個註解的註解處理器,以Java代碼(或者編譯過的字節碼)做爲輸入,生成文件(一般是.java文件)做爲輸出。這些由註解器生成的.java代碼和普通的.java同樣,能夠被javac編譯。

導入

由於AbstractProcessor是javac中的一個工具,因此在Android的工程下無法直接調用。下面提供一個本人嘗試可行的導入方式。

File-->New Module-->java library 新建一個java module,注意必定要是java library,不是Android library

接下來就能夠在對應的library中使用AbstractProcessor

準備工做完成以後,下面經過一個簡單的註解綁定控件的例子來說述

工程目錄

--app                 (主工程)
--app_annotation      (java module 自定義註解)
--annotation-api      (Android module)
--app_compiler        (java module 註解處理器邏輯)
複製代碼

在annotation module下建立註解

@Retention(RetentionPolicy.CLASS)
public @interface BindView {
	//綁定控件
	int value();
}
複製代碼

在compiler module下建立註解處理器 CustomProcessor

public class CustomProcessor extends AbstractProcessor {
    //文件相關的輔助類
    private Filer mFiler;
    //元素相關的輔助類
    private Elements mElements;
	
	//初始化參數
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mElements = processingEnvironment.getElementUtils();
        mFiler = processingEnvironment.getFiler();
    }
	
	//核心處理邏輯,至關於java中的主函數main(),你須要在這裏編寫你本身定義的註解的處理邏輯
	//返回值 true時表示當前處理,不容許後續的註解器處理
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment env) {
        return true;
    }

    //自定義註解集合
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
        types.add(BindView.class.getCanonicalName());
        return types;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
}
複製代碼

其中核心代碼process函數有兩個參數,咱們重點關注第二個參數,由於env表示的是全部註解的集合

首先咱們先簡單的說明一下porcess的處理流程

  1. 遍歷env,獲得咱們須要的元素列表
  2. 將元素列表封裝成對象,方便以後的處理(如同平時解析json數據同樣)
  3. 經過JavaPoet庫將對象以咱們指望的形式生成java文件
  1. 遍歷env,獲得咱們須要的元素列表
for(Element element : roundEnvironment.getElementsAnnotatedWith(BindView.class)){
	// todo ....

    // 判斷元素的類型爲Class
    if (element.getKind() == ElementKind.CLASS) {
        // 顯示轉換元素類型
        TypeElement typeElement = (TypeElement) element;
        // 輸出元素名稱
        System.out.println(typeElement.getSimpleName());
        // 輸出註解屬性值
        System.out.println(typeElement.getAnnotation(BindView.class).value());
    }
}
複製代碼

直接經過getElementsAnnotatedWith函數就能獲取到須要的註解的列表,函數體內加了些element簡單的使用

2.將元素列表封裝成對象,方便以後的處理

首先,咱們須要明確,在綁定控件的這個事件下,咱們須要的是控件的id。

新建類 BindViewField.class 用來保存自定義註解BindView相關的屬性

BindViewField.class

public class BindViewField {

    private VariableElement mFieldElement;

    private int mResId;

    public BindViewField(Element element) throws IllegalArgumentException {
        if (element.getKind() != ElementKind.FIELD) {
            throw new IllegalArgumentException(String.format("Only field can be annotated with @%s",
                    BindView.class.getSimpleName()));
        }
        mFieldElement = (VariableElement) element;
        BindView bindView = mFieldElement.getAnnotation(BindView.class);
        mResId = bindView.value();
        if (mResId < 0) {
            throw new IllegalArgumentException(String.format("value() in %s for field % is not valid",
                    BindView.class.getSimpleName(), mFieldElement.getSimpleName()));
        }
    }

    public Name getFieldName() {
        return mFieldElement.getSimpleName();
    }

    public int getResId() {
        return mResId;
    }

    public TypeMirror getFieldType() {
        return mFieldElement.asType();
    }
}
複製代碼

上述的BindViewField只能表示一個自定義註解bindView對象,而一個類中極可能會有多個自定義註解,因此還須要建立一個對象Annotation.class來管理自定義註解集合、

AnnotatedClass.class

public class AnnotatedClass {

    //類
    public TypeElement mClassElement;

    //類內的註解變量
    public List<BindViewField> mFiled;

    //元素幫助類
    public Elements mElementUtils;

    public AnnotatedClass(TypeElement classElement, Elements elementUtils) {
        this.mClassElement = classElement;
        this.mElementUtils = elementUtils;
        this.mFiled = new ArrayList<>();
    }
	
	//添加註解變量
    public void addField(BindViewField field) {
        mFiled.add(field);
    }
	
	//獲取包名
    public String getPackageName(TypeElement type) {
        return mElementUtils.getPackageOf(type).getQualifiedName().toString();
    }
	
	//獲取類名
    private static String getClassName(TypeElement type, String packageName) {
        int packageLen = packageName.length() + 1;
        return type.getQualifiedName().toString().substring(packageLen).replace('.', '$');
    }
}
複製代碼

給上完整的解析流程

//解析事後的目標註解集合
private Map<String, AnnotatedClass> mAnnotatedClassMap = new HashMap<>();

@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    mAnnotatedClassMap.clear();
    try {
        processBindView(roundEnvironment);
    } catch (Exception e) {
        e.printStackTrace();
        return true;
    }
    return true;
}

private void processBindView(RoundEnvironment env) {
    for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
        AnnotatedClass annotatedClass = getAnnotatedClass(element);
        BindViewField field = new BindViewField(element);
        annotatedClass.addField(field);
        System.out.print("p_element=" + element.getSimpleName() + ",p_set=" + element.getModifiers());
    }
}

private AnnotatedClass getAnnotatedClass(Element element) {
    TypeElement encloseElement = (TypeElement) element.getEnclosingElement();
    String fullClassName = encloseElement.getQualifiedName().toString();
    AnnotatedClass annotatedClass = mAnnotatedClassMap.get(fullClassName);
    if (annotatedClass == null) {
        annotatedClass = new AnnotatedClass(encloseElement, mElements);
        mAnnotatedClassMap.put(fullClassName, annotatedClass);
    }
    return annotatedClass;
}
複製代碼

3.經過JavaPoet庫將對象以咱們指望的形式生成java文件

經過上述兩步成功獲取了自定義註解的元素對象,可是仍是缺乏一步關鍵的步驟,缺乏一步findViewById(),實際上ButterKnife這個很出名的庫也並無省略findViewById()這一個步驟,只是在編譯的時候,在build/generated/source/apt/debug下生成了一個文件,幫忙執行了findViewById()這一行爲而已。

一樣的,咱們這裏也須要生成一個java文件,採用的是JavaPoet這個庫。具體的使用 參考連接

process函數中增長生成java文件的邏輯

@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
    mAnnotatedClassMap.clear();
    try {
        processBindView(roundEnvironment);
    } catch (Exception e) {
        e.printStackTrace();
        return true;
    }

    try {
        for (AnnotatedClass annotatedClass : mAnnotatedClassMap.values()) {
            annotatedClass.generateFinder().writeTo(mFiler);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return true;
}
複製代碼

其中核心邏輯annotatedClass.generateFinder().writeTo(mFiler);
具體實如今AnnotatedClass

public JavaFile generateFinder() {

    //構建 inject 方法
    MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("inject")
            .addModifiers(Modifier.PUBLIC)
            .addAnnotation(Override.class)
            .addParameter(TypeName.get(mClassElement.asType()), "host", Modifier.FINAL)
            .addParameter(TypeName.OBJECT, "source")
            .addParameter(Utils.FINDER, "finder");

    //inject函數內的核心邏輯,
    // host.btn1=(Button)finder.findView(source,2131427450);  ----生成代碼
    // host.$N=($T)finder.findView(source,$L)                 ----原始代碼
    // 對比就會發現這裏執行了實際的findViewById綁定事件
    for (BindViewField field : mFiled) {
        methodBuilder.addStatement("host.$N=($T)finder.findView(source,$L)", field.getFieldName()
                , ClassName.get(field.getFieldType()), field.getResId());
    }

    String packageName = getPackageName(mClassElement);
    String className = getClassName(mClassElement, packageName);
    ClassName bindClassName = ClassName.get(packageName, className);

    //構建類對象
    TypeSpec finderClass = TypeSpec.classBuilder(bindClassName.simpleName() + "$$Injector")
            .addModifiers(Modifier.PUBLIC)
            .addSuperinterface(ParameterizedTypeName.get(Utils.INJECTOR, TypeName.get(mClassElement.asType())))   //繼承接口
            .addMethod(methodBuilder.build())
            .build();

    return JavaFile.builder(packageName, finderClass).build();
}
複製代碼

到這裏,大部分邏輯都已實現,用來綁定控件的輔助類也已通關JavaPoet生成了,只差最後一步,宿主註冊,如同ButterKnife通常,ButterKnife.bind(this)

編寫調用接口

在annotation-api下新建

注入接口Injector

public interface Injector<T> {

    void inject(T host, Object source, Finder finder);
}
複製代碼

宿主通用接口Finder(方便以後擴展到view和fragment)

public interface Finder {

    Context getContext(Object source);

    View findView(Object source, int id);
}
複製代碼

activity實現類 ActivityFinder

public class ActivityFinder implements Finder{

    @Override
    public Context getContext(Object source) {
        return (Activity) source;
    }

    @Override
    public View findView(Object source, int id) {
        return ((Activity) (source)).findViewById(id);
    }
}
複製代碼

核心實現類 ButterKnife

public class ButterKnife {

    private static final ActivityFinder finder = new ActivityFinder();
    private static Map<String, Injector> FINDER_MAP = new HashMap<>();

    public static void bind(Activity activity) {
        bind(activity, activity);
    }

    private static void bind(Object host, Object source) {
        bind(host, source, finder);
    }

    private static void bind(Object host, Object source, Finder finder) {
        String className = host.getClass().getName();
        try {
            Injector injector = FINDER_MAP.get(className);
            if (injector == null) {
                Class<?> finderClass = Class.forName(className + "$$Injector");
                injector = (Injector) finderClass.newInstance();
                FINDER_MAP.put(className, injector);
            }
            injector.inject(host, source, finder);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
複製代碼

主工程下調用

對應的按鈕能夠直接使用,不須要findViewById()

public class MainActivity extends AppCompatActivity {

	@BindView(R.id.annotation_tv)
	public TextView tv1;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
	    super.onCreate(savedInstanceState);
	    setContentView(R.layout.activity_main);
	    ButterKnife.bind(this);
	    tv1.setText("annotation_demo");
	}
}
複製代碼

JavaPoet的簡單介紹

經常使用的幾個類

  • MethodSpec 表明一個構造函數或方法聲明。
  • TypeSpec 表明一個類,接口,或者枚舉聲明。
  • FieldSpec 表明一個成員變量,一個字段聲明。
  • JavaFile包含一個頂級類的Java文件。

經常使用的佔位符

$L for variable (變量)

$S for Strings

$T for Types

$N for Names(咱們本身生成的方法名或者變量名等等)

補充內容

自定義Processor註解處理器中最主要的處理方法是process()函數,而process()函數中重要的是 RoundEnvironment參數,

一般的使用方式

for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
	//todo 
}
複製代碼

經過BindView註解獲取全部的Element對象,而這個Element是什麼呢?

Element表示一個程序元素,能夠是包,類或者是方法,全部經過註解取到的元素都將以Element類型處理.準確的來講是Element對象的子類處理。

Element的子類

  • ExecutableElement 表示某個類或接口的方法、構造方法或初始化程序(靜態或實例),包括註釋類型元素。
    對應註解時的@Target(ElementType.METHOD)@Target(ElementType.CONSTRUCTOR)
  • PackageElement 表示一個包程序元素,提供對有關包及其成員的信息訪問。對應註解時@Target(ElementType.PACKAGE)
  • TypeElement 表示一個類或接口程序元素,提供對有關類型及其成員的信息訪問。對應@Target(ElementType.TYPE)
    注意:枚舉類型是一種類,而註解類型是一種接口。
  • TypeParameterElement 表示通常類、接口、方法或構造方法元素的類型參數。
    對應@Target(ElementType.PARAMETER)
  • VariableElement 表示一個字段、enum常量、方法或構造方法參數、局部變量或異常參數。
    對應@Target(ElementType.LOCAL_VARIABLE)

不一樣類型的Element的信息獲取方式不一樣

修飾方法的註解和ExecutableElement

當你有一個註解是以@Target(ElementType.METHOD)定義時,表示該註解只能修飾方法。

獲取咱們須要的一些基本信息

//BindClick.class 以 @Target(ElementType.METHOD)修飾
for (Element element : roundEnv.getElementsAnnotatedWith(BindClick.class)) {
    //對於Element直接強轉
    ExecutableElement executableElement = (ExecutableElement) element;

    //非對應的Element,經過getEnclosingElement轉換獲取
    TypeElement classElement = (TypeElement) element.getEnclosingElement();

    //當(ExecutableElement) element成立時,使用(PackageElement) element.getEnclosingElement();將報錯。
    //須要使用elementUtils來獲取
    Elements elementUtils = processingEnv.getElementUtils();
    PackageElement packageElement = elementUtils.getPackageOf(classElement);

    //全類名
    String fullClassName = classElement.getQualifiedName().toString();
    //類名
    String className = classElement.getSimpleName().toString();
    //包名
    String packageName = packageElement.getQualifiedName().toString();
    //方法名
    String methodName = executableElement.getSimpleName().toString();

    //取得方法參數列表
    List<? extends VariableElement> methodParameters = executableElement.getParameters();
    //參數類型列表
    List<String> types = new ArrayList<>();
    for (VariableElement variableElement : methodParameters) {
        TypeMirror methodParameterType = variableElement.asType();
        if (methodParameterType instanceof TypeVariable) {
            TypeVariable typeVariable = (TypeVariable) methodParameterType;
            methodParameterType = typeVariable.getUpperBound();

        }
        //參數名
        String parameterName = variableElement.getSimpleName().toString();
        //參數類型
        String parameteKind = methodParameterType.toString();
        types.add(methodParameterType.toString());
    }
}
複製代碼

修飾屬性、類成員的註解和VariableElement

for (Element element : roundEnv.getElementsAnnotatedWith(IdProperty.class)) {
    //ElementType.FIELD註解能夠直接強轉VariableElement
    VariableElement variableElement = (VariableElement) element;

    TypeElement classElement = (TypeElement) element
            .getEnclosingElement();
    PackageElement packageElement = elementUtils.getPackageOf(classElement);
    //類名
    String className = classElement.getSimpleName().toString();
    //包名
    String packageName = packageElement.getQualifiedName().toString();
    //類成員名
    String variableName = variableElement.getSimpleName().toString();

    //類成員類型
    TypeMirror typeMirror = variableElement.asType();
    String type = typeMirror.toString();

}
複製代碼

修飾類的註解和TypeElement

for (Element element : roundEnv.getElementsAnnotatedWith(BindView.class)) {
    //ElementType.TYPE註解能夠直接強轉TypeElement
    TypeElement classElement = (TypeElement) element;

    PackageElement packageElement = (PackageElement) element
                .getEnclosingElement();

    //全類名
    String fullClassName = classElement.getQualifiedName().toString();
    //類名
    String className = classElement.getSimpleName().toString();
    //包名
    String packageName = packageElement.getQualifiedName().toString();
     //父類名
     String superClassName = classElement.getSuperclass().toString();

}
複製代碼

源碼地址

demo地址

參考

探究Android中的註解
註解快速入門

自定義註解之編譯時註解

一小時搞明白註解處理器

javapoet——讓你從重複無聊的代碼中解放出來

JavaPoet源碼沒法正常導入Modifier類的討論

Android編譯時註解框架5-語法講解

相關文章
相關標籤/搜索