Android注解之从入门到并没有放弃
定義
注解是代碼里的特殊標記,這些標記可以在編譯、類加載、運行時被讀取,并執(zhí)行相應的處理。
分類
注解分為標準注解和元注解;
標準注解
@Override:對覆蓋超類中的方法進行標記,如果被標記的方法并沒有實際覆蓋超類中的方法,則編譯器會發(fā)出錯誤警告;
@Deprecated:對不鼓勵使用或者已過時的方法添加注解,當編程人員使用這些方法時,將會在編譯時顯示提示信息;
@SuppressWarnings:選擇性地取消特定代碼段中的警告;
@SafeVarargs:JDK7新增,用來聲明使用了可變長度參數(shù)的方法,其在與泛型類一起使用時不會出現(xiàn)類型安全問題;
元注解
用來注解其他注解,從而創(chuàng)建新的注解;
@Target:注解所修飾的對象范圍,取值是一個ElementType類型的數(shù)組,copy了下ElemenType的代碼,以下是可以修飾的類型;
public enum ElementType {/** 能修飾類,接口(包含注解類型),或者枚舉類型 */TYPE,/** 修飾成員變量 */FIELD,/** 修飾方法 */METHOD,/** 修飾參數(shù) */PARAMETER,/** 修飾構(gòu)造方法 */CONSTRUCTOR,/** 修飾局部變量 */LOCAL_VARIABLE,/** 修飾注解 */ANNOTATION_TYPE,/** 修飾包 */PACKAGE,/** 類型參數(shù)聲明 */TYPE_PARAMETER,/** 使用類型 */TYPE_USE }@Retention:用來聲明注解的保留策略 , @Retention注解有三種取值,分別代表不同級別的保留策略;
- RetentionPolicy.SOURCE:源碼級注解;
注解信息只會保留在.java源碼中,源碼在編譯后,注解信息被丟棄,不會保留在.class中; - RetentionPolicy.CLASS:編譯時注解;
注解信息回保留在.java源碼以及.class中,當運行Java程序時,JVM會丟棄該注解信息,不會保留在JVM中; - RetentionPolicy.RUNTIME:運行時注解;
當運行Java程序時,JVM也會保留該注解信息,可以通過反射獲取該注解信息;
?
@Inherited:表示注解可以被繼承;
@Documented:表示這個注解應該被JavaDoc工具記錄;
@Repeatable:JDK8新增,允許一個注解在同一聲明類型(類、屬性或方法)上多次使用;
驗證三種保留策略
-
RetentionPolicy.SOURCE(源碼級注解):
雖然書上說,源碼級注解,在編譯后注解信息會被丟掉,但是我還是自己寫了個例子試了下
上代碼,先看兩個注解;
我們常用的@Override(剛剛提到的標準注解之一),先看源碼:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE)//源碼級注解 public @interface Override { }如果需要自定義一個注解,格式應該是:public @interface 注解名,接下來自定義一個編譯時注解@ClassAnnotation:
public class SuperTest {void methodA(){System.out.println("SuperTest,method methodA");} }public class Test extends SuperTest{@Overridevoid methodA() {super.methodA();System.out.println("Test,method methodA");}@ClassAnnotationvoid methodB(){} }使用javac編譯后,會得到Test.class文件,如下:
public class Test extends SuperTest {public Test() {}//Override沒啦void methodA() {super.methodA();System.out.println("Test,method methodA");}@ClassAnnotation//編譯時注解還在void methodB() {} }可以看到,在編譯后的.class文件中,@Override沒了,但@ClassAnnotation還在,那運行時注解(RetentionPolicy.RUNTIME)呢:
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;@Target({ElementType.METHOD}) @Retention(RetentionPolicy.CLASS) public @interface ClassAnnotation { }這是@ClassAnnotation編譯后的文件,可以看到,@Target注解還是在的,而@Target便是一個運行時注解(RetentionPolicy.RUNTIME);
自定義注解
自定義運行時注解(RetentionPolicy.RUNTIME)
剛剛也提到的@ClassAnnotation便是一個RetentionPolicy.RUNTIME(運行時注解),不過比較簡單;通常呢,我們會把RetentionPolicy.RUNTIME(運行時注解)和反射一塊兒用;
舉個小栗子,先自定義一個注解@GetViewTo,設置其修飾對象為成員變量,是一個運行時注解;
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface GetViewTo {int value() default -1; }然后用這個注解去標注Activity的成員變量mTextView和mButton,這里有個點,就是當注解類只有一個方法且方法名為“value"時,可以直接在注解后的括號里寫明它的值,如下:
@GetViewTo(value = R.id.text_view) private TextView mTextView;@GetViewTo(R.id.button) private Button mButton;其實吧,注解就是一種標記,如果我們只是標記了,但對標記不做任何處理,也是沒什么用的,下面來對@GetViewTo這個注解做點’處理’:
private void getAnnotations() {try {//獲取MainActivity的成員變量Field[] fields = getClass().getDeclaredFields();for (Field field : fields) {if (null != field.getAnnotations()) {//確定注解類型if (field.isAnnotationPresent(GetViewTo.class)) {//允許修改反射屬性field.setAccessible(true);GetViewTo getViewTo = field.getAnnotation(GetViewTo.class);//findViewById將注解的id,找到View注入成員變量中field.set(this, findViewById(getViewTo.value()));}}}} catch (IllegalAccessException e) {e.printStackTrace();} }然后設置一下Button的點擊事件,編譯運行,可以看到,mButton和mTextView的id,通過@GetViewTo綁定成功了;
@Override protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);getAnnotations();mButton.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {Toast.makeText(MainActivity.this,"text on textview is :" + mTextView.getText(), Toast.LENGTH_SHORT).show();}}); }自定義編譯時注解(RetentionPolicy.CLASS)
在自定義編譯時注解之前,我們先了解以下注解處理器,通常,我們在處理運行時注解時,會采用反射機制處理,如上述的@GetViewTo注解這個栗子;
而在處理編譯時注解時,我們通常會采用注解處理器AbstractProcessor去處理,AbstractProcessor是個抽象類,這里面會有4個比較重要的方法,通常我們自定義注解,都會重寫這四個方法,注釋里表明了它們的作用,如下:
/*** 初始化處理器,一般在這里獲取我們需要的工具類* @param processingEnvironment*/@Overridepublic synchronized void init(ProcessingEnvironment processingEnvironment) {super.init(processingEnvironment);}/*** 指定注解處理器是注冊給哪個注解的,返回指定支持的注解類集合* @return*/@Overridepublic Set<String> getSupportedAnnotationTypes() {return super.getSupportedAnnotationTypes();}/*** 指定java版本* @return*/@Overridepublic SourceVersion getSupportedSourceVersion() {return super.getSupportedSourceVersion();}/*** 處理器實際處理邏輯入口* @param set* @param roundEnvironment* @return*/@Overridepublic boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {return false;}我們平常去餐廳,會點餐,然后根據(jù)所點食物的名稱,餐廳服務員會告訴我們多少錢,就拿這個作為栗子吧;
這里定義一個ChineseFood接口,以及它的兩個實現(xiàn)類Dumplings和Noodles,還有Restaurant類(負責點餐),看代碼,相信不用過多解釋了;
public interface ChineseFood {float getPrice(); }public class Dumplings implements ChineseFood {@Overridepublic float getPrice() {return 16f;} }public class Noodles implements ChineseFood {@Overridepublic float getPrice() {return 10.5f;} }public class Restaurant {private ChineseFood order(String name){if ("noodles".equals(name)){return new Noodles();}if ("dumplings".equals(name)){return new Dumplings();}throw new IllegalArgumentException("Unknown ChineseFood name : " + name);}public static void main(String[] args) throws IOException {Restaurant restaurant = new Restaurant();ChineseFood food = restaurant.order("dumplings");System.out.println("dumplings's price is " + food.getPrice());} }我們試著用注解來解決這個問題;
step1
先新建一個Java Library,命名為annotations,在這里定義一個注解@Factory,這里有個type字段,比如,如果我們?nèi)ゲ蛷d,并不是點的ChineseFood,而是Pizza或者Hamburger,那它們便不屬于ChineseFood,而是WesternFood,而id這個字段,便是我們?yōu)槊糠NChineseFood指定的唯一id,譬如"noodles";
@Target(ElementType.TYPE) @Retention(RetentionPolicy.CLASS) public @interface Factory {/*** 所屬的type* @return*/Class type();/*** 唯一的id* @return*/String id(); }故,我們給@Factory這個注解定義一些規(guī)則:
1. 只有類可以被@Factory注解,因為接口或者抽象類并不能用new操作實例化;
2. 被@Factory注解的類必須直接或者間接的繼承于type()指定的類型;
3. id只能是String類型,并且在同一個type組中必須唯一;
4. 具有相同的type的注解類,將被聚合在一起生成一個工廠類。這個生成的類使用Factory后綴,例如type = ChineseFood.class,將生成ChineseFoodFactory工廠類;
step2
新建一個Java Library,命名為processor,新建一個類FactoryProcessor,繼承AbstractProcessor,然后設置指定的注解和java版本,Init方法一般用來獲取一些工具類;
public class FactoryProcessor extends AbstractProcessor {private Messager mMessager;private Elements mElementUtils;private Types mTypeUtils;private Filer mFiler;/*** 初始化處理器,一般在這里獲取我們需要的工具類* @param processingEnvironment*/@Overridepublic synchronized void init(ProcessingEnvironment processingEnvironment) {super.init(processingEnvironment);mMessager = processingEnvironment.getMessager();mElementUtils = processingEnvironment.getElementUtils();mTypeUtils = processingEnvironment.getTypeUtils();mFiler = processingEnvironment.getFiler();}/*** 指定注解處理器是注冊給哪個注解的,返回指定支持的注解類集合* @return*/@Overridepublic Set<String> getSupportedAnnotationTypes() {Set<String> annotations = new LinkedHashSet<>();annotations.add(Factory.class.getCanonicalName());return annotations;}/*** 指定java版本,一般返回最新版本即可* @return*/@Overridepublic SourceVersion getSupportedSourceVersion() {return SourceVersion.latestSupported();}/*** 處理器實際處理邏輯入口* @param set* @param roundEnvironment* @return*/@Overridepublic boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {return true;} }我們看下通過init方法獲得的四個工具類,這里先做簡單的介紹:
- Messager:提供給注解處理器一個報告錯誤、警告以及提示信息的途徑,它能夠打印非常優(yōu)美的錯誤信息。除此之外,你還可以連接到出錯的元素。在像IntelliJ這種現(xiàn)代的IDE(集成開發(fā)環(huán)境)中,第三方開發(fā)者可以直接點擊錯誤信息,IDE將會直接跳轉(zhuǎn)到第三方開發(fā)者項目的出錯的源文件的相應的行;
- Elements:一個用來處理Element的工具類;
- Types:一個用來處理TypeMirror的工具類;
- Filer:使用Filer我們可以創(chuàng)建文件;
step3
為了能夠使用注解處理器,我們需要用一個服務文件來注冊它。比較簡單的處理方式,是使用Google開源的AutoService,在Project Structure里搜索“auto-service"查找該庫并添加,最后再在注解處理器中添加@AutoService(Processor.class)就可以了;
@AutoService(Processor.class) public class FactoryProcessor extends AbstractProcessor{ }接下來,需要在app/build.gradle下添加:
dependencies {implementation project(':annotations')annotationProcessor project(':processor') }step4
這里主要是process()的實現(xiàn),剛剛定義的4條規(guī)則,在process里面我們會一一實現(xiàn);
如若被注解的元素不滿足規(guī)則,我們會拋出異常,這里自定義一個ProcessorException類,然后通過Messager(這是在init()方法中獲得的工具類)來打印相關(guān)信息:
public class ProcessorException extends Exception{private Element element;public ProcessorException(Element element, String msg, Object... args) {super(String.format(msg, args));this.element = element;}public Element getElement() {return element;} }//在FactoryProcessor類中private void showError(ProcessorException e){mMessager.printMessage(Diagnostic.Kind.ERROR,e.getMessage(),e.getElement());}第一條規(guī)則:
只有類可以被@Factory注解,因為接口或者抽象類并不能用new操作實例化;
第二條規(guī)則:
被@Factory注解的類必須直接或者間接的繼承于type()指定的類型;
為了方便以后的操作,這里先定義一個FactoryAnnotationClass類,將被@Factory注解的類的關(guān)鍵信息保存下;
public class FactoryAnnotationClass {/*** 被Factory注解的元素*/private TypeElement element;/*** {@link Factory#id()} 注解時設置的id*/private String id;/*** {@link Factory#type()} 注解時設置的type類型的合法全名;*/private String superTypeName;public FactoryAnnotationClass(TypeElement element) {this.element = element;Factory factory = element.getAnnotation(Factory.class);id = factory.id();superTypeName = getRequiredTypeClassName(element);}private String getRequiredTypeClassName(TypeElement currentElement){Factory factory = currentElement.getAnnotation(Factory.class);String requiredTypeClassName;try {//這種情況是針對第三方jar包Class clazz = factory.type();requiredTypeClassName = clazz.getCanonicalName();}catch (MirroredTypeException e){//平常在源碼上注解時,都會走到這里DeclaredType declaredType = (DeclaredType) e.getTypeMirror();TypeElement element = (TypeElement) declaredType.asElement();requiredTypeClassName = element.getQualifiedName().toString();}return requiredTypeClassName;}}注釋寫的很清楚了,主要是去獲得type成員的合法全名時,直接通過getCanonicalName()可能會失敗;
- 當type為第三方jar包時,這個類已經(jīng)被編譯,是可以直接通過Class去獲取的;
- 但如果是被@Factory注解的源碼,即我們這個栗子的情況,會拋MirroredTypeException異常,此時便需要通過DeclaredType去獲取type的合法全名;
接下來,在process方法中驗證第二條規(guī)則
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {...//強轉(zhuǎn)TypeElement typeElement = (TypeElement) element;FactoryAnnotationClass currentClass = new FactoryAnnotationClass(typeElement);//2.被@Factory注解的類必須直接或者間接的繼承于type()指定的類型checkType(currentClass); }private void checkType(FactoryAnnotationClass annotationClass) throws ProcessorException {String superTypeName = annotationClass.getSuperTypeName();TypeElement typeElement = annotationClass.getElement();//根據(jù)type的class名來獲取TypeElementTypeElement requiredElement = mElementUtils.getTypeElement(superTypeName);//若type類是一個接口if (ElementKind.INTERFACE == requiredElement.getKind()){// 檢查接口是否實現(xiàn)了if (!typeElement.getInterfaces().contains(requiredElement.asType())) {throw new ProcessorException(typeElement, "The class %s annotated with @%s must implement the interface %s",typeElement.getQualifiedName().toString(), Factory.class.getSimpleName(),superTypeName);}}else {// 檢查子類TypeElement currentClass = typeElement;while (true) {TypeMirror superClassType = currentClass.getSuperclass();if (superClassType.getKind() == TypeKind.NONE) {// 到達了基本類型(java.lang.Object), 所以退出throw new ProcessorException(typeElement, "The class %s annotated with @%s must inherit from %s",typeElement.getQualifiedName().toString(), Factory.class.getSimpleName(),superTypeName);}if (superClassType.toString().equals(superTypeName)) {// 找到了要求的父類break;}// 在繼承樹上繼續(xù)向上搜尋currentClass = (TypeElement) mTypeUtils.asElement(superClassType);}} }checkType()方法走完后,就代表當前的Element是符合前兩條規(guī)則的,這時候,我們將這些復合規(guī)則的Element存起來;
在這里,定義FactoryGroup類,里面有個HashMap的成員變量,我們的邏輯是,先將具有相同type類型的Element放入一個set集合中,再把type類型的合法全名做key,將這個set集合做為value;
這里存儲set集合的過程中,需要考慮到第三條規(guī)則,即:
id只能是String類型,并且在同一個type組中必須唯一;
public class FactoryGroup {/*** key:Factory type 類型的合法全名*/private Map<String,Set<FactoryAnnotationClass>> mGroupMap;FactoryGroup() {mGroupMap = new HashMap<>();}void add(FactoryAnnotationClass annotationClass) throws ProcessorException {Set<FactoryAnnotationClass> set = mGroupMap.get(annotationClass.getSuperTypeName());if (null == set){set = new HashSet<>();set.add(annotationClass);mGroupMap.put(annotationClass.getSuperTypeName(),set);}else {for (FactoryAnnotationClass factoryAnnotationClass : set){//3.id只能是String類型,并且在同一個type組中必須唯一;if (annotationClass.getId().equals(factoryAnnotationClass.getId())){throw new ProcessorException(annotationClass.getElement(),"Conflict: The class %s annotated with @%s with id ='%s' but %s already uses the same id",annotationClass.getElement().getQualifiedName().toString(),Factory.class.getSimpleName(),annotationClass.getId(),factoryAnnotationClass.getElement().getQualifiedName().toString());}}set.add(annotationClass);}} }接著,在process中調(diào)用FactoryGroup的add()方法
public class FactoryProcessor extends AbstractProcessor {private FactoryGroup mFactoryGroup;@Overridepublic boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {for (Element element : roundEnvironment.getElementsAnnotatedWith(Factory.class)){//符合條件的加入到group中mFactoryGroup.add(currentClass);}...} }最后,看第四條規(guī)則:
具有相同的type的注解類,將被聚合在一起生成一個工廠類。這個生成的類使用Factory后綴,例如type = ChineseFood.class,將生成ChineseFoodFactory工廠類
也就是說,我們需要為相同type的注解類,生成一個類,這個類以type的簡單名字加Factory結(jié)尾,譬如,在這個栗子,就應該是ChineseFoodFactory,具體生成類代碼如下,在FactoryGroup類中:
/*** 具有相同的type的注解類,將被聚合在一起生成一個工廠類。* 這個生成的類使用*Factory*后綴,* 例如type = ChineseFood.class,將生成ChineseFoodFactory工廠類;* @param elementUtils* @param filer* @throws IOException*/ void createFactory(Elements elementUtils, Filer filer) throws IOException {if (null != mGroupMap){for (String typeName : mGroupMap.keySet()){TypeElement superTypeElement = elementUtils.getTypeElement(typeName);PackageElement pkg = elementUtils.getPackageOf(superTypeElement);//isUnnamed;如果此包是一個未命名的包,則返回 true,否則返回 falseString packageName = pkg.isUnnamed() ? null : pkg.getQualifiedName().toString();//創(chuàng)建一個create方法MethodSpec.Builder method = MethodSpec.methodBuilder("create").addModifiers(Modifier.PUBLIC).addParameter(String.class, "id").returns(TypeName.get(superTypeElement.asType()));// 判斷id是否為空method.beginControlFlow("if (id == null)").addStatement("throw new IllegalArgumentException($S)", "id is null!").endControlFlow();Set<FactoryAnnotationClass> classSet = mGroupMap.get(typeName);// 根據(jù)id去返回各自的實現(xiàn)類for (FactoryAnnotationClass item : classSet) {method.beginControlFlow("if ($S.equals(id))", item.getId()).addStatement("return new $L()", item.getElement().getQualifiedName().toString()).endControlFlow();}method.addStatement("throw new IllegalArgumentException($S + id)", "Unknown id = ");TypeSpec typeSpec = TypeSpec.classBuilder(superTypeElement.getSimpleName().toString() + Factory.class.getSimpleName()).addMethod(method.build()).build();JavaFile.builder(packageName, typeSpec).build().writeTo(filer);}} }然后我們需要在FactoryProcessor的process中,在結(jié)束for循環(huán)后調(diào)用createFactory方法:
private FactoryGroup mFactoryGroup;@Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {for (Element element : roundEnvironment.getElementsAnnotatedWith(Factory.class)){//符合條件的加入到group中mFactoryGroup.add(currentClass);}...mFactoryGroup.createFactory(mElementUtils,mFiler);mFactoryGroup.clear(); }這里先埋個點,即在process中調(diào)用了mFactoryGroup.clear(),這是為啥?
ok,到這里,FactoryProcessor基本已經(jīng)實現(xiàn)了。
step5
用@Factory注解Noodles和Dumplings兩個類:
step6
先clean project,再build一下,會看到生成了ChineseFoodFactory.java的文件
`
具體代碼如下:
接下來,就可以"點餐"了,我點了個餃子…
public class Restaurant {public static void main(String[] args) throws IOException {ChineseFoodFactory factory = new ChineseFoodFactory();ChineseFood food = factory.create("dumplings");System.out.println("dumplings's price is " + food.getPrice());} }題外話:
還記得剛剛說的,在process中調(diào)用了mFactoryGroup.clear()嗎?
是這樣的,在這個栗子中,FactoryProcessor在處理注解時,process方法會運行3次:
- 第一次時,處理了Dumplings.java和Noodles.java上的@Factory注解;同時也生成了ChineseFoodFactory.java文件,此時,ChineseFoodFactory上也有可能被注解;
- 第二次時,處理ChineseFoodFactory.java上的注解;
- 第三次時,沒有被@Factory注解的元素,over;
*代碼地址:https://github.com/suya1994/annotations.git
*參考資料:
1.https://www.jianshu.com/p/9ca78aa4ab4d
2.https://race604.com/annotation-processing/
總結(jié)
以上是生活随笔為你收集整理的Android注解之从入门到并没有放弃的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ADC外围电路的设计
- 下一篇: android sina oauth2.