天天看點

Android 自定義注解詳解,位元組跳動資深面試官親述

2、由于反射對性能會有一定的損耗,是以上述類型的注解處理器并不占主流,現在使用最多的還是 AbstractProcessor 自定義注解處理器,因為後者并不需要通過反射實作,效率和直接調用普通方法沒有差別,這也是為什麼編譯期注解比運作時注解更受歡迎,但是并不是說為了性能運作期注解就不能用了,隻能說不能濫用,要在性能方面給予考慮。目前主要的用到運作期注解的架構差不多都有緩存機制,隻有在第一次使用時通過反射機制,當再次使用時直接從緩存中取出。好了,說着說着就跑題,我們還是來聊一下這個 AbstractProcessor 類吧,到底有何魅力讓這麼多人為她沉迷,方法如下(注意得在 java Moudle 下哦,android project 沒有提供相關的包):

public class MyFirstProcessor extends AbstractProcessor { @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { Elements es = processingEnvironment.getElementUtils(); Filer filer = processingEnvironment.getFiler(); Types types = processingEnvironment.getTypeUtils(); Messager messager = processingEnvironment.getMessager(); messager.printMessage(Diagnostic.Kind.ERROR,"例如當預設值為空則提示一個錯誤"); SourceVersion version = processingEnvironment.getSourceVersion(); super.init(processingEnvironment); } @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { return false; } @Override public Set<String> getSupportedAnnotationTypes() { return super.getSupportedAnnotationTypes(); } @Override public SourceVersion getSupportedSourceVersion() { return super.getSupportedSourceVersion(); } }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94

5. 自定義運作期注解(RUNTIME)

我們在開發中經常會需要計算一個方法所要執行的時間,以此來直覺的比較哪個實作方式最優,常用方法是開始結束時間相減

System.currentTimeMillis()

但是當方法多的時候,是不是減來減去都要減的懷疑人生啦,哈哈,那麼下面我就來寫一個運作時注解來列印方法執行的時間。

1.首先我們先定義一個注解,并給注解添加我們需要的元注解:

@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface CalculateMethodRunningTime { //要計算時間的方法的名字 String methodName() default "no method to set"; }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

2.利用反射方法在程式運作時,擷取被添加注解的類的資訊:

public class AnnotationUtils { //使用反射通過類名擷取類的相關資訊 public static void getClassInfo(String className) { try { Class c = Class.forName(className); //擷取所有公共的方法 Method[] methods = c.getMethods(); for(Method m : methods) { Class<CalculateMethodRunningTime> ctClass = CalculateMethodRunningTime.class; if(m.isAnnotationPresent(ctClass)){ CalculateMethodRunningTime anno = m.getAnnotation(ctClass); //目前方法包含查詢時間的注解時 if(anno != null){ long beginTime = System.currentTimeMillis(); m.invoke(c.newInstance(),null); long endTime = System.currentTimeMillis(); long time = endTime - beginTime; Log.i("Tag",anno.methodName()+"方法執行所需要時間:" + time + "ms"); } } } } catch (Exception e) { e.printStackTrace(); } } }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

3.在 activity 中使用注解,注意咱們的注解是作用于方法之上的:

public class ActivityAnnotattion extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_anno); AnnotationUtils.getClassInfo("com.annotation.zmj.annotationtest.ActivityAnnotattion"); } @CalculateMethodRunningTime(methodName = "method1") public void method1() { long i = 100000000L; while(i > 0) { i--; } } }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

4.運作結果:

Android 自定義注解詳解,位元組跳動資深面試官親述

6. 自定義編譯期注解(CLASS)

為什麼我要最後說編譯期注解呢,因為相對前面的自定義注解來說,編譯期注解有些難度,涉及到的東西比較多,但其卻是平時用到的最多的注解,因為編譯期注解不存在反射,是以對性能沒有影響。

本來也想用綁定 view 的例子講解,但是現在這樣的 demo 網上真是各種泛濫啊,而且還有各路大牛寫的,是以我就沒必要班門弄斧了。在我們的實際開發中,肯定都被跳轉界面煩過,幾乎每個界面都會來個:

Intent intent = new Intent (this,NextActivity.class);
startActivity (intent);

好煩人,是以本着友善就是改進的原則,讓我們定義一個編譯期注解,來自動生成上述的代碼,想想每次需要的時候隻需要一個注解就能跳轉到想要跳轉的界面是不是很刺激。

1.首先建立一個 android 項目,在建立兩個 java module(File -> New -> new Module ->java Module),因為有的類在android項目中不支援,建完後項目結構如下:

Android 自定義注解詳解,位元組跳動資深面試官親述

其中 annotation 中盛放自定義的注解,annotationprocessor 中建立注解處理器并做相關處理,最後的 app 則為我們的項目。

注意:MyFirstProcessor類為上文講解 AbstractProcessor 所建的類,可以删去,跟本項目沒有關系。

2.建立後的三個工程進行依賴,注解處理器必須依賴注解 module ,而app 需要同時依賴注解 module 和 注解處理器 module,這個很好了解,這三個都是獨立的,你如果不進行依賴怎麼去調用。

Android 自定義注解詳解,位元組跳動資深面試官親述
Android 自定義注解詳解,位元組跳動資深面試官親述
Android 自定義注解詳解,位元組跳動資深面試官親述

3.編寫自定義注解,這是一個應用到字段之上的注解,被注解的字段為傳遞的參數。

@Retention(RetentionPolicy.CLASS) @Target(ElementType.FIELD) public @interface IntentField { String value () default " "; }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

3.自定義注解處理器,擷取被注解元素的類型,進行相應的操作,方法不懂的上文應該都有解釋。

@AutoService(javax.annotation.processing.Processor.class) public class MyProcessot extends AbstractProcessor{ private Map<Element, List<VariableElement>> items = new HashMap<>(); private List<Generator> generators = new LinkedList<>(); @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); Utils.init(); generators.add(new ActivityEnterGenerator()); generators.add(new ActivityInitFieldGenerator()); } @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { //擷取所有注冊IntentField注解的元素 for (Element elem : roundEnvironment.getElementsAnnotatedWith(IntentField.class)) { //主要擷取ElementType 是不是null,即class,interface,enum或者注解類型 if (elem.getEnclosingElement() == null) { //直接結束處理器 return true; } //如果items的key不存在,則添加一個key if (items.get(elem.getEnclosingElement()) == null) { items.put(elem.getEnclosingElement(), new LinkedList<VariableElement>()); } //我們這裡的IntentField是應用在一般成員變量上的注解 if (elem.getKind() == ElementKind.FIELD) { items.get(elem.getEnclosingElement()).add((VariableElement)elem); } } List<VariableElement> variableElements; for (Map.Entry<Element, List<VariableElement>> entry : items.entrySet()) { variableElements = entry.getValue(); if (variableElements == null || variableElements.isEmpty()) { return true; } //去通過自動javapoet生成代碼 for (Generator generator : generators) { generator.genetate(entry.getKey(), variableElements, processingEnv); generator.genetate(entry.getKey(), variableElements, processingEnv); } } return false; } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } @Override public Set<String> getSupportedAnnotationTypes() { Set<String> annotations = new LinkedHashSet<>(2); annotations.add(IntentField.class.getCanonicalName()); return annotations; } }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75

4.這是一個工具類方法,提供了本 demo 中所用到的一些方法,其實實際裡面的方法都很常見,隻不過做了一個封裝而已。

public class Utils { private static Set<String> supportTypes = new HashSet<>(); static void init() { supportTypes.add(int.class.getSimpleName()); supportTypes.add(int[].class.getSimpleName()); supportTypes.add(short.class.getSimpleName()); supportTypes.add(short[].class.getSimpleName()); supportTypes.add(String.class.getSimpleName()); supportTypes.add(String[].class.getSimpleName()); supportTypes.add(boolean.class.getSimpleName()); supportTypes.add(boolean[].class.getSimpleName()); supportTypes.add(long.class.getSimpleName()); supportTypes.add(long[].class.getSimpleName()); supportTypes.add(char.class.getSimpleName()); supportTypes.add(char[].class.getSimpleName()); supportTypes.add(byte.class.getSimpleName()); supportTypes.add(byte[].class.getSimpleName()); supportTypes.add("Bundle"); } public static String getPackageName(Element element) { String clazzSimpleName = element.getSimpleName().toString(); String clazzName = element.toString(); return clazzName.substring(0, clazzName.length() - clazzSimpleName.length() - 1); } public static boolean isElementNoDefaultValue(String typeName) { return (String.class.getName().equals(typeName) || typeName.contains("[]") || typeName.contains("Bundle")); } public static String getIntentTypeName(String typeName) { for (String name : supportTypes) { if (name.equals(getSimpleName(typeName))) { return name.replaceFirst(String.valueOf(name.charAt(0)), String.valueOf(name.charAt(0)).toUpperCase()) .replace("[]", "Array"); } } return ""; } static String getSimpleName(String typeName) { if (typeName.contains(".")) { return typeName.substring(typeName.lastIndexOf(".") + 1, typeName.length()); }else { return typeName; } } public static void writeToFile(String className, String packageName, MethodSpec methodSpec, ProcessingEnvironment processingEnv, ArrayList<FieldSpec> listField) { TypeSpec genedClass; if(listField == null) { genedClass = TypeSpec.classBuilder(className) .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addMethod(methodSpec).build(); }else{ genedClass = TypeSpec.classBuilder(className) .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addMethod(methodSpec) .addFields(listField).build(); } JavaFile javaFile = JavaFile.builder(packageName, genedClass) .build(); try { javaFile.writeTo(processingEnv.getFiler()); } catch (IOException e) { e.printStackTrace(); } } }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103

文末

那麼對于想堅持程式員這行的真的就一點希望都沒有嗎?

其實不然,在網際網路的大浪淘沙之下,留下的永遠是最優秀的,我們考慮的不是哪個行業差哪個行業難,就逃避掉這些,無論哪個行業,都會有他的問題,但是無論哪個行業都會有站在最頂端的那群人。我們要做的就是努力提升自己,讓自己站在最頂端,學曆不夠那就去讀,知識不夠那就去學。人之是以為人,不就是有解決問題的能力嗎?擋住自己的由于隻有自己。點選我的GitHub下述資料免費領取

Android希望=技能+面試

  • 技能
    Android 自定義注解詳解,位元組跳動資深面試官親述
  • 面試技巧+面試題
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103

文末

那麼對于想堅持程式員這行的真的就一點希望都沒有嗎?

其實不然,在網際網路的大浪淘沙之下,留下的永遠是最優秀的,我們考慮的不是哪個行業差哪個行業難,就逃避掉這些,無論哪個行業,都會有他的問題,但是無論哪個行業都會有站在最頂端的那群人。我們要做的就是努力提升自己,讓自己站在最頂端,學曆不夠那就去讀,知識不夠那就去學。人之是以為人,不就是有解決問題的能力嗎?擋住自己的由于隻有自己。點選我的GitHub下述資料免費領取

Android希望=技能+面試

  • 技能

    [外鍊圖檔轉存中…(img-cahNLJda-1643946779855)]

  • 面試技巧+面試題
    Android 自定義注解詳解,位元組跳動資深面試官親述