Android註解筆記
簡介
關於註解的文章網上也很多了,文章也很不錯,本文總結記錄在開發註解時所踩得坑,希望對你在開發中有所幫助。
不太清楚註解是何物?以及如何開發使用註解可以參考一下鏈接:
秒懂,Java 註解 (Annotation)你可以這樣學
Android中註解的使用
Android編譯時註解
註解概念
註解,可以理解爲一種標籤!對代碼貼上(標籤)註解,能使我們快速認識代碼,以及輔助代碼完成某一特定的功能。
註解
@Retention(CLASS) @Target(FIELD)
public @interface BindView {
/** View ID to which the field will be bound. */
@IdRes int value();
}
作用對象@Target
它指明我們這個BindView註解可以用在哪些地方,@Target可以取以下值:
public enum ElementType {
TYPE,
FIELD,
METHOD,
PARAMETER,
CONSTRUCTOR,
LOCAL_VARIABLE,
ANNOTATION_TYPE,
PACKAGE,
TYPE_PARAMETER,
TYPE_USE
}
比如Android中的ButterKnife中@BindView爲FILED,可以用在成員屬性上
生存範圍@Retention
它指明註解存活使用的生命範圍,可以取以下值:
public enum RetentionPolicy {
SOURCE, 源代碼有效
CLASS, 編譯時有效
RUNTIME 運行時有效
}
如@Override,爲SOURCE只是在源代碼有效,起標識作用,因爲方法重寫是java的語言特性,而不是Override帶來的功能,所以只是標識作用,重寫方法時要不要他都無所謂
註解成員
註解內部沒有方法,只有成員以及默認default初值,在我們使用註解的時候爲這些成員賦值,最後在註解解析器時會用到這些值
註解原理
開發時,我們開發了註解,並完成了作用對象和生存範圍,然後再把我們開發的註解,如BindView用到其他代碼處,這個時候除了SOURCE類型的註解外,還不能正常工作,因爲它還需要一個註解解析器。
一般來說,註解解析器都是運用了反射原理,掃描或者獲取java類中的註解,然後反射獲取該註解修飾的成員、方法、參數等,然後把註解的賦值,通過反射爲這些成員賦值,或者調用其他方法來完成相應的功能
編譯時註解
編譯時註解只在編譯時有效,一般來說,編譯時註解是爲了生存一些中間輔助類,然後運行時用到。
首先,我們在開發在Module裏面開發,然後在主工程app內引入
apt-annotation是我們開發的註解工程(如BindView.java),java-lib
apt-api是開發註解的注入工程(如ButterKnife.java),android-lib
apt-processor則是註解解析器工程,java-lib,這部分通過annotationProcessor引入主工程,源代碼將不會封裝到apk
- apt-processor需要引入的工程:
implementation project(':apt-annotation')
implementation 'com.squareup:javapoet:1.10.0'
implementation 'com.google.auto.service:auto-service:1.0-rc4'
apt-annotation是定義的註解工程,因爲註解解析器需要知道我們要分析哪些註解
javapoet依賴主要是用於編譯時執行
auto-service是爲了讓我們自動執行我們的解析器用的
但是這裏我開發完成後並沒有執行,最後還是我執行了我的註解解析器在哪裏才執行了
在apt-processor工程下創建src/main/resources/META-INF/services/javax.annotation.processing.Processor,在這個文本文件中指明我的解析器,編譯就自動生成了我的java文件
- 開發解析器
@AutoService(Processor.class)
//我需要解析哪些註解,可以是多個參數
@SupportedAnnotationTypes("com.my.BindeView")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class XAptProcessor extends AbstractProcessor{
@Override 這個方法就是解析器的核心,執行邏輯再此
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindeView.class);
for (Element ele : elements) {
1. 拿到每個Element的所屬類
2. 獲取ele的註解以及註解裏面的值
3. 類名爲key 註解以及ele爲value存儲到Map中
}
讀取完所有註解後
4. 依次取出每個類,以及其註解
5. 爲每個類創建方法MethodSpec
6. 爲每個類創建輔助類TypeSpec
7. 創建java文件JavaFile
}
}
- 編寫註解注入類,在代碼中調用此類,利用反射生成我們的輔助類
public class XcInject {
public static void bind(Object host) {
String name = host.getClass().getName();
try {
Class<?> aClass = Class.forName(name + "_ViewInjector");
Method method = aClass.getMethod("inject", host.getClass(), Object.class);
method.invoke(aClass.newInstance(),host,host);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
最後,我們在代碼中調用XcInject.bind(this),就是在內存中生成我們的輔助類了,在用反射調用方法即可
運行時註解
運行時註解和上面類似,但是是在運行時JVM中執行,在運行時,通過獲取類的class,然後獲取註解以及修飾成員,反射進行賦值等
總結
註解是爲了提高我們開發效率而產生的,要注意它的使用場景和需求,因爲他很多地方都用到了反射原理,如果某些場景優先時間效率,儘量不要使用運行時註解,建議換成編譯時註解,使用反射較少,或者用其他代碼邏輯替換