安卓面经_安卓基础面全解析<28/30>之注解全解析

牛客高级系列专栏:

本人是2020年毕业于广东工业大学研究生:许乔丹,有国内大厂CVTE和世界500强企业安卓开发经验,该专栏整理本人对常见安卓高频开发面试题的理解;

网上安卓资料千千万,笔者将继续维护专栏,一杯奶茶价格不止提供答案解析,更有专栏内容免费技术答疑。助您提高安卓面试准备效率,为您面试保驾护航!

正文开始⬇

本人曾在面试中被面试官问到是否知道注解,当时的我仅回答了,比如@Override表明函数是重写的,@Deprecated表明函数被废弃,接着面试官就没有继续追问了。很明显我的回答让我扣分了。面试官可能会问:

  1. 你是否了解注解?⭐⭐⭐⭐⭐
  2. 你知道元注解吗?⭐⭐⭐
  3. 知道如何使用自定义注解吗?有哪些实现方法?⭐⭐⭐⭐
  4. 具体说一下编译时注解的实现思路⭐⭐⭐⭐
  5. 如何使用注解代替枚举?⭐⭐⭐
  6. 你知道哪些常用的注解?⭐⭐⭐⭐

看完以下的解析,一定可以让面试官眼前一亮。

目录

  • 1、注解是什么
  • 2、元注解
    • 2.1 @Retention
    • 2.2 @Target
    • 2.3 @Inherited
    • 2.4 @Documented
    • 2.5 @Repeatable
  • 3、自定义注解的两种使用方式
    • 3.1 运行时注解
    • 3.2 编译时注解
      • 3.2.1 @InitTextView的定义和使用
      • 3.2.2 实现注解处理器AbstractProcessor
        • 3.2.2.1 注解处理器原理
        • 3.2.2.2 注解处理器注册
        • 3.2.2.3 自定义注解处理器解析
      • 3.2.3 自动生成代理文件
      • 3.2.4 代理文件调用
  • 4、使用注解代替枚举
  • 5、常用注解
    • 5.1 null相关注解
    • 5.2 资源相关注解
    • 5.3 线程相关注解
    • 5.4 数值约束注解
    • 5.5 可访问性注解
    • 5.6 其他常用注解

1、注解是什么

注解是JDK 5 引入的,作为代码里的特殊标记。这些标记可以在代码编译、代码运行时执行特定的操作。注解和public、static等修饰符一样,都是程序元素的一部分。开发人员可以在不修改原来代码逻辑的情况下,通过注解来做到:

  • 降低项目的耦合度;
  • 自动完成一些规律性的代码;
  • 自动生成java代码,减轻开发者的工作量。

总结为一句话,注解就是用来简化代码,提高开发效率。

2、元注解

Java有自带的注解,如常见的@Override、@Deprecated、@SuppressWarnnings和 @SafeVarags,称之为“标准注解”。我们使用的各种第三方库,如Retrofit、GreenDao等,都有自己的注解,而且我们还可以自定义注解。因此,注解的种类和功能非常的多,但在这么多的注解上面,截止到Java8,共规定了5个“注解的注解”,它们用来说明解释其它自定义注解,也就是元注解。无论是第三方库的自定义注解还是我们自己自定义的注解,都是基于元注解去定义的。举个例子:

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface InitTextView { //1
    int value() default -1;
    String string() default "Hello World";
}

上面代码中,使用@interface来定义一个自定义注解:“InitTextView”,可以通过default为指定的元素指定一个默认值。同时使用元注解:@Retention和@Target,来修饰该自定义注解,具体的含义是什么呢?现在开始讲解:

2.1 @Retention

Retention就是“保持”的意思,定义了所修饰的注解的生命周期,也就是指定了注解的保留策略:

  • RetentionPoicy.SOURCE:注解只保留在源文件中,当 Java 文件被编译成 class 文件的时候,注解就会被遗弃。常用于做一些检查性的操作,比如 @Override、@SuppressWarnings;

  • RetentionPoicy.CLASS:注解被保留在 class 文件,但 JVM 加载 class 文件时被遗弃,这页是默认的生命周期。常用于在编译时进行一些预处理,比如生成一些辅助代码,例如 ButterKnife;

  • RetentionPoicy.RUNTIME:注解不仅被保存到 class 文件中,JVM 加载 class 文件之后,仍然存在。常用于在运行时动态获取注解信息,大多会与反射一起使用。

SOURCE < CLASS < RUNTIME,前者能作用的地方后者一定也能作用。

2.2 @Target

Target就是“目标”的意思,定义了所修饰的注解所修饰的对象结构:

  • ElementType.CONSTRUCTOR 用于描述构造器
  • ElementType.FIELD 用于描述属性、域声明,包括枚举实例
  • ElementType.LOCAL_VARIABLE 用于描述局部变量
  • ElementType.METHOD 用于描述方法
  • ElementType.PACKAGE 用于描述包
  • ElementType.TYPE 用于描述类、接口或枚举声明
  • ElementType.PARAMETER:用于描述参数,如函数的形参
  • ElementType.ANNOTATION_TYPE:用于描述注解声明
  • ElementType.TYPE_PARAMETER:用于描述类型参数声明
  • ElementType.TYPE_USE:用于描述类型声明

如果未标注,则表示可修饰所有。那么,我们重新回顾第二大节刚开始的代码示例,也就是[注释1] @InitTextView,通过元注解,我们定义了 @InitTextView 会保留在class文件,且所修饰的对象是类的属性(也就是类里定义的变量)。看看@InitTextView在项目中的实际使用,可以用于修饰变量textView,将textView变量和R.id.tv2控件绑定,并设置初始值为“Welcome”。

     @InitTextView(value = R.id.tv2, string = "Welcome")
     TextView textView;

我们可以多次使用@InitTextView注解来初始化多个TextView变量,如果按传统方法初始化TextView需要这么写:

TextView textView;
textView = findViewById(R.id.tv2);
textView.setText("Welcome");

是否更直观的感到注解的便利性了?@InitTextView注解的实现详见3.2小节。

2.3 @Inherited

Inherited就是“继承”的意思,该元注解代表是否允许子类继承父类的注解,默认是 false。

2.4 @Documented

@Documented就是“文件”的意思。该元注解代表所修饰的注解是否会保存到 Javadoc 文档中。

2.5 @Repeatable

Repeatable就是“可重复的”的意思。是JDK1.8新加的,表明当前所修饰的自定义注解的属性值有多个时,自定义注解可以多次使用。

3、自定义注解的两种使用方式

自定义注解使用较多的就是运行时注解和编译时注解。有个点要注意,运行时注解和编译时注解所对应的自定义注解的@Retention注解是不同的。

3.1 运行时注解

我们以动态绑定控件的例子做讲解。首先定义一个自定义注解:

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME) //2
public @interface BindView {
    int value() default  -1;
}

上面 [注释2]表明自定义注解的保存策略是不仅被保存到 class 文件中,JVM 加载 class 文件之后,仍然存在。常用于在运行时动态获取注解信息,大多会与反射一起使用。运行时注解比较简单,就是代码运行时,通过反射将findViewbyId()得到的控件,注入到我们需要的变量中。

// AnnotationActivity.java
public class AnnotationActivity extends AppCompatActivity {

    @BindView(R.id.annotation_tv) //3
    private TextView mTv;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_annotation);

        getAnnotationView(); //4

        mTv.setText("Annotation");
    }

    private void getAnnotationView() { //4
        //获得成员变量
        Field[] fields = this.getClass().getDeclaredFields();

        for (Field field : fields) {
            try {
                //判断注解
                if (field.getAnnotations() != null) {
                    //确定注解类型
                    if (field.isAnnotationPresent(BindView.class)) {
                        //允许修改反射属性
                        field.setAccessible(true);
                        BindView bindView = field.getAnnotation(BindView.class);
                        //findViewById将注解的id,找到View注入成员变量中
                        field.set(this, findViewById(bindView.value()));
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

上面的代码关键看[注释4]getAnnotationView()方法。首先获取当前类里定义的成员变量列表,逐一查询每个变量是否有添加注解,如果有的话,再判断是否是BindView.class这个注解。最后获取到[注释3]的注解,把该注释的值“R.id.annotation_tv”注入到成员变量mTv里面。这样就可以对mTv进行操作了。

然而,众所周知,反射的效率不高。因此,为了提高自定义注解的效率,使用编译时注解更合适。

3.2 编译时注解

3.2.1 @InitTextView的定义和使用

我们以动态绑定控件,并初始化控件内容为例子做讲解。首先定义一个自定义注解@InitTextView:

@Retention(RetentionPolicy.CLASS) //5
@Target(ElementType.FIELD)
public @interface InitTextView {
    int value() default -1;
    String string() default "Hello World";
}

上面 [注释2]表明自定义注解的保存策略是RetentionPolicy.CLASS,即注解被保留在 class 文件,但 JVM 加载 class 文件时被遗弃,RetentionPolicy.CLASS常用于在编译时进行一些预处理。 我们先看看最终的使用:

// MainActivity.java
public class MainActivity extends AppCompatActivity {
    @InitTextView(value = R.id.tv2, string = "Welcome") //6
    TextView textView;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        MyAnnotation.bind(this); //7
    }
}

上述代码一看就懂,在[注释6]完成了textView变量绑定R.id.tv2控件并初始化文本内容为“Welcome”。如何做到的呢?猜测和[注释7]代码有关,很明显是的,我们继续分析。

3.2.2 实现注解处理器AbstractProcessor

3.2.2.1 注解处理器原理

编译时注解的原理: 注解处理器的工作就是代码编译时扫描并处理注解,通过注解处理器来按照我们设置的规则作出相应的处理,通常是设置为自动生成处理注解逻辑的java文件。

所以我们需要创建自己的注解处理器,然后带着如何自动生成处理注解逻辑的java文件的问题继续往下看,因为这个问题是贯彻编译时注解的重要问题。我们来看看自己创建的MyProcessor的内容:

// MyProcessor.java
@AutoService(Processor.class) //8
public class MyProcessor extends AbstractProcessor {
    /**
     * 存放被注解标记的所有变量,按类来划分
     */
    private Map<String, AnnotatedProxyElement> AnnotatedProxyElementMap = new HashMap<>();


    /**
     * 一些初始化操作,获取一些有用的系统工具类
     *
     * @param processingEnv
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        filer = processingEnv.getFiler(); //生成文件的工具类
        messager = processingEnv.getMessager(); //打印信息
        elementUtils = processingEnv.getElementUtils(); //元素操作工具类
        typeUtils = processingEnv.getTypeUtils();//元素操作相关
    }

    /**
     * 设置支持的版本
     *
     * @return 这里用最新的就好
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }


    /**
     * 设置支持的注解类型
     *
     * @return
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        HashSet<String> set = new HashSet<>();
        set.add(InitTextView.class.getCanonicalName());
        return set;
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { //9
        messager.printMessage(Diagnostic.Kind.NOTE, "annotations size--->" + annotations.size());
        // Step1、获取被@InitTextView注解所标记的所有元素集合
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(InitTextView.class);

        //process()方法会调用3次,只有第一次有效,第2,3次调用的话生成.java文件会发生异常
        if (elements == null || elements.size() < 1) {
            return true;
        }

        // Step2、按类名来划分注解元素,因为每个使用注解的类都会生成相应的代理类
        for (Element element : elements) {
            //检查注解是否可用
            checkAnnotationValid(element, InitTextView.class);
            // 10: 获取被注解的成员变量
            VariableElement variableElement = (VariableElement) element;
            //获取该元素的父元素,这里是父类
            TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
            //获取当前被注解变量所在的类的全类名
            String className = typeElement.getQualifiedName().toString();
            //获取当前被注解变量所在的类的包名
            String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString();
            //获取当前注解的参数,根据@InitTextView的定义,value()代表控件id,string()代表文本内容
            int resourceId = element.getAnnotation(InitTextView.class).value();
            String TextContent = element.getAnnotation(InitTextView.class).string();
            //生成annotatedProxyElement对象,同一个类里面的所有注解都在一个annotatedProxyElement中处理
            AnnotatedProxyElement annotatedProxyElement = AnnotatedProxyElementMap.get(className);
            if (annotatedProxyElement == null) {
                annotatedProxyElement = new AnnotatedProxyElement(typeElement, packageName);
                //我们用类名作为key,实现同一个类里面的所有注解都在一个annotatedProxyElement中处理
                AnnotatedProxyElementMap.put(className, annotatedProxyElement); //11
            }
            //生成(控件id-文本内容)的Pair数据对
            Pair myPair = new Pair(resourceId, TextContent);
           //以变量名为key,(控件id-文本内容)为内容放到Map里存储
           annotatedProxyElement.viewVariableElement.put(variableElement.getSimpleName().toString(), myPair);
        }

        // Step3、生成注解逻辑处理类
        for (String key : AnnotatedProxyElementMap.keySet()) {
            AnnotatedProxyElement annotatedProxyElement = AnnotatedProxyElementMap.get(key);
            JavaFile javaFile = JavaFile.builder(annotatedProxyElement.packageName, annotatedProxyElement.generateProxyClass()) // 12
                    //在生成的文件头部添加注释
                    .addFileComment("该类是由MyProcessor类自动生成,请勿编辑")
                    .build();
            try {
                javaFile.writeTo(filer);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return true;
    }
}

3.2.2.2 注解处理器注册

打包注解处理器时需要一个特殊文件:javax.annotation.processing.Processor。目录在当前模块的build目录下:

alt

在javax.annotation.processing.Processor文件里写上我们自定义的注解处理器的全称,就完成了注解处理器的注册。上面代码[注释8]的@AutoService(Processor.class)是使用Google定义的注解。@AutoService用来用来生成 META-INF/services/javax.annotation.processing.Processor 文件的。META-INF目录实际上就是描述打包成jar包文件中的信息的一个目录。所以,在这个案例中,javax.annotation.processing.Processor文件的内容就是MyProcessor的路径。

com.xurui.processor.MyProcessor

3.2.2.3 自定义注解处理器解析

自定义的MyProcessor类中一般需要重写init()、getSupportedSourceVersion()、getSupportedAnnotationTypes()、process()这4个方法。其中init()方法里面有几个系统工具类需要了解下:

注解解析器工具类 解释
Elements 一个用来处理Element的工具类
Types 一个用来处理TypeMirror的工具类
Filer 生成文件的工具类(比如创建class文件)
Messager 打印信息工具类,类似printf函数

还有[注释10]VariableElement也需要了解一下,首先Element代码程序中的一个元素,可以是包名、类名、变量、函数等,但只在编译期存在。具体来说可以分为以下几类:

  • PackageElement :代表Package元素
  • TypeElement :代表代表类或者接口元素
  • VariableElement :代表成员变量,如字段、枚举常量、函数参数、局部变量等
  • ExecutableElement :代表类或者接口中的方法
  • TypeParameterElement:代表类、接口、方法或构造方法元素的泛型参数

最后,看看最重要的[注释9]process()方法。看着代码会比较多,不过大部分代码都有注释了,慢慢看即可。其主要步骤是:

  • 调用getElementsAnnotatedWith(InitTextView.class)获取被@InitTextView标记的所有元素;
  • 逐一遍历这些元素,因为这些元素可能分布在多个类里,所以通过[注释11],把每个类的所有@InitTextView注解都放在annotatedProxyElement里面,接着以类名为key,以annotatedProxyElement为value,放在AnnotatedProxyElementMap里面,这样实现同一个类里面的所有被@InitTextView标注过的元素,都在一个annotatedProxyElement中处理;
  • annotatedProxyElement也是一个Map,定义是
public Map<String, Pair<Integer, String>> viewVariableElement = new HashMap<>();

我们存放的是Map<变量名, Pair<控件id, 文本内容>>,举个例子,如果在同一个类里对两个变量进行@InitTextView注解标注:

    @InitTextView(value = R.id.tv2, string = "Welcome")
    TextView textView;
    
    @InitTextView(value = R.id.tv3, string = "Home")
    TextView textView3;

那么,annotatedProxyElement的内容就是<“textView”, Pair<R.id.tv2, “Welcome”>>和<“textView3”, Pair<R.id.tv3, “Home”>>。

  • 最后再遍历AnnotatedProxyElementMap,这个AnnotatedProxyElementMap包含了每个类里的多个被@InitTextView注解标注的变量和注解的值,在本案例中,AnnotatedProxyElementMap就包含了MainActivity类中,textView变量和textView3变量以及对应的注解的值。通过AnnotatedProxyElement类和JavaFile类生成具体的代理类文件。

如何生成代理文件的,具体看看[注释12]annotatedProxyElement.generateProxyClass()

3.2.3 自动生成代理文件

annotatedProxyElement.generateProxyClass()的代码在AnnotatedProxyElement.java里。

// AnnotatedProxyElement.java
     /**
     * 通过javapoet API生成代理类
     * @return
     */
    public TypeSpec generateProxyClass() {
        //代理类实现的接口
        ClassName viewInjector = ClassName.get("com.xurui.inject_api", "IViewInjector");
        ClassName className = ClassName.get(typeElement);

        //  泛型接口,implements IViewInjector<MainActivity>
        ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get(viewInjector, className);

        //生成接口的实现方法inject()
        MethodSpec.Builder bindBuilder = MethodSpec.methodBuilder("inject")
                .addModifiers(Modifier.PUBLIC)
                .addAnnotation(Override.class) //添加方法注解
                .addParameter(className, "target"); // 13

        for (Map.Entry<String, Pair<Integer, String>> entrySet : viewVariableElement.entrySet()) {
            String fieldName = entrySet.getKey();
            Pair<Integer, String> myField = entrySet.getValue();
            bindBuilder.addStatement("target.$L = ((android.app.Activity) target).findViewById($L);", fieldName, myField.getKey()); //14
            bindBuilder.addStatement("target.$L.setText(\"$L\");", fieldName, myField.getValue()); //15
        }
        MethodSpec bindMethodSpec = bindBuilder.build();

        //创建类
        TypeSpec typeSpec = TypeSpec.classBuilder(proxyClassName)
                .addModifiers(Modifier.PUBLIC)
                .addSuperinterface(parameterizedTypeName) //实现接口
                .addMethod(bindMethodSpec) //添加类中的方法
                .build();

        return typeSpec;
    }

生成java文件可以用FileWriter或者JavaPoet这两个开源库,上面的代码是通过JavaPoet API生成代理类,其中[注释13]是添加函数形参,[注释14-15]是添加函数主体,最终自动生成的文件如下:

// 该类是由MyProcessor类自动生成,请勿编辑
package com.xurui.compileannotation;

import com.xurui.inject_api.IViewInjector;
import java.lang.Override;

public class MainActivity_ViewBinding implements IViewInjector<MainActivity> {
  @Override
  public void inject(MainActivity target) {
    target.textView = ((android.app.Activity) target).findViewById(2131230918);;
    target.textView.setText("Welcome");;
  }
}

可以看到上面的代码,都是按我们规定的内容生成的。到此为止,最终明白注解

@InitTextView(value = R.id.tv2, string = "Welcome")

并不是真的可以省略下面传统的两行代码:

textView = findViewById(R.id.tv2);
textView.setText("Welcome");

而是按我们设定自动生成了新文件,文件里就有我们需要的实现。同理,自定义注解都可以按我们的设定,生成控件操作,代码规则检查,逻辑判断等等各种各样的代码,实现了不同注解的不同功能。那么,现在就剩下最后一个问题,我们如何调用代理文件?

3.2.4 代理文件调用

回顾3.2.1小节的[注释7]MyAnnotation.bind(this); 其实就是通过这句话来调用生成的代理文件。

// MyAnnotation.java
public class MyAnnotation {

    public static final String PROXY = "_ViewBinding";

    /**
     * 供Activity调用
     */
    public static void bind(Activity activity) {
        findProxyActivity(activity).inject(activity);
    }

    /**
     * 根据使用注解的类和约定的命名规则,通过反射找到动态生成的代理类(处理注解逻辑)
     *
     * @param object 调用类对象
     */
    private static IViewInjector findProxyActivity(Object object) { //16
        String proxyClassName = object.getClass().getName() + PROXY;
        Class<?> proxyClass = null;
        try {
            proxyClass = Class.forName(proxyClassName);
            return (IViewInjector) proxyClass.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

MyAnnotation.bind(this)最终会调用到[注释16]:findProxyActivity(),因为代理文件是编译器自动生成的,我们可以通过反射来找到这个类,并调用其中的方法。

那么,到此为止,我们就分析完了@InitTextView(value = R.id.tv2, string = "Welcome")注解是如何生效的。本小节的代码是通过学习旭哥的开源项目 CompileAnnotation(通过编译时注解,实现仿ButterKnife功能),最后附上旭哥的项目地址和项目讲解:

项目地址:CompileAnnotation

项目讲解:Android编译时注解--入门篇

4、使用注解代替枚举

我们知道枚举相对于Int变量或者String变量会带来额外的内存占用,在平常的开发中,就会陷入纠结。如果使用枚举,那么对于Android这种内存要求较高的项目就带来了额外的内存占用。但如果使用Int变量或者String变量,这种方式的参数可以接受任何字符串和整型值,无法把变量范围限制在一定范围内。如果我们希望既能像枚举一样对传入参数做范围限制,又不希望产生额外的内存占用,那就可以使用注解。

比如,我们需要对证书的状态做限制,每个证书的状态必须是[未申请、正常、已过期、已撤销、无效]中的一种。这5种状态我们可以用一个整形变量指定。最后,通过一个方法接受整型参数,并通过注解来要求指定的整型参数必须在我们上述声明的范围内:

public final class Certificate {

    public static final int STATUS_UNKNOWN                    = 0;
    public static final int STATUS_NORMAL                     = 1;
    public static final int STATUS_EXPIRED                    = 2;
    public static final int STATUS_REVOKE                     = 3;
    public static final int STATUS_INVALID                    = 4;

    @IntDef({STATUS_UNKNOWN, STATUS_NORMAL, STATUS_EXPIRED, STATUS_REVOKE, STATUS_INVALID})
    @Retention(RetentionPolicy.SOURCE)
    public @interface CertificateStatus {
    }
}

如上面代码所示,其中@IntDef注解的作用是用于强制使用已定义的整形。如果指定的参数是字符串,那么可以用@StringDef来做限制,@StringDef可用于强制使用已定义的字符串。接着,我们就可以这么使用该注解:

public class CertificateActivity extends AppCompatActivity {
    
    @Certificate.CertificateStatus
    private int mCertificateStatus = Certificate.STATUS_NORMAL;

    public void setCertificateStatus(@Certificate.CertificateStatus int certStatus) { //17
        this.mCertificateStatus = certStatus;
    }
}

如果[注释17]传入的参数不在自定义注解CertificateStatus限制的[0-4]范围内,IDE就会自动给出提示:

alt

如给setCertificateStatus(7)传入了7,IDE就会提醒传入的数值必须是STATUS_UNKNOWN, STATUS_NORMAL, STATUS_EXPIRED, STATUS_REVOKE, STATUS_INVALID其中的一个。

5、常用注解

本节汇总一些比较常见的注解,更多详情见 《官方注解》

5.1 null相关注解

常用注解 解释
@Nullable 指示允许方法返回值、参数和变量为空
@NonNull 指示方法返回值、参数和变量不为空

5.2 资源相关注解

常用注解 解释
@StringRes 表明是一个字符串资源
@DrawableRes 表明是一个图片资源
@DimenRes 表明是一个尺寸资源
@InterpolatorRes 表明是一个插值器资源
@ColorRes和@ColorInt 表明是一个颜色资源,建议用@ColorInt,因为@ColorRes不能把颜色整形(RRGGBB或者AARRGGBB)识别为颜色资源)
@AnyRes 表明是一个任意类型的R资源

5.3 线程相关注解

常用注解 解释
@MainThread和@UiThread 表明是在主线程,正常情况下,两者相等,但是系统应用在不同线程上有多个视图时,两者不相等,此时,@UiThread标注与应用的视图层次结构相关联的方法,使用@MainThread仅用于标注与应用生命周期相关联的方法。
@WorkerThread 表明是在工作线程
@BinderThread 表明是在Binder线程
@AnyThread 表明是在任意线程

5.4 数值约束注解

常用注解 解释
@IntRange 指定一个整形范围的值, @IntRange(from = 0,to = 255)
@FloatRange 指定一个浮点型范围的值, @FloatRange(from = 0.0,to = 1.0)
@IntDef 用于强制使用已定义的整形
@IntRange 用于强制使用已定义的整形范围
@StringDef 用于强制使用已定义的字符串
@Size 检查集合和数组的大小或者字符串的长度, @Size(1)可以作为一个非空判断, @Size(3)指定大小或长度为3, @Size(min = 1)指定最小值为1, @Size(max = 10)指定最大值为10, @Size(mutiple = 2)指定大小或长度为2的倍数

5.5 可访问性注解

常用注解 解释
@VisibleForTesting 指示一个代码块的可见性是否高于让代码变得可测试所需要的水平
@Keep 可以确保标记的指定代码在混淆时不会被混淆。它一般会添加到通过反射访问的方法和类中,以阻止编译器将代码视为未使用。

5.6 其他常用注解

常用注解 解释
@RequiresPermission 验证方法调用方的权限,检查有效权限列表中是否有某个权限用anyOf属性,检查是否有一组有效权限用allOf属性。
@CheckResult 验证方法实际使用的是方法的结果还是返回值。
@CallSuper 用于验证子类方法是否会实现基类方法的超类实现
@BinderThread 表明是在Binder线程

alt

Android高频面试题全解析 文章被收录于专栏

#提供免费售后答疑!!花一杯奶茶的钱获得安卓面试答疑服务,稳赚不赔# Android发展已经很多年,安卓资料网上千千万,本专栏免费提供专栏内容技术答疑!!私聊当天必回。在阅读过程或者其他安卓学习过程有疑问,都非常欢迎私聊交流。

全部评论

相关推荐

12-08 07:42
门头沟学院 Java
27届末九,由于是女生,身边人几乎没有就业导向的,自学只能跟着网课,没人指导,很迷茫。下图是我目前的简历,不知道有需要修改的地方吗?求拷打。下面是目前的学习情况:目前算法过完了一遍力扣100和代码随想录,不过不是很熟,面经看了小林coding、JavaGuide,有一些没用过的技术看得不是很明白,掌握得不是很扎实。再加上常年跟黑马网课听思路,真正自己动手写代码的时间很少,这让我一直不敢投简历,总觉得内里空虚。项目没准备好面试相关的问题,简历上相应的考点不熟。如此种种。。。看到很多很多学长学姐大佬们的面经,愈发觉得面试可怕,自己没准备好,总担心自己是不是无望后端开发了。看到牛客很多同届以及更小一届的同学都找到实习了,很希望自己也能找到实习。而自己又好像摸不到后端学习的门路,只能不断赞叹黑马虎哥写的代码真优雅!微服务架构实在巧妙!消息队列、redis、sentinel、nacos、mybatisplus等等的引入都会让我赞叹这些工具的设计者的巧思,以及包括但不限于Java语言的优雅。然而只是停留在了解的程度,并不熟练。我是很希望能够继续深入探索这些知识的,只不过有一大部分时间都花在学校课程上了。我感觉我被困住了,我一方面必须保证我能够有个不错的学业分使我能有我几乎不想选择的读研退路(还有个原因是复习不全我会焦虑考试挂科,因此我会做好全面的准备,而这一步很费时间),一方面在B站学习各种网课,一方面得考虑提升自己并不扎实的算法基础,另一方面还得准备八股面经。这让我有点苦恼,我好像没那么多时间,因为绝大部分时间都花在了复习学校科目中了。我好像处处用时间,但收效甚微。想问问各位大佬是怎么平衡时间的呢?算法、项目和八股是怎么准备的呢?有什么高效的方法吗?谢谢您们花时间阅读我的稿件!
菜菜狗🐶:大胆投,我当时也是害怕面试,投多了发现根本约不到面🤡
投递哔哩哔哩等公司6个岗位
点赞 评论 收藏
分享
评论
1
4
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务