2024年Android最全Android 自定义注解详解,2024年最新熬夜整理小米Android面试题

最后

在这里小编整理了一份Android大厂常见面试题,和一些Android架构视频解析,都已整理成文档,全部都已打包好了,希望能够对大家有所帮助,在面试中能顺利通过。

image

image

喜欢本文的话,不妨顺手给我点个小赞、评论区留言或者转发支持一下呗

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 17

  • 18

4.运行结果:

这里写图片描述

6. 自定义编译期注解(CLASS)

为什么我要最后说编译期注解呢,因为相对前面的自定义注解来说,编译期注解有些难度,涉及到的东西比较多,但其却是平时用到的最多的注解,因为编译期注解不存在反射,所以对性能没有影响。

本来也想用绑定 view 的例子讲解,但是现在这样的 demo 网上真是各种泛滥啊,而且还有各路大牛写的,所以我就没必要班门弄斧了。在我们的实际开发中,肯定都被跳转界面烦过,几乎每个界面都会来个:

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

startActivity (intent);

好烦人,所以本着方便就是改进的原则,让我们定义一个编译期注解,来自动生成上述的代码,想想每次需要的时候只需要一个注解就能跳转到想要跳转的界面是不是很刺激。

1.首先新建一个 android 项目,在创建两个 java module(File -> New -> new Module ->java Module),因为有的类在android项目中不支持,建完后项目结构如下:

这里写图片描述

其中 annotation 中盛放自定义的注解,annotationprocessor 中创建注解处理器并做相关处理,最后的 app 则为我们的项目。

注意:MyFirstProcessor类为上文讲解 AbstractProcessor 所建的类,可以删去,跟本项目没有关系。

2.新建后的三个工程进行依赖,注解处理器必须依赖注解 module ,而app 需要同时依赖注解 module 和 注解处理器 module,这个很好理解,这三个都是独立的,你如果不进行依赖怎么去调用。

annotation

processor

app

3.编写自定义注解,这是一个应用到字段之上的注解,被注解的字段为传递的参数。

/** * 这是一个自定义的跳转传值所用到的注解 * value 表示要跳转到哪个界面activity的元素,传入那个界面的名字 */ @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; } /** * 指定当前注解器使用的Java版本 */ @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<>(); /** * 当getIntent的时候,每种类型写的方式都不一样,所以把每种方式都添加到了Set容器中 */ 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"); } /** * 获取元素所在的包名 * @param element * @return */ public static String getPackageName(Element element) { String clazzSimpleName = element.getSimpleName().toString(); String clazzName = element.toString(); return clazzName.substring(0, clazzName.length() - clazzSimpleName.length() - 1); } /** * 判断是否是String类型或者数组或者bundle,因为这三种类型getIntent()不需要默认值 * @param typeName * @return */ public static boolean isElementNoDefaultValue(String typeName) { return (String.class.getName().equals(typeName) || typeName.contains("[]") || typeName.contains("Bundle")); } /** * 获得注解要传递参数的类型 * @param typeName 注解获取到的参数类型 * @return */ 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 ""; } /** * 获取类的的名字的字符串 * @param typeName 可以是包名字符串,也可以是类名字符串 * @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

5.自定义一个接口,目的是为了把我们需要自动生成的每个java文件的方法都独立出去,使代码更清晰。

/** * Created by zmj on 2017/6/16. * * 定义一个接口,能让每个需要自动生成代码的类都抽象出去 * */ public interface Generator { void genetate(Element typeElement , List<VariableElement> variableElements , ProcessingEnvironment processingEnv); }

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

6.编写自动生成文件的格式,生成后的类格式如下:

跳转类格式

上图为本例中的MainActivity$Enter类,如果你想生成一个类,那么这个类的格式和作用肯定已经在你的脑海中有了定型,如果你自己都不知道想要生成啥,那还玩啥。

/** * 这是一个要自动生成跳转功能的.java文件类 * 主要思路:1.使用javapoet生成一个空方法 * 2.为方法加上实参 * 3.方法的里面的代码拼接 * 主要需要:获取字段的类型和名字,获取将要跳转的类的名字 */ public class ActivityEnterGenerator implements Generator{ private static final String SUFFIX = "$Enter"; private static final String METHOD_NAME = "intentTo"; @Override public void genetate(Element typeElement, List<VariableElement> variableElements, ProcessingEnvironment processingEnv) { MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(METHOD_NAME) .addModifiers(Modifier.PUBLIC) .returns(void.class); //设置生成的METHOD_NAME方法第一个参数 methodBuilder.addParameter(Object.class, "context"); methodBuilder.addStatement("android.content.Intent intent = new android.content.Intent()"); //获取将要跳转的类的名字 String name = ""; //VariableElement 主要代表一般字段元素,是Element的一种 for (VariableElement element : variableElements) { //Element 只是一种语言元素,本身并不包含信息,所以我们这里获取TypeMirror TypeMirror typeMirror = element.asType(); //获取注解在身上的字段的类型 TypeName type = TypeName.get(typeMirror); //获取注解在身上字段的名字 String fileName = element.getSimpleName().toString(); //设置生成的METHOD_NAME方法第二个参数 methodBuilder.addParameter(type, fileName); methodBuilder.addStatement("intent.putExtra(\"" + fileName + "\"," +fileName + ")"); //获取注解上的元素 IntentField toClassName = element.getAnnotation(IntentField.class); String name1 = toClassName.value(); if(name != null && "".equals(name)){ name = name1; } //理论上每个界面上的注解value一样,都是要跳转到的那个类名字,否则提示错误 else if(name1 != null && !name1.equals(name)){ processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "同一个界面不能跳转到多个活动,即value必须一致"); } } methodBuilder.addStatement("intent.setClass((android.content.Context)context, " + name +".class)"); methodBuilder.addStatement("((android.content.Context)context).startActivity(intent)"); /** * 自动生成.java文件 * 第一个参数:要生成的类的名字 * 第二个参数:生成类所在的包的名字 * 第三个参数:javapoet 中提供的与自动生成代码的相关的类 * 第四个参数:能够为注解器提供Elements,Types和Filer */ Utils.writeToFile(typeElement.getSimpleName().toString() + SUFFIX, Utils.getPackageName(typeElement), methodBuilder.build(), processingEnv,null); } }

  • 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

当我们定义了跳转的类,那么接下来肯定就是在另一个界面获取传递过来的数据了,参考格式如下,这是本demo中自动生成的MainActivity$Init 类。

获取参数格式

/** * 要生成一个.Java文件,在这个Java文件里生成一个获取上个界面传递过来数据的方法 * 主要思路:1.使用Javapoet生成一个空的的方法 * 2.为方法添加需要的形参 * 3.拼接方法内部的代码 * 主要需要:获取传递过来字段的类型 */ public class ActivityInitFieldGenerator implements Generator { private static final String SUFFIX = "$Init"; private static final String METHOD_NAME = "initFields"; @Override public void genetate(Element typeElement, List<VariableElement> variableElements, ProcessingEnvironment processingEnv) { MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(METHOD_NAME) .addModifiers(Modifier.PROTECTED) .returns(Object.class); ArrayList<FieldSpec> listField = new ArrayList<>(); if(variableElements != null && variableElements.size() != 0){ VariableElement element = variableElements.get(0); //当前接收数据的字段的名字 IntentField currentClassName = element.getAnnotation(IntentField.class); String name = currentClassName.value(); methodBuilder.addParameter(Object.class, "currentActivity"); methodBuilder.addStatement(name + " activity = (" + name + ")currentActivity"); methodBuilder.addStatement("android.content.Intent intent = activity.getIntent()"); } for (VariableElement element : variableElements) { //获取接收字段的类型 TypeName currentTypeName = TypeName.get(element.asType()); String currentTypeNameStr = currentTypeName.toString(); String intentTypeName = Utils.getIntentTypeName(currentTypeNameStr); //字段的名字,即key值 Name filedName = element.getSimpleName(); //创建成员变量 FieldSpec fieldSpec = FieldSpec.builder(TypeName.get(element.asType()),filedName+"") .addModifiers(Modifier.PUBLIC) .build(); listField.add(fieldSpec); //因为String类型的获取 和 其他基本类型的获取在是否需要默认值问题上不一样,所以需要判断是哪种 if (Utils.isElementNoDefaultValue(currentTypeNameStr)) { methodBuilder.addStatement("this."+filedName+"= intent.get" + intentTypeName + "Extra(\"" + filedName + "\")"); } else { String defaultValue = "default" + element.getSimpleName(); if (intentTypeName == null) { //当字段类型为null时,需要打印错误信息 processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "the type:" + element.asType().toString() + " is not support"); } else { if("".equals(intentTypeName)){ methodBuilder.addStatement("this."+ filedName +"= (" +TypeName.get(element.asType())+ ")intent.getSerializableExtra(\"" + filedName + "\")"); }else{ methodBuilder.addParameter(TypeName.get(element.asType()), defaultValue); methodBuilder.addStatement("this."+ filedName +"= intent.get" + intentTypeName + "Extra(\"" + filedName + "\", " + defaultValue + ")"); } } } } methodBuilder.addStatement("return this"); Utils.writeToFile(typeElement.getSimpleName().toString() + SUFFIX, Utils.getPackageName(typeElement), methodBuilder.build(), processingEnv,listField); } }

  • 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

7、在app项目中,建立MainActivity类并使用刚才的自定义注解。

public class MainActivity extends AppCompatActivity { @IntentField("NextActivity") int count = 10; @IntentField("NextActivity") String str = "编译器注解"; @IntentField("NextActivity") StuBean bean = new StuBean(1,"No1"); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); addOnclickListener(); } public void addOnclickListener() { findViewById(R.id.tvnext).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { /** * 从哪个界面进行跳转,则以哪个界面打头,enter 结尾,例如 MainActivity$Enter */ new MainActivity$Enter() .intentTo(MainActivity.this,count,str,bean); } }); } }

  • 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

8、一个简单的学生bean,主要用于测试可传递Serializable序列化的数据。

/** * Created by zmj on 2017/6/15. */ public class StuBean implements Serializable{ public StuBean(int id , String name) { this.id = id; this.name = name; } //学号 public int id; //姓名 public String name; }

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 17

9、在另一个界面接收并打印数据。

public class NextActivity extends AppCompatActivity { /** 显示数据的TextView*/ private TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_next); textView = (TextView) findViewById(R.id.tv); /** * 想获取从哪个界面传递过来的数据,就已哪个类打头,init结尾,例如 MainActivity$Init */ MainActivity$Init formIntent = (MainActivity$Init)new MainActivity$Init().initFields(this,0); textView.setText(formIntent.count + "---" + formIntent.str + "---" +formIntent.bean.name); //打印上个界面传递过来的数据 Log.i("Tag",formIntent.count + "---" + formIntent.str + "---" + formIntent.bean.name); } }

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 17

  • 18

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

otected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_next); textView = (TextView) findViewById(R.id.tv); /** * 想获取从哪个界面传递过来的数据,就已哪个类打头,init结尾,例如 MainActivity I n i t ∗ / M a i n A c t i v i t y Init */ MainActivity Init/MainActivityInit formIntent = (MainActivity I n i t ) n e w M a i n A c t i v i t y Init)new MainActivity Init)newMainActivityInit().initFields(this,0); textView.setText(formIntent.count + “—” + formIntent.str + “—” +formIntent.bean.name); //打印上个界面传递过来的数据 Log.i(“Tag”,formIntent.count + “—” + formIntent.str + “—” + formIntent.bean.name); } }`

  • 1

  • 2

  • 3

  • 4

  • 5

  • 6

  • 7

  • 8

  • 9

  • 10

  • 11

  • 12

  • 13

  • 14

  • 15

  • 16

  • 17

  • 18

[外链图片转存中…(img-spCsSkUP-1715705335067)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值