Android开发必备——注解

Posted 嘴巴吃糖了

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android开发必备——注解相关的知识,希望对你有一定的参考价值。

前言

阅读官方源码以及各类第三方框架时可以发现,很多地方都有注解,作为一名android程序员,掌握注解属于必不可少的一项技能。

1. 什么是注解

注解是以@符号开头的用来标识如类、字段、方法等的工具。说到注解,就不得不提另外一个概念——注释,两者其实都是做解释的功能,只不过注释是面向开发者,而注解则是针对程序。注解一般需要结合注解处理器或者反射等实现对应的功能,否者将没有实际的意义。两者的区别如下:

  1. 定义不同:

注解:英名为Annotation,它是JDK1.5及以后版本引入的一个特性。 与类、接口、枚举是在同一个层次,可以成为java 的一个类型。用一个词描述注解------元数据,它是一种描述数据的数据。所以,可以说注解就是源代码的元数据。 注释:是对源代码作介绍、评议或说明的文字。 2. 作用不同: 注解是Java 编译器可以理解的部分,是给编译器看的。通过标记包、类、字段、方法、局部变量、方法参数等元素据,告诉jvm这些元素据的信息。 注释是程序员对源代码做一些记忆或提示性描述,是给人来看的。它能告诉开发者这段代码的逻辑、说明、特点等内容,对代码起到解释、说明的作用。

2. 元注解

元注解是由Java提供的一套用来注解其他注解的基础注解,听起来可能有点绕,其实就是Java编译器在对注解做处理的时候需要知道该注解的时效性、作用范围等一些信息,于是提供了一套用来对注解做限定的工具——元注解。 JDK1.5加入的元注解有如下:

  • @Target:指定注解的作用范围
  • @Retention:指定注解的作用时机
  • @Inherited:被该注解修饰的注解,作用在某个类上,该类的此注解可以被子类继承
  • @Documented:不常用,给Javadoc配置的,这里略过

2.1 @Target

此元注解用来指定注解的作用范围,参数如下:

  • ElementType.TYPE:类、接口(包括注解类型)或枚举声明
  • ElementType.FIELD:字段声明(包括枚举常量)
  • ElementType.METHOD:方法声明
  • ElementType.PARAMETER:形参声明
  • ElementType.CONSTRUCTOR:构造函数声明
  • ElementType.LOCAL_VARIABLE:局部变量声明
  • ElementType.ANNOTATION_TYPE:注解类型声明
  • ElementType.PACKAGE:包声明
  • ElementType.TYPE_PARAMETER:类型参数声明——JDK1.8加入
  • ElementType.TYPE_USE:类型的使用——JDK1.8加入
  • ElementType.MODULE:模块声明——JDK1.9加入

2.2 @Retention

此元注解用来指定注解的作用时机,也就是说注解是在什么阶段有效,参数如下:

  • RetentionPolicy.SOURCE:指定注解只在源码阶段有用,编译器编译之后将会丢弃,这类型的注解一般用来做代码限制或者提示等。如Override用来提示方法重写了父类的方法,如果在没有重写父类方法的方法上面使用此注解,编译器会报错。
  • RetentionPolicy.CLASS:指定注解将由编译器记录在类文件中,但在运行时虚拟机不会保留,如果不指定@Retention,此为@Retention默认的参数。Android中经常用到一些方法参数的限制中,如LayoutResNonNullIntRange等。
  • RetentionPolicy.RUNTIME:指定注解会被编译器记录在类文件中,并在运行时虚拟机会保留,因此它们可以在程序运行时读取。

2.3 @Inherited

此元注解主要用来指定注解是否可以被继承,下面我们通过一个例子进行解释。

// 被@Inherited注解了的注解
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ExAnn 


// 没有@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface NoExAnn 
 

上面定义了两个注解,两者都是属于运行时可以获取,唯一的区别是@ExAnn使用了@Inherited

// 父类使用了上面两个注解
@ExAnn
@NoExAnn
public class Parent 


// 子类继承了父类,但是没有任何实现
public class Child extends Parent 
 

然后我们通过子类获取注解信息,测试代码如下:

private void test() 
    Annotation[] annotations = Child.class.getAnnotations();
    System.out.println("-----start-----"); 
    for (Annotation annotation : annotations) 
        System.out.println(annotation.toString());
    
    System.out.println("------end------");
 

运行以上代码,最终会打印

System.out: -----start----- System.out: @com.payne.annotation.demo.ExAnn() System.out: ------end------

通过以上打印可以发现:父类里面的注解如果有被@Inherited注解,其子类才可以获取到父类的注解。

3. 自定义注解

3.1 运行时注解

以前在Android开发过程中,我们经常会使用到findViewById去获取View,大量的视图控件意味着我们会去重复多次使用findViewById,以至于后面出现了第三方框架ButterKnife,下面以findViewById做示例。

  1. 首先创建一个注解类,创建的注解类和接口有点像,只不过在interface前面多了一个@符号,如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD, ElementType.METHOD)
public @interface BindView 
    int value();
 
  1. 其次在测试类中使用,代码如下:
public class MainActivity extends AppCompatActivity 

    @BindView(R.id.tv_show)
    TextView tvShow;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        processView(this);
        tvShow.setText("赋值成功");
    

    /**
     * 反射处理findViewById
     */
    public static void processView(Activity activity) 
        // 获取Activity中所有的字段
        Field[] fields = activity.getClass().getDeclaredFields();
        if (fields == null) 
            return;
        
        // 遍历字段数组,找到带有BindView注解的字段
        for (Field field : fields) 
            BindView bindView = field.getAnnotation(BindView.class);
            if (bindView == null) 
                continue;
            
            // 获取注解值——即ViewID
            int value = bindView.value();
            // 通过ViewID找到View
            View viewById = activity.findViewById(value);
            try 
                // 给字段View赋值
                field.set(activity, viewById);
             catch (IllegalAccessException e) 
                e.printStackTrace();
            
        
    
 

下面是运行效果:

可以看到并没有直接给tvShow设置findViewById,但是运行之后TextView依然被成功设置为"赋值成功"。这是因为运行时在processView方法中通过反射获取并设值。

3.2 编译时注解

反射使用多了比较影响性能,翻看ButterKnifeRetrofit等第三方框架源码我们也能发现其并不是通过运行时反射赋值,而是通过编译工具在编译期间就对注解进行了处理,而处理注解需要使用到注解处理器,那什么是注解处理器呢? 注解处理器:英文为Annotation Processor,顾名思义,是用来处理注解的工具,其基本原理是将自定义注解处理器注册到编译器,编译器在编译阶段会去执行注册了的自定义注解处理器,完成对应的代码注入。 实现自定义注解处理器主要分为三步:

  1. 编写注解。
  2. 编写继承自javax.annotation.processing包下的AbstractProcessor类的自定义注解处理器。
  3. 将自定义注解处理器注册到编译器。

下面是自定义注解处理器的实现示例:

如上图,示例主要包括两个Java module和一个Android module。

  1. test-annotation:Java module,主要用来存放自定义注解。
  2. test-compiler:Java module,主要存放继承自AbstractProcessor类的自定义注解处理器类。
  3. test-butterknife:Android module,主要存放工具类,用来反射获取编译器根据注解处理器生成的类。

3.2.1 定义注解

test-annotation模块下定义一个需要处理的注解类BindView

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView 
    int value();
 

3.2.2 实现注解处理器

public class AnnotationCompiler extends AbstractProcessor 

    /**
     * 设置支持的源版本,默认为RELEASE_6
     * 两种方式设置版本:
     * 1. 此处返回指定版本
     * 2. 类上面设置SupportedSourceVersion注解,并传入版本号
     */
    @Override
    public SourceVersion getSupportedSourceVersion() 
        return SourceVersion.latestSupported();
    

    /**
     * 设置支持的注解类型,默认为空集合(都不支持)
     * 两种方式设置注解集合:
     * 1. 此处返回支持的注解集合
     * 2. 类上面设置SupportedAnnotationTypes注解,并传入需要支持的注解
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() 
        Set<String> types = new HashSet<>();
        types.add(BindView.class.getCanonicalName());
        return types;
    

    /**
     * 初始化操作
     *
     * @param processingEnv 环境
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) 
        super.init(processingEnv);
    

    /**
     * 处理注解
     *
     * @param set              待处理的注解集合
     * @param roundEnvironment RoundEnvironment
     * @return 返回true表示后续处理器不再处理
     */
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) 
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);
        //TypeElement->类    ExecutableElement->方法   VariableElement->属性
        Map<String, List<VariableElement>> map = new HashMap<>(16);
        for (Element element : elements) 
            //属性元素
            VariableElement variableElement = (VariableElement) element;
            //获取类名
            String activityName = variableElement.getEnclosingElement().getSimpleName().toString();
            //根据类名将属性元素保存在集合中
            List<VariableElement> variableElements = map.get(activityName);
            if (variableElements == null) 
                variableElements = new ArrayList<>();
                map.put(activityName, variableElements);
            
            variableElements.add(variableElement);
        

        if (map.size() > 0) 
            for (String activityName : map.keySet()) 
                //根据类名获取属性元素集合
                List<VariableElement> variableElements = map.get(activityName);
                //获取类元素
                TypeElement enclosingElement = (TypeElement) variableElements.get(0).getEnclosingElement();
                //获取类的包名
                String packageName = processingEnv.getElementUtils().getPackageOf(enclosingElement).toString();
                //生成对应的类
                generateClass(variableElements, packageName, activityName);
            
        
        return false;
    

    /**
     * 根据注解信息生成对应的类,本方法中手动生成类文件内容
     * 我们还可以使用第三方工具JavaPoet优雅的生成,具体参考地址:https://github.com/square/javapoet
     *
     * @param variableElements 设置了对应注解的属性元素的集合
     * @param packageName      包名
     * @param activityName     类名
     */
    private void generateClass(List<VariableElement> variableElements, String packageName, String activityName) 
        Writer writer = null;
        try 
            JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(packageName + "." + activityName + "_ViewBinding");
            writer = sourceFile.openWriter();
            //包名
            writer.write("package " + packageName + ";\\n");
            //导入包
            writer.write("import com.payne.buf.IBinder;\\n");
            //类名以及实现的接口名
            writer.write("public class " + activityName + "_ViewBinding implements IBinder<"
                    + packageName + "." + activityName + "> \\n");
            //实现接口中的方法
            writer.write("  @Override\\n");
            writer.write("  public void bind(" + packageName + "." + activityName + " target) \\n");
            //遍历属性元素集合,根据信息生成findViewById操作
            for (VariableElement variableElement : variableElements) 
                String variableName = variableElement.getSimpleName().toString();
                int id = variableElement.getAnnotation(BindView.class).value();
                TypeMirror typeMirror = variableElement.asType();
                writer.write("      target." + variableName + " = (" + typeMirror + ") target.findViewById(" + id + ");\\n");
            
            writer.write("  \\n");
            writer.write("\\n");
         catch (IOException e) 
            e.printStackTrace();
         finally 
            if (writer != null) 
                try 
                    writer.close();
                 catch (IOException e) 
                    e.printStackTrace();
                
            
        
    
 

3.2.3 注册注解处理器

有两种方法将自定义注解处理器注册到编辑器。

  1. 手动注册:在test-compiler模块下的src/main/目录下创建META-INF/services/子目录,并创建文件名为javax.annotation.processing.Processor的文件,文件名是Processor类的全路径名,如下图:

文件中加入自定义注解处理器的全路径名com.payne.annotation.test_compiler.AnnotationCompiler,如下图:

  1. 自动注册:依赖于Google的工具框架。

首先在test-compiler模块下的build.gradle中加入依赖框架,然后在自定义注解处理器类上面添加@AutoService(Processor.class)注解即可。

implementation 'com.google.auto.service:auto-service:1.0'
annotationProcessor 'com.google.auto.service:auto-service:1.0' 

编译之后可以发现test-compiler模块下的build文件夹中自动生成了和我们手动注册一样的文件。

最后在test-butterknife模块下提供IBinder接口以及findViewById的绑定类供App调用。

public interface IBinder<T> 
    void bind(T target);
 

public class PayneButterKnife 
    @SuppressWarnings("unchecked")
    public static void bind(Activity activity) 
        String name = activity.getClass().getName() + "_ViewBinding";
        try 
            Class<?> aClass = Class.forName(name);
            IBinder<Activity> iBinder = (IBinder<Activity>) aClass.newInstance();
            iBinder.bind(activity);
         catch (Exception e) 
            e.printStackTrace();
        
    
 

4. 小结

  1. 注释面向开发者,注解面向程序。
  2. 可以通过Target元注解设置注解的作用范围,Retention元注解设置注解的作用时机。
  3. 源码时注解主要作用源码阶段,编译则被舍弃,主要面向编辑器等开发工具,用来在开发阶段提示开发者或者限制开发者使用范围。
  4. 编译时注解主要作用于编译阶段,编译之后则被舍弃,主要面向编译器,可以根据注解信息生成对应的类。
  5. 运行时注解主要作用于运行阶段,注解信息运行时依然存在,可以通过反射获取使用。

文末

要想成为架构师,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。

相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

一、架构师筑基必备技能

1、深入理解Java泛型
2、注解深入浅出
3、并发编程
4、数据传输与序列化
5、Java虚拟机原理
6、高效IO
……

二、Android百大框架源码解析

1.Retrofit 2.0源码解析
2.Okhttp3源码解析
3.ButterKnife源码解析
4.MPAndroidChart 源码解析
5.Glide源码解析
6.Leakcanary 源码解析
7.Universal-lmage-Loader源码解析
8.EventBus 3.0源码解析
9.zxing源码分析
10.Picasso源码解析
11.LottieAndroid使用详解及源码解析
12.Fresco 源码分析——图片加载流程

三、Android性能优化实战解析

  • 腾讯Bugly:对字符串匹配算法的一点理解
  • 爱奇艺:安卓APP崩溃捕获方案——xCrash
  • 字节跳动:深入理解Gradle框架之一:Plugin, Extension, buildSrc
  • 百度APP技术:Android H5首屏优化实践
  • 支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」
  • 携程:从智行 Android 项目看组件化架构实践
  • 网易新闻构建优化:如何让你的构建速度“势如闪电”?

四、高级kotlin强化实战

1、Kotlin入门教程
2、Kotlin 实战避坑指南
3、项目实战《Kotlin Jetpack 实战》

  • 从一个膜拜大神的 Demo 开始

  • Kotlin 写 Gradle 脚本是一种什么体验?

  • Kotlin 编程的三重境界

  • Kotlin 高阶函数

  • Kotlin 泛型

  • Kotlin 扩展

  • Kotlin 委托

  • 协程“不为人知”的调试技巧

  • 图解协程:suspend

五、Android高级UI开源框架进阶解密

1.SmartRefreshLayout的使用
2.Android之PullToRefresh控件源码解析
3.Android-PullToRefresh下拉刷新库基本用法
4.LoadSir-高效易用的加载反馈页管理框架
5.Android通用LoadingView加载框架详解
6.MPAndroidChart实现LineChart(折线图)
7.hellocharts-android使用指南
8.SmartTable使用指南
9.开源项目android-uitableview介绍
10.ExcelPanel 使用指南
11.Android开源项目SlidingMenu深切解析
12.MaterialDrawer使用指南

六、NDK模块开发

1、NDK 模块开发
2、JNI 模块
3、Native 开发工具
4、Linux 编程
5、底层图片处理
6、音视频开发
7、机器学习

七、Flutter技术进阶

1、Flutter跨平台开发概述
2、Windows中Flutter开发环境搭建
3、编写你的第一个Flutter APP
4、Flutter开发环境搭建和调试
5、Dart语法篇之基础语法(一)
6、Dart语法篇之集合的使用与源码解析(二)
7、Dart语法篇之集合操作符函数与源码分析(三)

八、微信小程序开发

1、小程序概述及入门
2、小程序UI开发
3、API操作
4、购物商场项目实战……

全套视频资料:

一、面试合集

二、源码解析合集


三、开源框架合集


欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取【保证100%免费】↓↓↓

开发者涨薪指南 48位大咖的思考法则、工作方式、逻辑体系

以上是关于Android开发必备——注解的主要内容,如果未能解决你的问题,请参考以下文章

mybatis的缓存和注解开发

Java 中 Lombok 的使用,提高开发速度必备

Java 中 Lombok 的使用,提高开发速度必备

英名不朽--杨致远费罗和yahoo公司

2021年是做安卓开发人员的绝佳时机,全套教学资料

注解的那些事儿| 如何自定义注解