Android开发APT技术,与使用案例

news2025/9/19 9:50:20

annotationProcessor 是 javac 的一个工具,全称为 APT(apt 工具,Annotation Processor Tool),它用来在编译时扫描和处理注解,获取注解和被注解对象的相关信息,然后根据注解自动生成 java 代码。

简单理解就是在编译时将 java 文件编译为 class 文件时,annotationProcessor 能通过标记的注解对源码编译过程做一些调整辅助生成一些代码,提高代码执行性能。

APT注解处理器

APT全称Annotation Processing Tool,即注解处理器,它在代码编译时扫描和处理注解,即对源代码文件进行检测找出其中的注解,然后使用注解进行额外的处理,比如生成处理注解逻辑的Java文件等。

ButterKnife、EventBus、ARouter、GreenDAO都使用了APT技术。在ARouter框架中,路由表的生成过程就使用了APT技术,路由表就是在运行时生成用于帮助填充WareHouse路由元信息的类。

APT的作用时间是编译时。Android中代码编译流程:Java—>class —> dex,代码最终生成dex文件打入到APK包里面。

①APT是在编译开始时就介入的,用来处理编译时注解。

②AOP(Aspect Oridnted Programming)是在编译完成后生成dex文件之前,通过直接修改.class文件的方式,来对代码进行修改或添加逻辑。常用在代码监控、代码修改、代码分析这些场景。

使用APT可以在编译时处理注解,有如下效果:

①可以达到减少重复代码手工编写的效果。如ButterKnife,可以直接使用注解来减少findviewbyid这些代码,只需要通过注解表示是哪个id就够了。

②获取注解及生成代码都是在代码编译时候完成的,相比反射在运行时处理注解大大提高了程序性能。

注意:APT并不能对源文件进行修改,只能获取注解信息和被注解对象的信息,然后做一些自定义的处理。

宏观上理解,APT就是javac提供给开发者在编译时处理注解的一种技术;微观上,具体到实例中就是指继承自AbstractProcessor的实现类,即一个处理特定注解的处理器。

APT原理

在Java源码到class文件之间需要经过注解处理器的处理,注解处理器生成的代码也同样会经过这一过程,最终一起生成class文件。在Android中,class文件还会被打进Dex文件中,最后生成APK文件。

使用步骤

事实上它是javac的一个工具,命令行运行javac后便可以看到:

接下来我们就来实现一个apt的实例,类似于ButterKnife中@BindView注解,基本步骤如下:

1、定义要被处理的注解。

2、定义注解处理器(生成具体的类)。

3、调用处理器生成的代码

对应的,我们在工程中需要有这几个模块:

1、app。测试我们的功能

2、apt-annotation。一个Java library module,放置我们自定义注解

3、apt-processor。一个Java library module,注解处理器模块

4、apt-sdk。一个Android library module,通过反射调用apt-processor模块生成的方法,实现view的绑定。

工程目录如下:

1、在apt-annotation中自定义注解:


import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;  @Retention(RetentionPolicy.CLASS) @Target(ElementType.FIELD) public @interface BindView {     int value(); }

2、apt-processor中引入依赖,它需要依赖apt-annotation,同时还需要依赖auto-service第三方库,后面创建注解处理器的时候需要用到。

apt-processor/build.gradle文件中:

implementation project(':apt-annotation') implementation 'com.google.auto.service:auto-service:1.0-rc2'

3、在pat-processor中创建注解处理器:

处理器需要继承AbstractProcessor,注意该module是 java module,如果创建的是android module的话那么就会找不到AbstractProcessor


@AutoService(Processor.class) @SuppressWarnings("unused") public class BindViewProcessor extends AbstractProcessor {     private Elements mElementUtils;     private Map<String, ClassCreatorFactory> mClassCreatorFactoryMap = new HashMap<>();      @Override     public synchronized void init(ProcessingEnvironment processingEnvironment) {         super.init(processingEnvironment);         //拿到工具类         mElementUtils = processingEnvironment.getElementUtils();     }      @Override     public Set<String> getSupportedAnnotationTypes() {         //这个注解处理器是给哪个注解用的         HashSet<String> supportType = new LinkedHashSet<>();         supportType.add(BindView.class.getCanonicalName());         return supportType;     }      @Override     public SourceVersion getSupportedSourceVersion() {         //返回java版本 return SourceVersion.latestSupported();     }      @Override     public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {         mClassCreatorFactoryMap.clear();         //得到所有包含该注解的element集合         Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class);          for (Element element : elements) {             //转换为VariableElement,VariableElement为element的子类             VariableElement variableElement = (VariableElement) element;             //可以获取类的信息的element,也是element的子类             TypeElement classElement = (TypeElement) variableElement.getEnclosingElement();             //获取包名加类名             String fullClassName = classElement.getQualifiedName().toString();                          //保存到集合中             ClassCreatorFactory factory = mClassCreatorFactoryMap.get(fullClassName);             if (factory == null) {                 factory = new ClassCreatorFactory(mElementUtils, classElement);                 mClassCreatorFactoryMap.put(fullClassName, factory);             }             BindView bindViewAnnotation = variableElement.getAnnotation(BindView.class);             int id = bindViewAnnotation.value();             factory.putElement(id, variableElement);         }         //开始创建java类 for (String key : mClassCreatorFactoryMap.keySet()) {             ClassCreatorFactory factory = mClassCreatorFactoryMap.get(key);             try {                 JavaFileObject fileObject = processingEnv.getFiler().createSourceFile(                         factory.getClassFullName(), factory.getTypeElement());                 Writer writer = fileObject.openWriter();                 //写入java代码                 writer.write(factory.generateJavaCode());                 writer.flush();                 writer.close();             } catch (IOException e) {                 e.printStackTrace();             }         }         return true;     } }

需要注意的是代码中不能有中文,否则编译不通过,我这里为了方便注释解释加上了中文。

ClassCreatorFactory的代码如下,这个类负责提供需要写入新的类的代码:


public class ClassCreatorFactory {     private String mBindClassName;     private String mPackageName;     private TypeElement mTypeElement;     private Map<Integer, VariableElement> mVariableElementMap = new HashMap<>();      ClassCreatorFactory(Elements elementUtils, TypeElement classElement) {         this.mTypeElement = classElement;         //PackageElement是element的子类,可以拿到包信息         PackageElement packageElement = elementUtils.getPackageOf(mTypeElement);         String packageName = packageElement.getQualifiedName().toString();         String className = mTypeElement.getSimpleName().toString();         this.mPackageName = packageName;         //生成的类的名称 this.mBindClassName = className + "_ViewBinding";     }      public void putElement(int id, VariableElement element) {         mVariableElementMap.put(id, element);     }      public String generateJavaCode() {         StringBuilder stringBuilder = new StringBuilder();         stringBuilder.append("/**\n" + " * Auto Created by apt\n" + "*/\n");         stringBuilder.append("package ").append(mPackageName).append(";\n");         stringBuilder.append('\n');         stringBuilder.append("public class ").append(mBindClassName);         stringBuilder.append(" {\n");         generateBindViewMethods(stringBuilder);          stringBuilder.append('\n');         stringBuilder.append("}\n");         return stringBuilder.toString();     }      private void generateBindViewMethods(StringBuilder stringBuilder) {         stringBuilder.append("\tpublic void bindView(");         stringBuilder.append(mTypeElement.getQualifiedName());         stringBuilder.append(" owner) {\n");         for (int id : mVariableElementMap.keySet()) {             VariableElement variableElement = mVariableElementMap.get(id);             String viewName = variableElement.getSimpleName().toString();             String viewType = variableElement.asType().toString();             stringBuilder.append("\t\towner.");             stringBuilder.append(viewName);             stringBuilder.append(" = ");             stringBuilder.append("(");             stringBuilder.append(viewType);             stringBuilder.append(")(((android.app.Activity)owner).findViewById( ");             stringBuilder.append(id);             stringBuilder.append("));\n");         }         stringBuilder.append("  }\n");     }      public String getClassFullName() {         return mPackageName + "." + mBindClassName;     }      public TypeElement getTypeElement() {         return mTypeElement;     } }

先不谈apt-sdk模块,我们先来看看生成的代码是怎么样的。

在app的gradle中引入:

implementation project(‘:apt-annotation’) annotationProcessor project(‘:apt-processor’)

特别要注意的是apt-processor模块的依赖引进要用 annotationProcessor,否则编译报错

两个activity中:


public class MainActivity extends AppCompatActivity {     @BindView(R.id.tv)     TextView textView;      @BindView(R.id.tv_1)     TextView tv;      @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main);     } }



public class Main2Activity extends AppCompatActivity {      @BindView(R.id.tv_2)     TextView textView;      @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main2);     } }

rebuild一下便可以看到在这个目录下有我们生成的文件了。

gradle高版本出现编译后没出现文件的问题,无奈只好降低版本,我使用的版本是gradle 3.1.4 + gralde_wrap gradle-4.4-all.zip

 点进入其中一个可以看到是这样的代码:
/**  * Auto Created by apt */ package com.example.aptsample;  public class MainActivity_ViewBinding {     public void bindView(com.example.aptsample.MainActivity owner) {         owner.tv = (android.widget.TextView)(((android.app.Activity)owner).findViewById( 2131165360));         owner.textView = (android.widget.TextView)(((android.app.Activity)owner).findViewById( 2131165359));   }  }

所以我们只要调用bindView就能够找到该view了,这也是apt-sdk要做的事情。

4、在apt-sdk中创建类,反射调用生成的类中的方法


public class DataApi {     public static void bindView(Activity activity) {         Class clazz = activity.getClass();         try {             Class<?> bindViewClass = Class.forName(clazz.getName() + "_ViewBinding");             Method method = bindViewClass.getMethod("bindView", activity.getClass());             method.invoke(bindViewClass.newInstance(),activity);         } catch (ClassNotFoundException e) {             e.printStackTrace();         } catch (NoSuchMethodException e) {             e.printStackTrace();         } catch (IllegalAccessException e) {             e.printStackTrace();         } catch (InstantiationException e) {             e.printStackTrace();         } catch (InvocationTargetException e) {             e.printStackTrace();         }     } }

5、app的gradle中引入apt-sdk,然后代码调用DataApi的方法

implementation project(':apt-annotation') annotationProcessor project(':apt-processor') implementation project(':apt-sdk')

app的MainActivity中实现


public class MainActivity extends AppCompatActivity {     @BindView(R.id.tv)     TextView textView;      @BindView(R.id.tv_1)     TextView tv;      @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(R.layout.activity_main);         DataApi.bindView(this);         tv.setText("a");     } }

这样就大功告成了

有关Android开发中的apt技术就解析到这里,有关更多的Android开发技术可以参考《Android核心技术手册》点击可以查看详细类目。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1086601.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【arm实验2】按键中断事件控制实验

设置按键中断&#xff0c;按键1按下&#xff0c;LED亮&#xff0c;再次按下&#xff0c;灭 按键2按下&#xff0c;蜂鸣器叫&#xff0c;再次按下&#xff0c;停 按键3按下&#xff0c;风扇转&#xff0c;再次按下&#xff0c;停 主函数&#xff1a; linuxlinux:~/study/08-c$…

江苏建筑模板厂家-建筑模板批发供应商

江苏建筑模板厂家在建筑行业中扮演着重要的角色。建筑模板是建筑施工中不可或缺的一部分&#xff0c;用于支撑混凝土浇筑过程中的形状和结构。在江苏地区的气候地形、经济发展和建筑风格等方面的考虑下&#xff0c;我们将对几种常见的建筑模板材料进行比较&#xff0c;包括钢模…

Linux Centos安装Sql Server数据库,结合cpolar内网穿透实现公网访问

目录 前言 1. 安装sql server 2. 局域网测试连接 3. 安装cpolar内网穿透 4. 将sqlserver映射到公网 5. 公网远程连接 6.固定连接公网地址 7.使用固定公网地址连接 前言 简单几步实现在Linux centos环境下安装部署sql server数据库&#xff0c;并结合cpolar内网穿透工具…

ElementUI编辑表格单元格与查看模式切换的应用

需求&#xff1a;有时候在填写表单的时候&#xff0c;想要在输入的时候是input输入框的状态&#xff0c;但是当鼠标移出输入框失去焦点时&#xff0c;希望是查看的状态&#xff0c;这种场景可以通过 v-if实现 vue2ElementUi里面使用如下&#xff1a; 1.el-table标签注册 cell-…

非交互式ssh command无法找到命令command not found

起因 mha做check时无法获得node的版本 问题判断 分析下源码 执行的代码实际是 ssh $MHA::ManagerConst::SSH_OPT_ALIVE $ssh_user_host -p $ssh_port apply_diff_relay_logs --version 这里附一下ssh用的const SSH_OPT_ALIVE "-o ServerAliveInterval60 -o ServerAl…

MySQL 3 环境搭建 MySQL 5.7版本的安装、配置

MySQL5.7.43官网下载地址 MySQL :: Download MySQL Community Server 这里选5.7.43&#xff0c;Windows版本&#xff0c;然后点击Go to Download Page&#xff0c;下载msi安装包的版本 MSI安装包版本比ZIP压缩包版本的安装过程要简单的多&#xff0c;过程更加清楚直观&#x…

位运算解决简单逻辑推理问题

今天在学习Go语言中的位运算时&#xff0c;老师突然问我们位运算可以有哪些运用场景&#xff1f;这一点让我一下子想到了刷到的leetcode中的两道题目&#xff0c;例如丢失的数字和只出现一次的数字。有兴趣的小伙伴可以尝试做做看。 这两道题目都是用位运算解决的&#xff0c;并…

软考实行机考后,考前需要带什么?

软考实行机考之后&#xff0c;很多宝子们都在问考场需要带什么&#xff1f;今天给大家分享一下需要准备的东西~大家自行查看 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 2023年11月软考考前必看—考试需携带的物品 1、从2023年下半年开始&#xff0c…

网页博弈测试报告

文章目录 项目介绍项目测试用例自动化测试注册自动化测试登录自动化测试匹配测试 性能测试编写脚本创建测试场景性能测试报告 项目介绍 该项目是一个网页对战的五子棋游戏&#xff0c;支持真人匹配对战和人机对战&#xff0c;支持多用户同时匹配进行五子棋对战&#xff0c;通过…

mysql数据库 windows迁移至linux

1.打开navicat&#xff0c;选择一个数据库进行操作&#xff1a; 之后文件会保存为一个xxx.sql文件&#xff0c;之后打开xftp&#xff0c;把生成的sql放进一个文件夹中(/home/dell/linuxmysql)&#xff1a; 之后登录mysql数据库&#xff0c;并创建一个新的数据库&#xff0c;然后…

Pytorch之ConvNeXt图像分类

文章目录 前言一、ConvNeXt设计决策1.设计方案2.Training Techniques3.Macro Design&#x1f947;Changing stage compute ratio&#x1f948;Change stem to "Patchify" 4.ResNeXt-ify5. Inverted Bottleneck6.Large Kernel Size7.Micro Design✨Replacing ReLU wit…

论文阅读:Fast-BEV: Towards Real-time On-vehicleBird’s-Eye View Perception

Abstract 现有的BEV解决方案要么需要大量的资源执行车载推理&#xff0c;要么效果一般。Fast-BEV包含五部分&#xff1a; 1&#xff09;一个轻量化部署友好的视角转换方式&#xff0c;可以快速将2D图像特征转到3D体素空间。 2&#xff09;一个多尺度图像编码器利用多尺度特征。…

Mac/Wins Matlab如何查看APPs源码

查看Apps方法一样&#xff0c;点击HOME-preferences-MATLAB-Apps查看你的Apps安装路径。 你的Apps文件就安装在该目录下&#xff0c;直接进入这个目录就可以看到你自己写的APPs文件&#xff0c;

Fastjson历史版本记录

1.2.24 TemplatesImpl&#xff0c;利用条件苛刻&#xff0c;需要开启Feature.SupportNonPublicField {"type": "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes": ["yv66vgAAADQA...CJAAk"],"_name…

光通路数据单元(ODU),ODUk,同步传送模块(STM),虚容器(VC)等级

文章目录 ODUk同步传送模块&#xff0c;STM虚容器&#xff0c;VC时隙 光通路 数据 单元(ODU&#xff0c;Optical channel data unit) 提供与信号无关的连通性&#xff0c;连接保护和监控等功能&#xff0c;这一层也叫数据通道层。 ODUk&#xff0c;k值越大&#xff0c;帧周期越…

SpringBootCms

SpringBootCMS&#xff0c;极速开发&#xff0c;动态添加字段&#xff0c;自定义标签&#xff0c;动态创建数据库表并crud数据&#xff0c;数据库备份、还原&#xff0c;动态添加站点(多站点功能)&#xff0c;一键生成模板代码&#xff0c;让您轻松打造自己的独立网站&#xff…

spark读取hive表字段,区分大小写问题

背景 spark任务读取hive表&#xff0c;查询字段为小写&#xff0c;但Hive表字段为大写&#xff0c;无法读取数据 问题错误: 如何解决呢&#xff1f; In version 2.3 and earlier, when reading from a Parquet data source table, Spark always returns null for any column …

高防CDN:网络安全的锁与钥匙

深度解析高防CDN原理 高防CDN&#xff0c;一门整合防护与分发功能的互联网安全技术&#xff0c;以卓越之姿应对DDoS攻击等网络威胁&#xff0c;巧妙地辨识和过滤访问源地址&#xff0c;以保障用户畅通访问和服务的稳定。其核心原理包括&#xff1a; 流量清洗&#xff1a; 高防C…

mysql数据库语法改造成dm数据库DATE_SUB

给了一个任务就是把一个项目用dm数据库正常跑起来&#xff0c;真的难呀&#xff0c;项目原本是使用的mysql、 问题寻找 dm数据库保存&#xff0c;我看mysql跑的挺好的&#xff0c;然后开始找原因。 其中还没有看懂这两个函数&#xff0c;特意搜了搜 LAST_DAY 取最后一天 DATE_…

社区投稿| 以安全视角,深度剖析 Sui Staking 与 LSD

本篇技术研报由 MoveBit 研究团队的 Jason 撰写 #1 Sui Staking 介绍 1.1 Sui 网络概述 Sui 网络由一组独立的验证者运行&#xff0c;每个验证者在自己的机器或集群上运行独立的 Sui 软件实例。 Sui 采用委托权益证明&#xff08;DPoS&#xff09;来确定哪些验证者参与网络…