安卓面经_安卓基础面全解析<28/30>之注解全解析
牛客高级系列专栏:
- 安卓高频面经解析大全专栏链接:Android高频面经解析大全
- 安卓高频面经解析大全目录详情:安卓面经_Android面经_150道安卓基础面试题目录
- 安卓系统Framework面经专栏链接:Android系统面试题解析大全
- 安卓系统Framework面经目录详情:Android系统面经_Framework开发面经_150道面试题答案解析
- 嵌入式面经解析大全专栏链接:嵌入式面经_C++软件开发面经_111道面试题全解析
- 嵌入式面经解析大全目录详情:111道嵌入式面经题全解析软件开发面经C++面经目录
本人是2020年毕业于广东工业大学研究生:许乔丹,有国内大厂CVTE和世界500强企业安卓开发经验,该专栏整理本人对常见安卓高频开发面试题的理解;
网上安卓资料千千万,笔者将继续维护专栏,一杯奶茶价格不止提供答案解析,更有专栏内容免费技术答疑。助您提高安卓面试准备效率,为您面试保驾护航!
正文开始⬇
本人曾在面试中被面试官问到是否知道注解,当时的我仅回答了,比如@Override表明函数是重写的,@Deprecated表明函数被废弃,接着面试官就没有继续追问了。很明显我的回答让我扣分了。面试官可能会问:
- 你是否了解注解?⭐⭐⭐⭐⭐
- 你知道元注解吗?⭐⭐⭐
- 知道如何使用自定义注解吗?有哪些实现方法?⭐⭐⭐⭐
- 具体说一下编译时注解的实现思路⭐⭐⭐⭐
- 如何使用注解代替枚举?⭐⭐⭐
- 你知道哪些常用的注解?⭐⭐⭐⭐
看完以下的解析,一定可以让面试官眼前一亮。
目录
- 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目录下:
在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就会自动给出提示:
如给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线程 |
#提供免费售后答疑!!花一杯奶茶的钱获得安卓面试答疑服务,稳赚不赔# Android发展已经很多年,安卓资料网上千千万,本专栏免费提供专栏内容技术答疑!!私聊当天必回。在阅读过程或者其他安卓学习过程有疑问,都非常欢迎私聊交流。
