android 注解的使用

前言

對注解,一開始是在學(xué)習(xí)java的時(shí)候接觸到的,就是在《Thinking in Java》里草草看過。后來開發(fā)android,自己接了項(xiàng)目時(shí),把xUtils3里的注解模塊摳出來使用了,再到后來畢業(yè)了來了大公司,同事們都說不要使用注解,用反射影響性能,于是,注解就從入門到放棄了。后來發(fā)現(xiàn)注解也可以在編譯時(shí)生成代碼,并不會怎么影響性能,于是準(zhǔn)備寫點(diǎn)來復(fù)習(xí)下注解

基礎(chǔ)概況

注解是Java SE5中的重要特性,也被稱為元數(shù)據(jù)。為我們在代碼中添加信息提供一種形式化的方式,使我們可以在稍后某個(gè)時(shí)刻非常方便地使用這些數(shù)據(jù)。主要作用:

  1. 可以由編譯器來測試和驗(yàn)證格式
  2. 存儲有關(guān)程序的額外信息
  3. 可以用來生成描述符文件或新的類定義
  4. 減少編寫樣板代碼的負(fù)擔(dān)

分類

  • 根據(jù)注解中成員個(gè)數(shù)(0個(gè),1個(gè),多個(gè))把注解分為:標(biāo)記注解,單值注解,完整注解
  • 也可以根據(jù)把注解的來源分為jdk自帶的,元注解,和我們自己定義的注解
1. Java SE5中自帶了三種標(biāo)準(zhǔn)注解

@Override 表示當(dāng)前的方法定義將覆蓋超類中的方法
@Deprecated 表示廢棄的意思,使用了該注解的方法或者對象,則會有提示。
@SuppressWarnings 關(guān)閉不當(dāng)?shù)木幾g器警告信息
java 8新特性:加入了 @Repeatable注解,允許多次使用同一個(gè)注解

@Repeatable(Authorities.class)
public @interface Num{
     int value();
}

public class Opera{
    @Num(value = 1)
    @Num(value = 2)
    public void add(){ }
}
2. Java SE5中還有四個(gè)元注解,元注解專職負(fù)責(zé)注解其他注解。在 java.lang.annotation下
元注解 作用
@Target 表示該注解可以用于什么地方,可能在ElementType參數(shù)包括
CONSTRUCTOR: 用于描述構(gòu)造器
FIELD: 用于描述域
LOCAL_VARIABLE: 用于描述局部變量
METHOD: 用于描述方法
PACKAGE: 用于描述包
PARAMETER: 用于描述參數(shù)
TYPE: 用于描述類、接口(包括注解類型) 或enum聲明
@Retention 表示需要在什么級別保存該注解信息??蛇x的RetentionPolicy參數(shù)包括:
SOURCE: 注解將被編譯器丟棄
CLASS: 注解在class文件中可用,但會被VM丟棄。
RUNTIME: VM將在運(yùn)行期也保留注解,因此可以通過反射機(jī)制讀取注解的信息
@Document 將此注解包含在javadoc中
@Inhrited 允許子類繼承父類中的注解

java 8新特性:java 8之前注解只能是在聲明的地方所使用,比如類,方法,屬性;java 8里面,注解可以應(yīng)用在任何地方,比如方法參數(shù)前面等。

3.自定義注解

先寫幾個(gè)簡單的注解:

  1. 沒有元素的標(biāo)記注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {}
  1. 單值注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Num {
    int value();
}
  1. 完整注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Event {
    int[] value();
    int[] parentId() default 0;
    Class<?> type() default View.OnClickListener.class;
    String setter() default "";
    String method() default "";
}
自定義注解格式:

public @interface 注解名{注解體}。注解體中注解元素可以被public修飾,也可以什么也不寫,元素類型可以是:

  • 所有基本數(shù)據(jù)類型(int,float,boolean等)
  1. String類型
  2. Class類型
  3. enum類型
  4. Annotation類型(說明注解可以嵌套)
  5. 以上所有類型的數(shù)組
默認(rèn)值的限定:

編譯器對默認(rèn)值過分的挑剔,要么有確定的默認(rèn)值,要么在使用的時(shí)候提供元素的值。
基本類型的元素都有默認(rèn)值,不用寫default也可以,但是想String這類就必須要寫,而且不能寫null,因此在某些需要分清是null還是空字符串的地方要注意。

4. 注解的使用
@Event(value = R.id.btn_test1, type = View.OnClickListener.class)
private void onTestClick(View view) {
    ……
}

注解元素使用時(shí)變現(xiàn)為鍵值對的形式,如上的value=R.id.btn_test1,沒有賦值的就用默認(rèn)的值了。
當(dāng)然為了簡便,也可以直接寫上值,特別是單值注解

@Num (2)
class Goods {
……
}
5. 注解處理器類庫的使用

如果沒有用來讀取注解的工具,那注解也不會比注釋更有用。使用注解的過程中很重要的一個(gè)部分就是創(chuàng)建與使用注解處理器。Java SE5擴(kuò)展了反射機(jī)制的API,在java.lang.reflect 包下新增了AnnotatedElement接口,該接口代表程序中可以接受注解的程序元素,以下是源碼,不要看到接口里的方法有具體實(shí)現(xiàn)感到驚訝(Java 8允許我們給接口添加一個(gè)非抽象的方法實(shí)現(xiàn),只需要使用 default關(guān)鍵字即可,這個(gè)特征又叫做擴(kuò)展方法),原來的注釋太長我去掉了,寫上簡單的漢字注釋,java 8比起之前新增了兩個(gè)方法。

public interface AnnotatedElement {

    //該元素是否被注解標(biāo)記了
    default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
        return getAnnotation(annotationClass) != null;
    }

    //獲取該元素指定注解類型的值
    <T extends Annotation> T getAnnotation(Class<T> annotationClass);

    //獲取該元素所有的注解
    Annotation[] getAnnotations();

    //1.8新增,返回重復(fù)注解(@Repeatable)的類型
    default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) {

        T[] result = getDeclaredAnnotationsByType(annotationClass);

        if (result.length == 0 && // Neither directly nor indirectly present
                this instanceof Class && // the element is a class
                AnnotationType.getInstance(annotationClass).isInherited()) { // Inheritable
            Class<?> superClass = ((Class<?>) this).getSuperclass();
            if (superClass != null) {
                // Determine if the annotation is associated with the
                // superclass
                result = superClass.getAnnotationsByType(annotationClass);
            }
        }

        return result;
    }

    //返回直接存在于此元素上的所有注釋,不考慮繼承下來的
    default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) {
        Objects.requireNonNull(annotationClass);
        // Loop over all directly-present annotations looking for a matching one
        for (Annotation annotation : getDeclaredAnnotations()) {
            if (annotationClass.equals(annotation.annotationType())) {
                // More robust to do a dynamic cast at runtime instead
                // of compile-time only.
                return annotationClass.cast(annotation);
            }
        }
        return null;
    }

    //1.8新增,返回直接或者間接標(biāo)記在該元素的注解類型,不考慮繼承。
    default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass) {
        Objects.requireNonNull(annotationClass);
        return AnnotationSupport.
                getDirectlyAndIndirectlyPresent(Arrays.stream(getDeclaredAnnotations()).
                        collect(Collectors.toMap(Annotation::annotationType, Function.identity(),
                                ((first, second) -> first), LinkedHashMap::new)), annotationClass);
    }

    Annotation[] getDeclaredAnnotations();
}

該接口主要有如下幾個(gè)實(shí)現(xiàn)類:

  • Class:類定義
  • Constructor:構(gòu)造器定義
  • Field:累的成員變量定義
  • Method:類的方法定義
  • Package:類的包定義

通過反射獲取成員名再獲取注解值得一般寫法,同樣的可以通過反射獲取方法等

Field fields[] = clazz.getDeclaredFields();
for (Field field : fields) {
      if (field.isAnnotationPresent(Num.class)) {
           Num num= field.getAnnotation(Num.class);
           ……
      }
      ……
}

在網(wǎng)上找到了一張注解的提綱,非常詳細(xì),除了沒有java 8里注解新特性,這里引用下:


注解提綱

講了這么多,都是純java和運(yùn)行時(shí)注解的,下面開始結(jié)合Android Studio講講編譯時(shí)注解


APT

講編譯時(shí)注解,先了解下注解處理器工具APT(Annotation Processing Tool)
   APT(Annotation processing tool)是一種處理注釋的工具,它對源代碼文件進(jìn)行檢測找出其中的Annotation,使用Annotation進(jìn)行額外的處理??梢栽诰幾g時(shí)進(jìn)行注解處理,也可以在運(yùn)行時(shí)通過反射API進(jìn)行注解處理。編譯時(shí)進(jìn)行注解處理是根據(jù)源文件中的Annotation生成額外的源文件和其它的文件,將它們一起生成class文件。
  使用APT主要的目的是簡化開發(fā)者的工作量,因?yàn)锳PT可以編譯程序源代碼的同時(shí),生成一些附屬文件(比如源文件,類文件,程序發(fā)布描述文件等),這些附屬文件的內(nèi)容也都是與源代碼相關(guān)的,換句話說,使用APT可以代替?zhèn)鹘y(tǒng)的對代碼信息和附屬文件的維護(hù)工作。
  
下面將結(jié)合Android Studio實(shí)現(xiàn)編譯時(shí)注解,主要內(nèi)容來自這篇文章:THE 10-STEP GUIDE TO ANNOTATION PROCESSING IN ANDROID STUDIO。先看下項(xiàng)目的結(jié)構(gòu),如果是第一次嘗試,包名最好先完全一樣,省的哪里出錯(cuò)。

項(xiàng)目.png
  1. 新建一個(gè)android項(xiàng)目 AnnotationProcessor,包名為com.stablekernel.annotationprocessor,再建一個(gè)Java Library 的module,如下圖所示

    構(gòu)建注解module

    并且設(shè)置下app模塊依賴processor模塊
    Paste_Image.png

  2. 設(shè)置兼容性

  • 在app 模塊的gradle里添加
compileOptions {
   sourceCompatibility JavaVersion.VERSION_1_7
   targetCompatibility JavaVersion.VERSION_1_7
}

如下圖所示:


Paste_Image.png
  • 在processor 模塊的gradle里添加
sourceCompatibility = 1.7
targetCompatibility = 1.7

如下圖所示:


Paste_Image.png
  1. 在processor 模塊里創(chuàng)建注解


    Paste_Image.png

    CustomAnnotation是自定義的注解,源碼很簡單:

package com.stablekernel.annotationprocessor.processor;
public @interface CustomAnnotation {
}
  1. 在processor 模塊里創(chuàng)建注解處理器CustomAnnotationProcessor是注解處理器,源碼如下:
@SupportedAnnotationTypes("com.stablekernel.annotationprocessor.processor.CustomAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class CustomAnnotationProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        StringBuilder builder = new StringBuilder()
                .append("package com.stablekernel.annotationprocessor.generated;\n\n")
                .append("public class GeneratedClass {\n\n") // open class
                .append("\tpublic String getMessage() {\n") // open method
                .append("\t\treturn \"");
        // for each javax.lang.model.element.Element annotated with the CustomAnnotation
        for (Element element : roundEnv.getElementsAnnotatedWith(CustomAnnotation.class)) {
            String objectType = element.getSimpleName().toString();
            // this is appending to the return statement
            builder.append(objectType).append(" says hello!\\n");
        }
        builder.append("\";\n") // end return
                .append("\t}\n") // close method
                .append("}\n"); // close class
        try { // write the file
            JavaFileObject source = processingEnv.getFiler()
                   .createSourceFile("com.stablekernel.annotationprocessor.generated.GeneratedClass");
            Writer writer = source.openWriter();
            writer.write(builder.toString());
            writer.flush();
            writer.close();
        } catch (IOException e) {
            // Note: calling e.printStackTrace() will print IO errors
            // that occur from the file already existing after its first run, this is normal
        }
        return true;
    }
}

稍微解釋下,注解器需要繼承AbstractProcessor ,并且實(shí)現(xiàn)process方法,CustomAnnotationProcessor 類上面兩個(gè)注解@SupportedAnnotationTypes 和@SupportedSourceVersion 分別表示支持的注解的類型和支持的版本,在process里面的StringBuilder 就是在編譯時(shí)要?jiǎng)?chuàng)建的新文件里面的內(nèi)容了,里面一個(gè)for循環(huán)是遍歷了所有使用該CustomAnnotation.class注解的元素,取出其名字。最下面的try catch 里是將該文件寫入source下, 編譯成功后,具體位置如下圖:


Paste_Image.png
  1. 創(chuàng)建resources


    Paste_Image.png

    在processor 的main文件夾下 新建文件夾resources ,新建文件夾META-INF ,新建文件javax.annotation.processing.Processor。該文件里面的內(nèi)容就是注解處理器的路徑,如果有多個(gè)注解處理器,記得換行,每行寫一個(gè)。


    Paste_Image.png
  2. 在全局的gradle里添加android-apt依賴,再在app 模塊的gradle中添加插件。


    Paste_Image.png

    Paste_Image.png
  3. 設(shè)置構(gòu)建依賴:


    Paste_Image.png

    或者

dependencies {
   compile files('libs/processor.jar')
   ……
}

然后寫個(gè)任務(wù),使得processor.jar復(fù)制到app的lib下,和預(yù)構(gòu)建的任務(wù)

task processorTask(type: Copy) {
    from '../processor/build/libs/processor.jar'
    into 'libs/'
}
processorTask.dependsOn(':processor:build')
preBuild.dependsOn(processorTask)

app的gralde最終變成這樣,供參考:

Paste_Image.png

到這里其實(shí)就已經(jīng)結(jié)束了,最后為了驗(yàn)證下,在MainActivity中調(diào)用下就可以

@CustomAnnotation
public class MainActivity extends AppCompatActivity {

    @CustomAnnotation
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        showAnnotationMessage();
    }

    private void showAnnotationMessage() {
        GeneratedClass generatedClass = new GeneratedClass();
        String message = generatedClass.getMessage();
        // android.support.v7.app.AlertDialog
        new AlertDialog.Builder(this)
                .setPositiveButton("Ok", null)
                .setTitle("Annotation Processor Messages")
                .setMessage(message)
                .show();
    }
}

為了加深對編譯時(shí)注解的理解,再推薦下這篇文章:ButterKnife源碼分析

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容