天天看点

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 自定义注解详解,字节跳动资深面试官亲述