关于Android注解这些基础,这些都不知道?历时半个月呕心之作
Posted 初一十五啊
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于Android注解这些基础,这些都不知道?历时半个月呕心之作相关的知识,希望对你有一定的参考价值。
前言
前段时间去三亚旅游了一圈,玩是玩嗨了,也晒黑了。最后还被隔离了一段时间,无奈有点无聊,就静下心来收集内容来刷面试题。好在前前后后历时半个月,终于出土了😃
饭要一口一口吃,路要一步一步走。先从注解,泛型聊起。后续在聊性能优化再到架构,音视频,flutter等等。发车🏎️ 。
android面试题
💡 一丶架构设计核心-注解
视频:
1.1Android 注解入门以及自定义注解
Java 注解是JDK5.0 引入的一种注释机制。可以在类、方法、变量、参数和包都可以被标注。注解对注解的代码没有直接影响,之所以产生作用, 是对其解析后做了相应的处理。
JDK5.0内置了3个标准注解,4个元注解
-
@Override
,检查方法是否是重写方法,如父类或引用的接口中没有该方法,则报编译报错。 -
@Desprecated
,标记元素被废弃,使用该注解的元素,编译器会发出警告。 -
@SuppressWarnings
,指示编译器忽略警告信息。 -
@Target
,标记注解可用在什么地方(元注解) -
@Retention
,标记注解可用在什么地方(元注解) -
@Documented
,标记注解是否出现在Javadoc
中(元注解) -
@Inherited
,标记标注的Annotation
是否具有继承性(元注解)
在JDK7.0之后,额外增加了3个注解:
-
SafeVarargs
,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告(Java 7支持) -
FunctionalInterface
,标识一个匿名函数或函数式接口(Java 8支持) -
Repeatable
,标识某注解可以在同一个声明上使用多次(Java 8支持)
定义注解
注解的通用定义如下,关键字@interface
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation
定义一个注解,它的名字是 MyAnnotation
,之后可以通过 @MyAnnotation
在代码中使用它。其中 @Documented
,@Target
,@interface
都是修饰 MyAnnotation
的。
@MyAnnotation
public void annotationTest()
@interface
定义注解时,意味着它实现了 java.lang.annotation.Annotation
接口,即该注解就是一个 Annotation
,定义 Annotation
时,@interface
是必须的。
@Documented
表示注解是否出现在 javadoc
中,缺省则表示不出现在javadoc
中。
@Target
作用指定Annotation
的类型,如缺失@Targetz
则Annotation
可用于任何地方,否则用于指定的地方。
@Retention
作用指定Annotation
的策略。
元注解
@MyAnnotation
的注解中又引入了其它注解,如 @Target,@Retention
,作用在其它注解上注解称为元注解
元注解有四个,@Target,@Retention,@Documented,@Inherited
@Target(ElementType.TYPE)
,标记注解可用在什么地方
package java.lang.annotation;
public enum ElementType
TYPE, //应用于接口、类、枚举、注解
FIELD, //应用于属性字段、枚举常量
METHOD, //应用于方法
PARAMETER, //应用于方法的参数
CONSTRUCTOR, //应用于构造函数
LOCAL_VARIABLE, //应用于局部变量
ANNOTATION_TYPE, //应用于注解类型@Retention注解中使用该属性
PACKAGE, //应用于包声明
TYPE_PARAMETER, //应用于类型泛型,即泛型方法、泛型类、泛型接口(jdk1.8)
TYPE_USE //应用于类型使用,可用于出标注任意类型,除了class(jdk1.8)
@Retention(RetentionPolicy.RUNTIME)
,标记注解的保存策略
package java.lang.annotation;
public enum RetentionPolicy
SOURCE, //源文件,编译时丢弃,class字节码文件中不包含
CLASS, //默认策略,编译时保留,class字节码中存在,运行时丢弃,运行时无法获得
RUNTIME //编译时保留,运行时保留,可通过反射获得
@Documented
,标记注解是否出现在 Javadoc
中,缺省不出现在javadoc
中
@Inherited
,标记标注的Annotation是否具有继承性,类使用@Inherited
修饰的注解,子类会继承该注解,注意接口或者父类的方法使用@Inherited
修饰,子类不会继承这个注解。
@Override
的实现如下所示,其中@Target
为ElementType.METHOD
,表示@Override
注释只可使用在方法上,不可使用在类、构造函数等上面。@Retention
为RetentionPolicy.SOURCE
表示@Override
的生效阶段只在源代码阶段,编译时和运行时无意义。@Documented
缺失,表示@Override
注解不会出现在javadoc
中。
package java.lang;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override
1.2注解作用
作用一 :Annotation
具有"让编译器进行编译检查的作用"。例如 @Override@Deprecated @SuppressWarnings
都具有编译检查作用。
比如 某个方法被@Override
标注,则意味着该方法会覆盖父类中的同名方法,如方法被 @Override
修饰,父类中没有同名方法,则编译器报错。
比如某个方法被@Deprecated
标注,则意味着该方法不再被建议使用,如开发人员试图使用,编译器则会给出相应的提示信息。
作用二 :在编译和运行时解析和使用Annotation
。
获取和解析注解内容,做相应的逻辑处理,具体参考自定义注解代码实现。比如很多第三方框架的实现ButterKnift,EventBus
等是基于此方法。
注解示例
自定义一个注解,命名为RoyAnnotation
@Retention(RetentionPolicy.RUNTIME)
@interface RoyAnnotation
String[] value() default "unknown";
使用RoyAnnotation
修饰testMethod
方法
class TestAnnotation
@RoyAnnotation(value = "hello", "world")
public void testMethod()
运行时解析注解信息,做相应的逻辑处理
Class<TestAnnotation> testAnnotationClass = TestAnnotation.class;
Method method = testAnnotationClass.getMethod("testMethod");
if (method.isAnnotationPresent(RoyAnnotation.class))
Method method = testAnnotationClass.getMethod("testMethod");
if (method.isAnnotationPresent(RoyAnnotation.class))
RoyAnnotation royAnnotation = method.getAnnotation(RoyAnnotation.class);
String[] values = royAnnotation.value();
for (String str:values)
System.out.printf(str+", ");
Annotation[] annotations = method.getAnnotations();
for(Annotation annotation: annotations)
System.out.println(annotation);
运行时注解
运行时注解,@Retention
设置为RetentionPolicy.RUNTIME
,需要注意的是,JVM
运行时通过反射注解处理的方式会对性能造成影响,日常开发中很少使用这样的方式。
通常获取组件通过findViewById
方式,如果引用组件比较多,需要写大量的重复代码:
public TextView textView = findViewById(R.id.showInfo);
可以通过注解,实现Android依赖注入,定义一个注解@BindView:
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView
int value();
创建注解处理器
public class InjectUtils
public static void inject(Activity activity) throws IllegalAccessException
Field[] fields = activity.getClass().getDeclaredFields();
for (Field field : fields)
BindView bindView = field.getAnnotation(BindView.class);
if (bindView != null)
int value = bindView.value();
View view = activity.findViewById(value);
try
field.set(activity, view);
catch (IllegalAccessException e)
e.printStackTrace();
activity
中使用方式:组件添加@BindView
注解,调用InjectUtils.inject(this)
通过反射处理逻辑
public class MainActivity extends AppCompatActivity
@BindView(R.id.showInfo)
public TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
try
InjectUtils.inject(this);
catch (IllegalAccessException e)
e.printStackTrace();
textView.setText("helloWorld");
编译时注解
编译时注解的核心是APT(Annotation Processing Tools)
,原理是代码中设置注解,在编译时检查AbstractProcessor
子类,并调用该类型的process
函数,将所有添加注解的元素传递到process
中,在process
做相应逻辑处理。
创建一个注解处理器MyProcessor
,首先需要继承AbstractProcessor
类,AbstractProcessor
类只在Java中有,Android库不存在,在项目中添加一个module
,命名为processor
,选择module时候需要选择Java。
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes("com.example.processor.MyAnnotation")
@AutoService(MyProcessor.class)
public class MyProcessor extends AbstractProcessor
@Override
public synchronized void init(ProcessingEnvironment processingEnv)
//processingEnv提供工具类
super.init(processingEnv);
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)
//注解解析,以及生成Java文件
return false;
@SupportedSourceVersion
是支持的Java版本
@SupportedAnnotationTypes
是支持的注解
@AutoService
是自动生成相应的配置
使用@AutoService Gradle
引入:implementation 'com.google.auto.service:auto-service:1.0-rc7'
APT始于jdk5,止于jdk8,自jdk6,可以使用Pluggable Annotation Processing API
替换它。
1.3APT实现原理
APT是什么?
APT可以在android编译期动态生成预设好格式的java文件
APT相比注解的优势
APT产生的原因是注解的低效率,APT主要解决的是注解+反射执行慢的问题,APT通过预设代码动态生成java代码文件,直接调用java对象,从而替换掉遍历查找注解的耗时操作,在Android这种执行效率不高的移动端,性能提升十分明显
APT可以做到什么?
减少模板代码:可以利用APT在编译期动态生成java代码,省去很多模板代码,节省很多编码量
解耦合,让代码更灵活:可以利用辅助类+反射的形式,做到类与类之间的解耦合,从而减少类之间的直接依赖
哪些框架在使用APT
Butterknife、Dagger2、DataBinding、EventBus3、ARouter
使用APT的步骤
-
创建自定义注解
-
创建注解处理器 继承自
AbstractProcessor
,生成处理注解逻辑的.java文件,封装一个供外部调用的API接口,也就是调用第二步中生成的注解处理逻辑文件中的方法,实现逻辑处理 -
然后在需要使用的Module中依赖注解,使用自定义注解处理器,然后调用API接口即可
下面我们以手写实现一个简易的Butterknife
为例,来实际使用一下APT, 我们这里主要以通过注解的方式实现findViewById()的操作;
创建自定义注解
我们首先创建一个Java Library Module
,名称为AnnotationModule
,在该Module下定义一个注解@MQBindView
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface MQBindView
int value();
自定义注解处理器
创建一个Java Library Module
,名称为APTModule
,定义一个类继承自AbstractProcessor;
这里自定义处理器里面会用到我们自定义的注解,所以这个Module的build.gradle
里面要依赖AnnotationModule
dependencies
implementation project(path: ':AnnotationModule')
public class MQProcessor extends AbstractProcessor
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)
//重写getSupportedAnnotationTypes方法,添加支持处理的注解类型
@Override
public Set<String> getSupportedAnnotationTypes()
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(MQBindView.class.getCanonicalName());
return supportTypes;
@Override
public SourceVersion getSupportedSourceVersion()
return SourceVersion.latestSupported();
自定义注解处理器一定要重写getSupportedAnnotationTypes
方法,添加需要处理的注解类型
在编写注解处理逻辑之前,这里我先对Processor
中的一些基本概念进行一下了解;
我们的app Module
中如果依赖了这个APTModule
,当我们如果build(编译)项目的时候,这个Processor
的process
方法就会被执行;
当我们想要在这个Processor
中添加一些打印信息,我们需要用Messager(javax.annotation.processing.Messager)
去打印,打印出来的内容,在build的时候,会在下方的build视图中打印出来,这个build视图,正常会显示的是编译的过程信息,我们使用Messager
打印的信息,也会在这里打印出来;
public class MQProcessor extends AbstractProcessor
Messager mMessager;
@Override
public synchronized void init(ProcessingEnvironment processingEnv)
super.init(processingEnv);
mMessager = processingEnv.getMessager();
mMessager.printMessage(Diagnostic.Kind.NOTE, "----------->init");
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)
mMessager.printMessage(Diagnostic.Kind.NOTE, "----------->process");
Element
Element
代表程序的一个元素,这个元素可以是:包、类/接口、属性变量、方法/方法形参、泛型参数,Element
是java-apt
( 编译时注解处理器)技术的基础,在编译期间可以获取元素的各类信息,结合APT技术动态生成代码实现相应的逻辑处理;
Element
接口中的一些方法:
getEnclosingElement()
获取包含该Element
的父Element
public class MainActivity extends AppCompatActivity
@MQBindView(R.id.tv_activity_main)
TextView mTextView;
这里,当我们拿到使用了@MQBindView
这个注解的Element(mTextView)
,然后通过getEnclosingElement()
方法就能获取到父Element,这里父Element就是MainActivity
process
方法中,有一个参数RoundEnvironment
,通过这个参数可以获取包含特定注解的被注解元素.
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(MQBindView.class);
init方法中有一个ProcessingEnvironment
参数,通过这个参数可以获取元素所在的包
String packageName = mProcessingEnv.getElementUtils().getPackageOf(element).toString();
Filer
Filer是文件生成器,可以用来生成Java源文件等
JavaFileObject sourceFile = mFiler.createSourceFile(文件名);
这里文件名,要是创建的类的全类名
JavaFileObject sourceFile = mFiler.createSourceFile(
packageName + "." + activityName + "_ViewBinding"
);
这样创建文件之后,编译项目之后,会在app/build/generated/ap_generated_sources/debug/out/应用包名/下生成这个Java文件
Writer
我们创建Java源文件对象 JavaFileObject
之后可以通过openWriter()
方法获取文件的Writer对象,然后就可以通过Writer对象往文件里面写内容
而我们要实现Butterknife
一样的通过注解的形式,帮我们实现findViewById()
的功能,我们查看Butterknifer
就知道实际上Butterknife
就是使用了APT技术,在编译时给每一个使用了@BindView
注解的类都生成了一个对应的Java源文件,名字为使用了注解的类的类名加上 _ViewBinding
,比如我们的MainActivity
使用了注解@BindView
,则编译之后会生成MainActivity_ViewBinding
这个Java源文件,生成的Java源文件就在app/build/generated/ap_generated_sources/debug/out/应用包名/
这个目录下,也即新建了一个MainActivity_ViewBinding
类,这个类的构造方法里面就会对MainActivtiy
里面的所有使用了@BindView
的View成员进行findViewById
的操作,如下图所示
ButterKnife
通过APT生成的Java源文件
Buterknife.bind(Activity)
这个方法的实现如下
查看Butterknife
的源码可知,Butterknife.bind(Activity)
的实现,就是利用反射通过Class获取生成的Java源文件中定义的类的Class对象,然后获取Class的构造方法,通过反射,创建生成的Java类的对象,从而触发构造方法,触发findViewById
相关代码;
了解了Butterknife
的实现原理,那我们自己就可以照着这个逻辑去实现;
我们在自己自定义的注解处理器的process
方法中,实现给每一个使用了@MQBindView
的注解的类都生成一个对应的Java源文件,Java 源文件中的内容,为定义一个对应的类,类中的构造方法需要一个Activtiy参数,构造方法中的内容为给类中每一个使用了@BindView
注解的成员进行findViewById
的操作;
public class MQProcessor extends AbstractProcessor
private static final String TAG = "MQProcessor";
Filer mFiler;
ProcessingEnvironment mProcessingEnv;
Messager mMessager;
@Override
public synchronized void init(ProcessingEnvironment processingEnv)
super.init(processingEnv);
mFiler = processingEnv.getFiler();
mProcessingEnv = processingEnv;
mMessager = processingEnv.getMessager();
mMessager.printMessage(Diagnostic.Kind.NOTE, "----------->init");
@Override
public Set<String> getSupportedAnnotationTypes()
HashSet<String> supportTypes = new LinkedHashSet<>();
supportTypes.add(MQBindView.class.getCanonicalName());
return supportTypes;
@Override
public SourceVersion getSupportedSourceVersion()
return SourceVersion.latestSupported();
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)
mMessager.printMessage(Diagnostic.Kind.NOTE, "----------->process");
//我们的源代码中所有使用了@MQBindView注解的元素
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(MQBindView.class);
//定义一个Map集合,以每一个Activity类名为键,这个Activtiy类中使用了@MQBindViwe注解的元素列表为值
Map<String, List<VariableElement>> map = new HashMap<>();
for (Element element : elementsAnnotatedWith)
VariableElement variableElement = (VariableElement) element;
//Activity的名字
String activityName = variableElement.getEnclosingElement().getSimpleName().toString();
List<VariableElement> variableElements;
if (map.get(activityName) != null)
variableElements = map.get(activityName);
variableElements.add(variableElement);
else
variableElements = new ArrayList<>();
variableElements.add(variableElement);
map.put(activityName, variableElements);
Writer writer = null;
//给每一个使用了@MQVindView注解的类(Activity/Fragment等)都生成对应的Java源文件
for (Map.Entry<String, List<VariableElement>> stringListEntry : map.entrySet())
List<VariableElement> variableElementList = stringListEntry.getValue();
TypeElement enclosingTypeElement = (TypeElement) variableElementList.get(0).getEnclosingElement();
String packageName =
mProcessingEnv.getElementUtils().getPackageOf(enclosingTypeElement).toString();
String activityName = variableElementList.get(0).getEnclosingElement().getSimpleName().toString();
try
mMessager.printMessage(Diagnostic.Kind.NOTE, "----------->process:" + mFiler);
JavaFileObject sourceFile = mFiler.createSourceFile(
packageName + "." + activityName + "_ViewBinding"
);
/**
* Java源文件里的内容为定义一个类,类名就为使用了@BindView的注解的Activity的类名加_ViewBinding
* 然后构造方法有一个Activity类型的target参数,构造方法里面给这个Activity中使用了@BindView注解 的View成员进行dinfViewByid操作
*
*/
writer = sourceFile.openWriter();
writer.write("package " + packageName + ";\\n");
writer.write("import android.view.View;\\n");
writer.write("public class " + activityName + "_ViewBinding \\n");
writer.write("public " + activityName + "_ViewBinding (" + activityName + " target)\\n");
writer.write("View decorView = target.getWindow().getDecorView();\\n");
for (VariableElement variableElement : variableElementList)
String variableName = variableElement.getSimpleName().toString();
int value = variableElement.getAnnotation(MQBindView.class).value();
writer.write("target." + variableName + "=decorView.findViewById(" + value + ");\\n");
writer.write("\\n");
writer.write("\\n");
try
mMessager.printMessage(Diagnostic.Kind.NOTE, "----------->process:" + writer);
//最终Writer这个写对象,要关闭,否则内容是不会写进创建的Java源文件中的
writer.close();
catch (IOException exception)
exception.printStackTrace();
catch (Exception exception)
exception.printStackTrace();
mMessager.printMessage(Diagnostic.Kind.NOTE, "----------->process:end");
return false;
配置注解处理器
自定义处理器代码编写好之后,还有一个非常重要的步骤,就是配置注解处理器
一共有两种方式:
第一种:
在自定义注解处理器所在的Module,APTModule
的src/main目录下新建一个 resources/META-INF/services
三级目录,然后在services目录下新建一个名字为javax.annotation.processing.Processor
的文件
文件里面的内容,写上自定义处理器的全类名即可.
第二种:
通过依赖google的auto-services
库,帮我们自动生成META-INF/services/javax.annotation.processing.Processor
文件;
在自定义处理器所在的Module,APTModule
的build.gradle
添加auto-services
依赖
dependencies
implementation 'com.google.auto.service:auto-service:1.0-rc6'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
implementation project(path: ':AnnotationModule')
然后在我们编写的MQProcessor
上方使用@AutoService
注解
然后编译代码,我们可以看到在APTModule的build/classess/java/main/
下会生成 META-INF/services/javax.annotation.processing.Processor
文件,文件里面的内容就是com.example.aptmodule.MQProcessor
所以也就成功配置了依赖处理器,然后我们build一下项目,查看app/build/generated/ap_generated_sources/debug/out/应用包名/
下成功生成了对应的Java源文件,表示注解处理器生效了.
定义一个使用入口,给使用者调用
我们这里定义了一个MQButterKnife
类,提供了一个静态方法bind(Activity), bind
方法里面就是通过反射创建Activity对应生成的 Activity_ViewBinding
对象触发findViewById
操作
public class MQButterKnife
private static final String TAG = "MQButterKnife";
public static void bind(Activity activity)
String viewBindingClassName =
activity.getClass().getCanonicalName() + "_ViewBinding";
Log.e(TAG, "bind:" + viewBindingClassName);
try
Class<?> aClass = Class.forName(viewBindingClassName);
Log.e(TAG, "bind: " + aClass);
Constructor<?> constructor = aClass.getConstructor(activity.getClass());
Object o = constructor.newInstance(activity);
Log.e(TAG, "bind: " + o);
catch (Exception e)
e.printStackTrace();
使用
在app主Module里面先依赖AnnotationModule
,以及使用APTModule
注解模块,在主Module的build.gradle
里面添加如下,注意自定义处理器模板的依赖方式是通过annotationProcessor
dependencies
implementation project(path: ':AnnotationModule')
annotationProcessor project(':APTModule')
然后在我们的Activty中去使用我们自定义的MQButterknife
,实现View的findViewById
操作
public class MainActivity extends AppCompatActivity
@MQBindView(R.id.tv_activity_main)
TextView mTextView;
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MQButterKnife.bind(this);
mTextView.setText("测试");
运行项目,mTextView
成功显示 “测试”,说明我们的简单版本Bunnerknife成功实现了.
这样我们就成功的在Android项目中利用APT编译时注解处理技术实现了一个简单版本的Butterknife
,也对APT技术的使用流程有了一定的了解;
以上是关于关于Android注解这些基础,这些都不知道?历时半个月呕心之作的主要内容,如果未能解决你的问题,请参考以下文章
赶紧收藏!这些Java中的流程控制知识你都不知道,你凭什么涨薪?