Android注解基础用法

2023-05-16

image-20220620155008563

注解的介绍

注解介绍

注解是在 Java SE5 引入进来的。

注解又称为标注,用于为代码提供元数据。 作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。可以作用在类、方法、变量、参数和包等上。 你可以通俗的理解成“标签”,这个标签可以标记类、方法、变量、参数和包。

注解作用

注解单独存在时是没有意义的,需要与注解处理器一起,才能起作用

  1. 注解+APT,用于生成一些Java 文件
  2. 注解+代码埋点,用户做日志手机统计等
  3. 注解+反射,用于为View 组件 增加事件监听等

Java 元注解

名字描述
@Retention标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问
@Documented标记这些注解是否包含在用户文档中,即包含到 Javadoc 中去
@Target标记这个注解的作用目标
@Inherited标记这个注解是继承于哪个注解类
@RepeatableJava 8 开始支持,标识某注解可以在同一个声明上使用多次

@Retention
表示注解保留时间长短。可选的参数值在枚举类型 java.lang.annotation.RetentionPolicy 中,取值为:

  • RetentionPolicy.SOURCE:注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视,不会写入 class 文件;
  • RetentionPolicy.CLASS:注解只被保留到编译进行的时候,会写入 class 文件,它并不会被加载到 JVM 中;
  • RetentionPolicy.RUNTIME:注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以反射获取到它们。

@Target
用于指明被修饰的注解最终可以作用的目标是谁,也就是指明,你的注解到底是用来修饰方法的?修饰类的?还是用来修饰字段属性的。 可能的值在枚举类 java.lang.annotation.ElementType 中,包括:

  • ElementType.TYPE:允许被修饰的注解作用在类、接口和枚举上;

  • ElementType.FIELD:允许作用在属性字段上;

  • ElementType.METHOD:允许作用在方法上;

  • ElementType.PARAMETER:允许作用在方法参数上;

  • ElementType.CONSTRUCTOR:允许作用在构造器上;

  • ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上;

  • ElementType.ANNOTATION_TYPE:允许作用在注解上;

  • ElementType.PACKAGE:允许作用在包上。

@Target 注解的参数也可以接收一个数组,表示可以作用在多种目标类型上,如: @Target({ElementType.FIELD, ElementType.LOCAL_VARIABLE})

注解+APT 实战:简单实现ButterKnife框架

APT是注解处理器,是一种用来处理注解的工具。JVM会在编译期就运行APT去扫描处理代码中的注解。我们在拿到对应的注解时,可以生成我们需要的模板代码

image-20220620143459616

在获取View空间的时候,我们往往需要通过findViewById来拿到控件的示例,会比较繁琐,我们可以通过APT的核心原理,来生成findViewById的模板代码,这样就不需要在每个Activity中都通过这个方式去执行。

核心原理:

  1. 声明控件变量,并添加BindView注解,绑定控件对应的ID
  2. 在apt注解处理器,处理BindView注解,生成对应的模板代码Java文件(用到Writer去生成文件)
  3. 调用模板代码,实现findViewById 功能

实现步骤

1. 创建annotation库

创建一个java-library库,命名为:annotation。这个库用来存放自定义注解。

image-20220620144816082

在此库中创建一个注解BindView

image-20220620144922691

BindView代码如下:

@Retention(RetentionPolicy.CLASS)//编译时起效
@Target(ElementType.FIELD)//针对的是属性
public @interface BindView {
    int value(); //定义输入参数为整形,例如:@BindView(R.id.xxx)
}

2. 创建annotation_compiler库

此库是APT的核心处理库,用来在编译时生成处模板代码

注意: 这也是一个java-library库,继承的 AbstractProcessor 在 javax包下才能引入

在此之前,我们需要在App 模块中定义一个IBinder接口,让即将生成的模板类继承此接口

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

这个接口的作用是,当我们通过反射的方式生成模板类实例,直接调用bind方法来实现View绑定

String name = activity.getClass().getName() + "_ViewBinding";//这个是模板类的类名
try {
    Class<?> aClass = Class.forName(name);
    IBinder iBinder = (IBinder) aClass.newInstance();//生成模板类实列
    iBinder.bind(activity);//实现findViewbyId功能
} catch (Exception e) {
    e.printStackTrace();
}

注意:记住这个IBinder所在包目录,下面生成模板时用到

创建完成后,在build.gradle中,引入如下:

plugins {
    id 'java-library'
}

java {
    sourceCompatibility = JavaVersion.VERSION_1_7
    targetCompatibility = JavaVersion.VERSION_1_7
}

dependencies {

    compileOnly 'com.google.auto.service:auto-service:1.0-rc7'
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7' //Google 注解处理器

    implementation project(path: ':annotation') //引用刚刚创建的注解
}

定义处理器代码 BindViewProcessor.java ,这个处理器的目的是生成如下模板代码

package com.example.annotation;
import com.example.annotation.IBinder;
public class MainActivity_ViewBinding implements IBinder<com.example.annotation.MainActivity> {
    @Override
    public void bind(com.example.annotation.MainActivity target) {
        target.mTvText = (android.widget.TextView) target.findViewById(2131231186);
    }
}

以上的 IBinder就是我们在App中创建的,在com.example.annotation包下面,我们需要一点点将以上目标代码拼装在一起

所有代码处理,都在 process方法中实现

@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {

    private static final String TAG = BindViewProcessor.class.getSimpleName();

    //1.支持的版本
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    //2.能用来处理哪些注解
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new HashSet<>();
        types.add(BindView.class.getCanonicalName());
        return types;
    }

    //3.定义一个用来生成APT目录下面的文件的对象
    private Filer filer;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);

        //filter 用于后续写文件
        filer = processingEnv.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //打印测试
        processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "===>" + set);
        //获取APP中所有用到了BindView注解的对象
        Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindView.class);

        // TypeElement -->类
        // ExecutableElement --> 方法
        // VariableElement--> 属性
        //开始对elementsAnnotatedWith进行分类:我们可能很多activity中都定义有 BindView注解,用activity作为key,List作为注解列表
        Map<String, List<VariableElement>> map = new HashMap<>();
        for (Element element : elementsAnnotatedWith) {
            VariableElement variableElement = (VariableElement) element;
            //例如MainActivity
            String activityName = variableElement.getEnclosingElement().getSimpleName().toString();
            Class aClass = variableElement.getEnclosingElement().getClass();
            List<VariableElement> variableElements = map.get(activityName);
            if (variableElements == null) {
                variableElements = new ArrayList<>();
                map.put(activityName, variableElements); //Activity跟注解列表是一对多的关系
            }
            variableElements.add(variableElement);
        }

        //开始遍历map, 生成每个activity对应的模板代码
        if (map.size() > 0) {
            Writer writer = null;
            Iterator<String> iterator = map.keySet().iterator();
            while (iterator.hasNext()) {
                String activityName = iterator.next();
                //对应Activity下面的注解列表
                List<VariableElement> variableElements = map.get(activityName);
                //获取Activity所在包名
                TypeElement enclosingElement = (TypeElement) variableElements.get(0).getEnclosingElement();
                String packageName = processingEnv.getElementUtils().getPackageOf(enclosingElement).toString();
                try {
                    //文件名:MainActivity_ViewBinding
                    JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + activityName + "_ViewBinding");
                    writer = sourceFile.openWriter();
                    //        package com.example.annotation;
                    writer.write("package " + packageName + ";\n");
                    //        import com.example.annotation.IBinder;
                    writer.write("import " + packageName + ".IBinder;\n");
                    //        public class MainActivity_ViewBinding implements IBinder<com.example.annotation.MainActivity> {
                    writer.write("public class " + activityName + "_ViewBinding implements IBinder<" +
                            packageName + "." + activityName + ">{\n");
                    //            @Overrid
                    //            public void bind(com.example.annotation.MainActivity target) {
                    writer.write(" @Override\n" +
                            " public void bind(" + packageName + "." + activityName + " target){");

                    // target.mTvText = (android.widget.TextView) target.findViewById(2131231186);
                    for (VariableElement variableElement : variableElements) { //这里可能有多个注解的View控件,因此需要循环遍历
                        //得到名字
                        String variableName = variableElement.getSimpleName().toString();
                        //得到ID
                        int id = variableElement.getAnnotation(BindView.class).value();
                        //得到类型
                        TypeMirror typeMirror = variableElement.asType();
                        writer.write("target." + variableName + "=(" + typeMirror + ")target.findViewById(" + id + ");\n");
                    }

                    writer.write("\n}}");

                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (writer != null) {
                        try {
                            writer.close();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }

        return false;
    }
}

创建完成处理器后,执行build方法,会生成模板代码:

image-20220620153708121

3. 创建辅助类,完成一键绑定

转到App模块,App的build.gradle中做如下依赖:

implementation project(path: ':annotation') //注解
annotationProcessor  project(path: ':annotation_compiler') //注解处理器

创建MyButterknife辅助类,其中IBinder是我们第2步骤中定义的接口,所有模板类都会继承此接口

public class MyButterknife {
    public static void bind(Activity activity) {
        String name = activity.getClass().getName() + "_ViewBinding";
        try {
            Class<?> aClass = Class.forName(name);
            IBinder iBinder = (IBinder) aClass.newInstance(); //通过反射,生成模板类的实例
            iBinder.bind(activity); //调用这个方法,会去执行对应模板类的bind方法,实现findViewById的功能
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在MainActivity中实现代码如下:

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.tvText)
    TextView mTvText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        MyButterknife.bind(this); //一键实现findViewById功能,
        mTvText.setText("Test"); 
    }
}

以上代码已上传 GitHub

参考

Android 注解知多少

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android注解基础用法 的相关文章

  • Android ListView 子项

    我最近为 Android 应用程序创建了一个新的 ListView 对象 但遇到了一些错误 当我尝试使用简单适配器创建一个包含列表中子项目的项目时 创建的最新项目与其他项目重叠 我正在使用地图列表来创建项目 例如 如果我向地图列表中添加一个
  • 如何在 TableRow 的一个单元格中添加超过 1 个视图?

    如上所述 如何将 2 个视图放入一个单元格中tablerow 我创建了一个表格布局 并通过代码添加行 下面是我的代码 TableLayout v TableLayout inflater inflate R layout featureit
  • tns run android --emulator 不工作 - NativeScript

    我已按照此步骤操作link http docs nativescript org tutorial chapter 1 当我使用命令运行时tns run android emulator 我在命令提示符中收到以下错误 Configuring
  • R 无法解析为变量

    我想修复这个错误 R 无法解析为变量 我查了很多答案 但找不到正确的答案 我尝试了一切 有人可以帮助我吗 我的主要活动是自动创建的 显示错误的是从以下位置开始的三行case R id button1 package de vogella a
  • Gson 解析 JSON 时出现 RuntimeException:无法调用没有参数的受保护的 java.lang.ClassLoader()

    我继承了一些代码 使用 Gson 将应用程序状态保存为 JSON 然后使用fromJson http google gson googlecode com svn trunk gson docs javadocs com google gs
  • 如何将数据从当前活动传递到暂停的活动?

    我想知道如何将数据从当前活动传递到暂停的活动 请指教 我们将暂停的活动称为 A 将 当前 活动称为 B 让 B 将结果传达给 A 的方法是 A 调用startActivityForResult 代替startActivity 并供 B 使用
  • Android - 尝试重新打开已关闭的对象:使用 loaderManager 的 SQLiteQuery

    我对 android 相当陌生 我对过滤后的 listView 和它从横向模式更改为纵向模式的活动有一些问题 反之亦然 我有一个用于过滤 drinkSearch 的 editText 只要我不更改视角 纵向与横向 此过滤就有效 这是我得到的
  • 如何阻止 Android ViewFlipper 循环?

    我将 ViewFlipper 设置为每 5 秒自动翻转一次 省略一些细节 它看起来像这样并且工作正常 ViewFlipper flipper ViewFlipper findViewById R id myflipperid flipper
  • 不要保留活动 - 这样做的目的是什么?

    标题是非常不言自明的 我明白这个开发者选项的作用 What I 不明白有以下几点 首先为什么要引入这个选项 经过这些年来该框架经历了所有的变化 它仍然有用吗 我很想知道这个选择背后的原因 我相信这是一个用于调试目的的功能 来自钛文档 htt
  • Android mediacontroller 播放暂停控件无法正确刷新

    我在我的活动中使用了 MediaController 它工作正常 但是当我第一次播放视频时 应该有 b 可见的暂停按钮 但相反 有播放 当我按下该按钮时 视频会正确暂停 状态保持不变 之后它工作正常 视频完成时也会发生同样的事情 这是一个错
  • 找不到导航对象。您的组件是否位于导航器屏幕内?

    在我下面的代码中 当我使用 useNavigation 时 它会给出像我的问题一样的错误 如何使用useNavigation 请任何人都可以解决此错误 错误 找不到导航对象 您的组件是否位于导航器屏幕内 我从这里跟踪了代码https rnf
  • android 中的 lang.NumberFormatException

    我有以下代码 除了在后台线程中从数据库读取一些值并使用这些值之外什么也不做 我使用 jar 绘制折线图 对于我用于每个数组值的折线图 问题是第三个我传递给绘制 LineChart 的构造函数的参数是 float float viteza S
  • HTC One M8 - 使用第二个后置摄像头

    我有一台 HTC One M8 设备 它有 2 个后置摄像头和一个额外的前置摄像头 我的问题是尝试访问第二个后置摄像头 我已经成功制作了一个应用程序 它同时运行 2 个摄像头 1 个前置摄像头和 1 个后置摄像头 但问题是我无法访问第二个后
  • 从应用程序打开无线设置

    我想直接从我的应用程序打开 设置 gt 无线和网络 我怎样才能做到这一点 尝试这个 startActivity new Intent android provider Settings ACTION WIRELESS SETTINGS 或者
  • 如何在 EditText 中用逗号分隔数字

    我有一个 EditText 其 inputType 为number 当用户打字时 我想用逗号分隔数字 这是一个小例子 123 将表示为 123 1234 将表示为 1 234 12345 将表示为 12 345 等等 我尝试使用 TextW
  • android.media.Ringtone.play() 在播放 28 次后停止工作

    我有一个打开了几个小时的应用程序 并使用后台服务并附加了前台通知 每隔一段时间就会使用以下方式播放声音 try Ringtone r RingtoneManager getRingtone context uri r play catch
  • Android:在 Android M 中完全禁用 deviceidle(“Doze”)?

    在已 root 的 Android M 设备中 我想始终完全禁用设备空闲模式 Doze 是的 我知道这会影响我的电池寿命 这对我来说没问题 我知道我可以调用以下内容 dumpsys deviceidle disable 但是 我无法找到该子
  • Gerrit/repo 遇到“error.GitError:远程没有评论 url”

    我正在尝试在一家封闭的商店中为 Android 开发设置 Gerrit 和存储库 我在安装 Gerrit 服务器时遇到了很少的问题 但我在客户端工作站上收到此错误 repo start Falk vi AndroidManifest xml
  • 无法在 MARSHAMALLOW 文件选择器中选择 pdf、doc、ppt 等文件

    我正在使用我的 Android 应用程序将 pdf ppt doc 等文件上传到服务器 但在 Marshmallow 中 当文件选择器打开并且我浏览我的 Sdcard 或内部存储时 存在两个问题 1 它显示了所有我无法选择的文件 例如图像
  • 在为 OSMDroid 实现片段时,maps.y.p.onResume(未知来源)处出现 NulPointerException

    我目前正在尝试将我的 OSMDroid 地图活动转移到片段中 似乎一切都已正确设置 但我收到了这个奇怪的 NullPointerException 我不确定能否解决 02 20 23 59 36 140 E AndroidRuntime 9

随机推荐

  • HoloLens原理分析和硬件拆解

    不同于Google Glass 等AR 产品只能在固定位置显示一个虚拟屏幕 xff0c HoloLens 能把全息影像和真实环境杂糅在一起 xff0c 使全息影像像真实物体一样摆放在一个固定位置 xff0c 并且能像真实物体在视觉上一样近大
  • Kalibr标定camera-IMU详细步骤

    Kalibr标定相机和IMU Camera IMU Calibration 基本情况 目的 我们进行camera IMU标定的目的是为了得到IMU和相机坐标系的相对位姿矩阵T和相对时间延时t shift t imu 61 t cam 43
  • catkin build和catkin_make的区别和使用

    网上找的资料都很碎 xff0c 最好的资料是官方github文档 xff1a https github com catkin catkin tools https catkin tools readthedocs io en latest
  • darknet_ros(yolo移植到ros系统)代码分析

    darknet ros就是darknet在ros操作系统下的版本 xff0c 即在ros下使用yolo进行目标检测 首先对其文件分类 xff1a launch文件是ros格式的roslaunch文件 xff1b src文件夹下的就是它的主要
  • 激光雷达(lidar)和相机(camera)联合标定调研(基于Autoware的详细步骤)

    简单记录一下使用Autoware对lidar和cam联合标定的步骤和一些注意事项 首先 xff0c 开源的lidar和cam标定方案不多 xff0c 花了一天查资料大概有以下几个 xff1a but velodyne https githu
  • Mark一下~激光雷达点云投影到图像的方法(基于autoware的lidar_camera_calibration,外参不匹配的一些坑)

    按上篇博客的思路 xff0c 先使用autoware完成了对lidar和cam的外参标定工作 xff0c 得到的外参包括3 3R xff08 旋转矩阵 xff09 和3 1T xff08 平移向量 xff09 xff0c 统一在4 4的外参
  • Bootloader for STM32F10X

    由于懒得上传图片 xff08 图片太多 xff0c 一个个传好慢 xff09 就麻烦大家去下载WORD文档吧 xff0c 有图有真相 文档下载 http pan baidu com s 1gdnFnMF 工程下载 http download
  • 互斥量实践

    互斥量是一种特殊的二值信号量 互斥量的状态只有两种 xff0c 开锁或闭锁 xff08 两种状态值 xff09 互斥量支持递归 xff0c 持有该互斥量的线程也能够再次获得这个锁而不被挂起 自己能够再次获得互斥量 互斥量可以解决优先级翻转问
  • PX4在GAZEBO仿真中加载iris模型问题

    1 PX4启动仿真的launch系列文件 1 1 单个launch文件解读 打开每个文件的开头 xff0c 都有对它功能的描述 px4 launch Posix SITL PX4 launch script Launches Only PX
  • ROS运行管理之launch文件

    ROS是多进程 节点 的分布式框架 xff0c 一个完整的ROS系统实现 xff1a 可能包含多台主机 xff1b 每台主机上又有多个工作空间 workspace xff1b 每个的工作空间中又包含多个功能包 package xff1b 每
  • C语言中#pragma pack(1)的用法

    一 xff1a 何时使用 pragma pack 1 的用法大多是用在结构体中 二 为什么使用 pragma pack 1 结构体的字节对齐方式在不同的编译器中不同 xff0c 会存在数据冗余 xff0c 以下举个例子 struct exa
  • STM32与ROS之间的串口通信

    一 引言 马上要找工作了 xff0c 因此总结一下之前做的项目里面涉及到的重要的知识点 xff0c 经常使用到STM32与ROS之间的串口通信 xff0c 串口通信必然涉及到发送和接收两个过程 xff0c 下面详细讲解 二 STM32向RO
  • CPU、MCU、MPU、DSP的区别

    CPU xff08 Central Processing Unit xff0c 中央处理器 xff09 发展出来三个分枝 xff0c 一个是DSP xff08 Digital Signal Processing Processor xff0
  • 四旋翼与四元数学习笔记

    为了实现四旋翼无人机的编队控制 xff0c 重点学习了四旋翼加速度转换为姿态角和推力指令的具体手段 xff0c 详细学习了四元数 旋转矩阵 欧拉角在四旋翼上的应用 xff0c 学习了四旋翼的几何控制法 姿态角控制器设计 xff0c 学习了m
  • Anaconda安装

    一 确认系统 Windows系统与Linux系统的安装包不一样 xff0c 首先确认机带系统 二 下载安装包 2 1 桌面版系统下载安装 从清华镜像下载速度会比较快 网址为 xff1a https mirrors tuna tsinghua
  • SPI接口及驱动

    1 简介 SPI接口是Motorola 首先提出的全双工三线同步串行外围接口 xff0c 采用主从模式 xff08 Master Slave xff09 架构 支持多slave模式应用 xff0c 一般仅支持单Master 时钟由Maste
  • SLAM基础知识总结

    SLAM基础知识总结 1 视差与深度信息2 3D世界坐标点 gt 2D像素坐标 正向投影 2 1 世界坐标 to 摄像机坐标 xff08
  • 第五章 控制系统的稳定性分析

    2006 04 04 10 26 46 第五章 控制系统的稳定性分析 控制系统实用的首要条件是系统必须稳定 本章介绍稳定性的基本概念 稳定性判据 系统的相对稳定性 1 控制系统稳定性的基本概念 一 xff0e 稳定性的定义 xff1a 系统
  • 【无人机开发】通讯协议MavLink详解

    1 MAVLink简介 MAVLink xff08 Micro Air Vehicle Link xff0c 微型空中飞行器链路通讯协议 xff09 是无人飞行器与地面站 xff08 Ground Control Station xff0c
  • Android注解基础用法

    注解的介绍 注解介绍 注解是在 Java SE5 引入进来的 注解又称为标注 xff0c 用于为代码提供元数据 作为元数据 xff0c 注解不直接影响你的代码执行 xff0c 但也有一些类型的注解实际上可以用于这一目的 可以作用在类 方法