《Android 高級進階》讀書筆記
Android 開發中在很多地方都用到了註解,註解在Android中的作用不可謂不大。Android Support Library甚至專門推出了一個支持庫Support Annotation,之前的一篇文章Android進階Support Annotation Library 使用詳解 ,基本把註解說的差不多了,這次咱們整體的說下註解。那什麼是註解呢?
註解的定義
註解是Java語言的特性之一,它是在源代碼中插入的標籤,這些標籤在後面的編譯或者運行過程中起到某種作用。每個註解都必須通過註解接口@interface進行聲明,接口的方法對應着註解的元素。
@interface聲明會創建一個實際的Java接口,與其他任何接口一樣,註解也將會編譯成.class文件。註解接口中的元素聲明實際上是方法聲明,註解接口中的方法沒有參數,沒有throws語句,也不能使用泛型。
標準註解
Java API中默認定義的註解我們稱之爲標準註解。它們定義在java.lang、java.lang.annotation和javax.annotation包中。按照使用場景不同,可以分爲如下三類。
1. 編譯相關注解
編譯相關的註解是給編譯器使用的,有以下幾種。
- @Override:編譯器會檢查被註解的方法是否真的重載了一個來自父類的方法、如果沒有,編譯器將會給出錯誤提示。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
}
- @Deprecated:可以用來修飾任何不再鼓勵使用或已被棄用的屬性、方法等。
/**
* @deprecated use {@link #setBackground(Drawable)} instead
*/
@Deprecated
public void setBackgroundDrawable(Drawable background) {
computeOpaqueFlags();
...
}
- SuppressWarnings:可用於除了包之外的其他聲明項中,用來抑制某種類型的警告。
//源碼中的註釋說的非常詳細
/*
* <p>As a matter of style, programmers should always use this annotation
* on the most deeply nested element where it is effective. If you want to
* suppress a warning in a particular method, you should annotate that
* method rather than its class.
* /
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value();
}
- @SafeVarargs:用於方法和構造函數,用來斷言不定長參數可以安全使用。
- @Generated:一般是給代碼生成工具使用,用來表示這段代碼不是開發者手動編寫的,而是工具生成的。被@Generated修飾的代碼一般不建議手動修改它。
- @FunctionalInterface:用來修飾接口,表示對應的接口是帶單個方法的函數式接口。
資源相關注解
資源相關的註解有四個,一般用在JavaEE領域,Android開發中應該不會用到,羅列一下:
- @PostConstruct:用在控制對象生命週期的環境中,例如Web容器和應用服務器,表示在構造函數之後應該立即調用被該註解修飾的方法。
- @PreDestory:表示在刪除一個被注入的對象之前應該立即調用被該註解修飾的方法。
- @Resource:用於Web容器的資源注入,表示單個資源。
- @Resources:用於Web容器的資源注入,表示一個資源數組。
元註解
元註解,就是用來定義和實現註解的註解,共分爲5種:
- @Target:這個註解的取值是一個ElementType類型的數組,用來指定註解所適用的對象範圍,總共有10種不同的類型,根據定義的註解進行靈活的組合。
元素類型 | 適用於 |
---|---|
ANNOTATION_TYPE | 註解類型聲明 |
CONSTRUCTOR | 構造函數 |
FIELD | 實例變量 |
LOCAL_VARIABLE | 局部變量 |
METHOD | 方法 |
PACKAGE | 包 |
PARAMETER | 方法參數或者構造函數的參數 |
TYPE | 類(包含enmu)和接口(包含註解類型) |
TYPE_PARAMETER | 類型參數 |
TYPE_USER | 類型的用圖 |
同時支持多個註解類型的註解定義如下:
@Target({ElementType.TYPE,ElementType.PACKAGE})
public @interface CrashReport
如果一個註解的定義沒有使用@Target修飾,那麼它可以用在除了TYPE_USE和TYPE_PARAMEYER之外的其他類型中。
@Retention:用來指明註解的訪問範圍,也就是在什麼級別保留註解,有如下三種:
- 源碼級註解:在定義註解接口時,使用@Retention(RetentionPolicy.SOURCE)修飾的註解,該類型的註解信息只會保留在.java源碼裏,源碼經過編譯後,註解信息會被丟棄,不會保留在編譯好的.class文件中。
- 編譯時註解:在定義註解接口時,使用@Retention(RetentionPolicy.CLASS)修飾的註解,該註解的註冊信息會保留在.java源碼裏和.class文件裏,在執行的時候,會被Java虛擬機丟棄,不會加載到虛擬機種。
- 運行時註解:在定義註解接口時,使用@Retention(RetentionPolicy.RUNTIME)修飾的註解,Java虛擬機在運行期也保留註解信息,可以通過反射機制讀取註解的信息(.java源碼,.class文件和執行的時候都有註解的信息)
未指定類型時,默認是CLASS類型。
- @Documented:表示被修飾的註解應該被包含在被註解項的文檔中(例如用JavaDoc生成的文檔)。
- @Inherited:表示該註解可以被子類繼承的。
- @Repeatable:表示這個註解可以在同一個項上面應用多次,不過這個註解是在Java8中才引入的,前面四個元註解都是在Java5中就已經引入的。
運行時註解
要定義運行時註解,只需要在聲明註解時指定@Retention(RetentionPolicy.RUNTIME)即可,運行時註解一般和反射機制配合使用,相比編譯時註解性能比較低,但靈活性好,實現起來比較簡答。
編譯時註解
編譯時註解能夠自動處理Java源文件並生成更多的源碼、配置文件、腳本或其他可能想要生成的東西。這些操作時通過註解處理器完成的。Java編譯器集成了註解處理、通過在編譯期間調用javac -processor命令可以調起註解處理器,它能夠允許我們實現編譯時註解的功能,從而提高函數庫的性能。
定義註解處理器
在編譯期間,編譯器會定位到Java源文件中的註解,註解處理器會對其感興趣的註解進行處理,需要注意的是,一個註解處理器只能產生新的源文件,它不能修改一個已經存在的源文件。註解處理器一般通過繼承AbstractProcessor類並實現process方法,同時需要指定這個處理器能夠處理的註解類型以及支持的Java版本,
public class MyProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment env){ }
@Override
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }
@Override
public Set<String> getSupportedAnnotationTypes() { }
@Override
public SourceVersion getSupportedSourceVersion() { }
}
- init(ProcessingEnvironment env):每個註解處理器都必須有個空的構造方法。不過,有一個特殊的init方法,它會被註解處理器工具傳入一個ProcessingEnvironment作爲參數來調用。ProcessingEnvironment提供了一些有用的工具類,如Elements,Types和Filter。我們後面會用到它們。
- process(Set? extends TypeElement> annotations, RoundEnvironment env):這個方法可以看做每個處理器的main方法。你要在這裏寫下你的掃描,判斷和處理註解的代碼,並生成java文件。通過傳入的RoundEnvironment參數,你可以查詢被某個特定註解註解的元素,我們稍後會看到。
- getSupportedAnnotationTypes( ):這裏你需要說明這個處理器需要針對哪些註解來註冊。注意返回類型是一個字符串的Set集合,包含了你要用這個處理器處理的註解類型的全名。
- getSupportedSourceVersion( ):用於指定你使用的java版本。通常返回SourceVersion.latestSupported( )即可,當然也可以明確指定只支持某個Java的版本
從Java7開始,我們也可以使用註解來代替上面的getSupportedAnnotationTypes( )和getSupportedSourceVersion( )方法,語句如下:
@SupportedSourceVersion(SourceVersion.latestSupported())
@SupportedAnnotationTypes({
//該註解處理器支持的所有註解全名字符串類型的集合
})
public class MyProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment env){ }
@Override
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }
}
註冊註解處理器
註冊處理器定義好之後,爲了讓javac -processor能夠對其進行處理,我們需要將註解處利器打包到一個jarwenjianzhong ,tongshi ,xuyao zia jar文件中增加一個名爲javax.annotation.processing.Processor的文件來指明jar文件中有哪些註解處理器,這個文件最終的路徑位於jar文件根目錄中的META-INF/services目錄中,所以你的.jar文件內容看起來會像這個樣子:
MyProcessor.jar
- com
- example
- MyProcessor.class
- META-INF
- services
- javax.annotation.processing.Processor
javax.annotation.processing.Processor文件的內容是註解處理器全路徑名,如果存在多個註解處理器,以換行符進行分隔,
com.example.MyProcessor
com.foo.OtherProcessor
net.blabla.SpecialProcessor
這樣, 一個註解處理器的框架就好了, 完成代碼後編譯成jar文件, 然後像開始介紹的那樣添加依賴就好了.
手動執行上面的註冊過程是很繁瑣的,因此Google開源了一個名爲AutoService的函數庫來解放開發者的雙手,引入這個函數庫之後,我們只需要在定義自己的Processor時使用@AutoService註解標記即可完成上面的註冊步驟,如下:
@AutoServices(Processor.class)
@SupportedSourceVersion(SourceVersion.latestSupported())
@SupportedAnnotationTypes({
//該註解處理器支持的所有註解全名字符串類型的集合
})
public class MyProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment env){ }
@Override
public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }
}
android-apt插件
註解處理器所在的jar文件只有在編譯期間起作用,到應用運行時不會用到,因此在build.gradle中引入依賴時應該以provided方式而不是compile方式引入,語句如下:
dependencies{
provided '***'
compile '***'
}
當然,我們還可以使用android-apt插件的方式。android-apt是在Android Studio中使用註解處理器的一個輔助插件,它的主要作用如下:
- 只在編譯期間引入註解處理器所在的函數庫作爲依賴,不會打包到最終生成的APK中。
- 爲註解處理器生成的源代碼設置好正確的路徑,以便Android Studio能夠正常找到。
android-apt的使用很簡單,首先在使用到註解處理器函數庫的模塊的build.gradle文件中引入apt插件
buildscript {
repositories {
mavenCentral()
}
dependencies {
...
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
接着以apt的方式引入註解處理器函數庫作爲依賴
dependencies {
apt '***'
compile '***'
}
到這裏註解都已經說的差不多了,有什麼問題歡迎留言給我。