APT案例之点击事件
目录介绍
- 01.创建项目步骤
- 1.1 项目搭建
- 1.2 项目功能
- 02.自定义注解
- 03.创建Processor
- 04.compiler配置文件
- 05.编译jar
- 06.如何使用
- 07.编译生成代码
- 08.部分源码说明
- 8.1 Process类-process方法
- 8.2 OnceProxyInfo代理类
- 8.3 OnceMethod类
好消息
- 博客笔记大汇总【16年3月到至今】,包括Java基础及深入知识点,Android技术博客,Python学习笔记等等,还包括平时开发中遇到的bug汇总,当然也在工作之余收集了大量的面试题,长期更新维护并且修正,持续完善……开源的文件是markdown格式的!同时也开源了生活博客,从12年起,积累共计N篇[近100万字,陆续搬到网上],转载请注明出处,谢谢!
- 链接地址:https://github.com/yangchong211/YCBlogs
- 如果觉得好,可以star一下,谢谢!当然也欢迎提出建议或者问题,万事起于忽微,量变引起质变!
关于apt实践与总结开源库地址
https://github.com/yangchong211/YCApt
00.注解系列博客汇总
0.1 注解基础系列博客
- 01.Annotation注解详细介绍
- 02.Dagger2深入分析,待更新
- 03.注解详细介绍
- 什么是注解,注解分类有哪些?自定义注解分类?运行注解案例展示分析,以一个最简单的案例理解注解……使用注解替代枚举,使用注解限定类型
- 04.APT技术详解
- 什么是apt?理解注解处理器的作用和用途……android-apt被替代?annotationProcessor和apt区别? 什么是jack编译方式?
- 06.自定义annotation注解
- @Retention的作用?@Target(ElementType.TYPE)的解释,@Inherited注解可以被继承吗?Annotation里面的方法为何不能是private?
- 07.注解之兼容kotlin
- 后期更新
- 08.注解之处理器类Processor
- 处理器类Processor介绍,重要方法,Element的作用,修饰方法的注解和ExecutableElement,了解修饰属性、类成员的注解和VariableElement……
- 10.注解遇到问题和解决方案
- 无法引入javax包下的类库,成功运行一次,修改代码后再运行就报错
- 11.注解代替枚举
- 在做内存优化时,推荐使用注解代替枚举,因为枚举占用的内存更高,如何说明枚举占用内存高呢?这是为什么呢?
- 12.注解练习案例开源代码
- 注解学习小案例,比较系统性学习注解并且应用实践。简单应用了运行期注解,通过注解实现了setContentView功能;简单应用了编译器注解,通过注解实现了防暴力点击的功能,同时支持设置时间间隔;使用注解替代枚举;使用注解一步步搭建简单路由案例。结合相应的博客,在来一些小案例,从此应该对注解有更加深入的理解……
- 13 ARouter路由解析
- 比较详细地分析了阿里路由库
- 14 搭建路由条件
- 为何需要路由?实现路由方式有哪些,这些方式各有何优缺点?使用注解实现路由需要具备的条件以及简单原理分析……
- 15 通过注解去实现路由跳转
- 自定义Router注解,Router注解里有path和group,这便是仿照ARouter对路由进行分组。然后看看注解生成的代码,手写路由跳转代码。
- 16 自定义路由Processor编译器
- Processor介绍,重要方法,Element的作用,修饰方法的注解和ExecutableElement
- 17 利用apt生成路由映射文件
- 在Activity类上加上@Router注解之后,便可通过apt来生成对应的路由表,那么究竟是如何生成的代码呢?
- 在组件化开发中,有多个module,为何要在build.gradle配置moduleName,又是如何通过代码拿到module名称?
- process处理方法如何生成代码的,又是如何写入具体的路径,写入文件的?
- 看完这篇文章,应该就能够理解上面这些问题呢!
- 18 路由框架的设计和初始化
- 编译期是在你的项目编译的时候,这个时候还没有开始打包,也就是你没有生成apk呢!路由框架在这个时期根据注解去扫描所有文件,然后生成路由映射文件。这些文件都会统一打包到apk里,app运行时期做的东西也不少,但总而言之都是对映射信息的处理,如执行执行路由跳转等。那么如何设计框架呢?
- 生成的注解代码,又是如何把这些路由映射关系拿到手,或者说在什么时候拿到手比较合适?为何注解需要进行初始化操作?
- 如何得到得到路由表的类名,如何得到所有的routerAddress---activityClass映射关系?
- 19 路由框架设计注意要点
- 需要注意哪些要点?
- 20 为何需要依赖注入
- 有哪些注入的方式可以解耦,你能想到多少?路由框架为何需要依赖注入?路由为何用注解进行依赖注入,而不是用反射方式注入,或者通过构造方法注入,或者通过接口方式注入?
- 21 Activity属性注入
- 在跳转页面时,如何传递intent参数,或者如何实现跳转回调处理逻辑?
01.创建项目步骤
1.1 项目搭建
- 首先创建一个Android项目。然后给我们的项目增加一个module,一定要记得是Java Library。因为APT需要用到jdk下的 【 *javax.~ *】包下的类,这在AndroidSdk中是没有的。
- 一定要注意:**需要说明的是:**我们的目的是写一个Android库,APT Moudle是java Library,不能使用Android API。所以还需要创建一个Android Library,负责框架主体部分. 然后由Android Library引用APT jar包。
- 项目目录结构如图:
- app:Demo
- AptAnnotation:java Library主要放一些项目中需要用到的自定义注解及相关代码
- AptApi:Android Library. OnceClick是我们真正对外发布并交由第三方使用的库,它引用了apt-jar包
- AptCompiler:java Library主要是应用apt技术处理注解,生成相关代码或者相关源文件,是核心所在。
1.2 项目功能
- 在一定时间内,按钮点击事件只能执行一次。未到指定时间,不执行点击事件。
02.自定义注解
- 创建Annotation Module,需要创建一个Java Library,名称可为annotation,主要放一些项目中需要用到的自定义注解及相关代码
- 新建一个类,OnceClick。就是我们自定义的注解。
/**
* <pre>
* @author 杨充
* blog : https://github.com/yangchong211
* time : 2017/06/21
* desc : 一定time时间内该点击事件只能执行一次
* revise:
* </pre>
*/
//@Retention用来修饰这是一个什么类型的注解。这里表示该注解是一个编译时注解。
@Retention(RetentionPolicy.CLASS)
//@Target用来表示这个注解可以使用在哪些地方。
// 比如:类、方法、属性、接口等等。这里ElementType.METHOD 表示这个注解可以用来修饰:方法
@Target(ElementType.METHOD)
//这里的interface并不是说OnceClick是一个接口。就像申明类用关键字class。申明注解用的就是@interface。
public @interface OnceClick {
//返回值表示这个注解里可以存放什么类型值
int value();
}
03.创建Processor
- 创建Compiler Module,需要再创建一个Java Library,名称可为compiler,主要是应用apt技术处理注解,生成相关代码或者相关源文件,是核心所在。
- Processor是用来处理Annotation的类。继承自AbstractProcessor。
/**
* <pre>
* @author 杨充
* blog : https://github.com/yangchong211
* time : 2017/06/21
* desc : 自定义Processor编译器
* revise:
* </pre>
*/
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class OnceClickProcessor extends AbstractProcessor { private Messager messager;
private Elements elementUtils; @Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
messager = processingEnv.getMessager();
elementUtils = processingEnv.getElementUtils();
} @Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//获取proxyMap
Map<String, OnceProxyInfo> proxyMap = getProxyMap(roundEnv);
//遍历proxyMap,并生成代码
for (String key : proxyMap.keySet()) {
OnceProxyInfo proxyInfo = proxyMap.get(key);
writeCode(proxyInfo);
}
return true;
} @Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
types.add(OnceClick.class.getCanonicalName());
return types;
} @Override
public SourceVersion getSupportedSourceVersion() {
return super.getSupportedSourceVersion();
}
}
04.compiler配置文件
- build.gradle文件配置
- auto-service的作用是向系统注册processor(自定义注解处理器),执行编译时使用processor进行处理。
- javapoet提供了一套生成java代码的api,利用这些api处理注解,生成新的代码或源文件。
- OnceClickAnnotation是上文创建的注解module。
apply plugin: 'java-library' dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.google.auto.service:auto-service:1.0-rc3'
implementation 'com.squareup:javapoet:1.10.0'
implementation project(':OnceClickAnnotation')
} sourceCompatibility = "7"
targetCompatibility = "7"
05.编译jar
- 这里有一个坑,主Module是不可以直接引用这个java Module的。(直接引用,可以成功运行一次~修改代码以后就不能运行了)而如何单独编译这个java Module呢?在编译器Gradle视图里,找到Module apt下的build目录下的Build按钮。双击运行。
- 代码没有问题编译通过的话,会有BUILD SUCCESS提示。生成的jar包在 apt 下的build目录下的libs下。将apt.jar拷贝到app下的libs目录,右键该jar,点击Add as Library,添加Library
06.如何使用
- 代码如下所示
public class MainActivity extends AppCompatActivity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化OnceClick,并设置点击事件间隔是2秒
OnceInit.once(this,2000);
} @OnceClick(R.id.tv_1)
public void Click1(){
Log.d("tag--------------------","tv_1");
} @OnceClick(R.id.tv_2)
public void Click2(View v){
Log.d("tag--------------------","tv_2");
}
}
07.编译生成代码
- 编译之后生成的代码路径,在项目中的build文件夹,如图所示
- 编译之后生成的代码
// 编译生成的代码,不要修改
// 更多内容:https://github.com/yangchong211
package com.ycbjie.ycapt; import android.view.View;
import com.ycbjie.api.Finder;
import com.ycbjie.api.AbstractInjector; public class MainActivity$$_Once_Proxy<T extends MainActivity> implements AbstractInjector<T> { public long intervalTime; @Override
public void setIntervalTime(long time) {
intervalTime = time;
} @Override
public void inject(final Finder finder, final T target, Object source) {
View view;
view = finder.findViewById(source, 2131165325);
if(view != null){
view.setOnClickListener(new View.OnClickListener() {
long time = 0L;
@Override
public void onClick(View v) {
long temp = System.currentTimeMillis();
if (temp - time >= intervalTime) {
time = temp;
target.Click1();
}
}});
}
view = finder.findViewById(source, 2131165326);
if(view != null){
view.setOnClickListener(new View.OnClickListener() {
long time = 0L;
@Override
public void onClick(View v) {
long temp = System.currentTimeMillis();
if (temp - time >= intervalTime) {
time = temp;
target.Click2(v);
}
}});
}
} }
08.部分源码说明
8.1 Process类-process方法
- 当某个类Activity使用了@OnceClick注解之后,我们就应该为其生成一个对应的代理类,代理类实现我们框架的功能:为某个View设置点击事件,并且这个点击事件一定时间内只能执行一次。所以,一个代理类可能有多个需要处理的View。
- 先看process代码:
- ProxyInfo对象:存放生成代理类的必要信息,并生成代码。
- getProxyMap方法:使用参数roundEnv,遍历所有@OnceClick注解,并生成代理类ProxyInfo的Map。
- writeCode方法:真正生成代码的方法。
- 总结一下:编译时,取得所有需要生成的代理类信息。遍历代理类集合,根据代理类信息,生成代码。
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
//获取proxyMap
Map<String, OnceProxyInfo> proxyMap = getProxyMap(roundEnv);
//遍历proxyMap,并生成代码
for (String key : proxyMap.keySet()) {
OnceProxyInfo proxyInfo = proxyMap.get(key);
//写入代码
writeCode(proxyInfo);
}
return true;
}
8.2 OnceProxyInfo代理类
- 其实这个类,才是这个框架的重中之重,因为生成什么代码,全靠这个类说了算。这个类也没什么好讲的,就是用StringBuidler拼出一个类来。ProxyInfo保存的是类信息,方法信息我们用List methods保存。然后根据这些信息生成类。
public class OnceProxyInfo { private String packageName;
private String targetClassName;
private String proxyClassName;
private TypeElement typeElement;
private List<OnceMethod> methods;
private static final String PROXY = "_Once_Proxy"; OnceProxyInfo(String packageName, String className) {
this.packageName = packageName;
this.targetClassName = className;
this.proxyClassName = className + "$$" + PROXY;
} String getProxyClassFullName() {
return packageName + "." + proxyClassName;
} String generateJavaCode() throws OnceClickException { StringBuilder builder = new StringBuilder();
builder.append("// 编译生成的代码,不要修改\n");
builder.append("// 更多内容:https://github.com/yangchong211\n");
builder.append("package ").append(packageName).append(";\n\n"); //写入导包
builder.append("import android.view.View;\n");
builder.append("import com.ycbjie.api.Finder;\n");
builder.append("import com.ycbjie.api.AbstractInjector;\n");
builder.append('\n'); builder.append("public class ").append(proxyClassName)
.append("<T extends ").append(getTargetClassName()).append(">")
.append(" implements AbstractInjector<T>").append(" {\n");
builder.append('\n'); generateInjectMethod(builder);
builder.append('\n'); builder.append("}\n");
return builder.toString(); } private String getTargetClassName() {
return targetClassName.replace("$", ".");
} private void generateInjectMethod(StringBuilder builder) throws OnceClickException {
builder.append(" public long intervalTime; \n");
builder.append('\n'); builder.append(" @Override \n")
.append(" public void setIntervalTime(long time) {\n")
.append(" intervalTime = time;\n } \n");
builder.append('\n'); builder.append(" @Override \n")
.append(" public void inject(final Finder finder, final T target, Object source) {\n");
builder.append(" View view;");
builder.append('\n'); //这一步是遍历所有的方法
for (OnceMethod method : getMethods()) {
builder.append(" view = ")
.append("finder.findViewById(source, ")
.append(method.getId())
.append(");\n");
builder.append(" if(view != null){\n")
.append(" view.setOnClickListener(new View.OnClickListener() {\n")
.append(" long time = 0L;\n");
builder.append(" @Override\n")
.append(" public void onClick(View v) {\n");
builder.append(" long temp = System.currentTimeMillis();\n")
.append(" if (temp - time >= intervalTime) {\n" +
" time = temp;\n");
if (method.getMethodParametersSize() == 1) {
if (method.getMethodParameters().get(0).equals("android.view.View")) {
builder.append(" target.")
.append(method.getMethodName()).append("(v);");
} else {
throw new OnceClickException("Parameters must be android.view.View");
}
} else if (method.getMethodParametersSize() == 0) {
builder.append(" target.")
.append(method.getMethodName()).append("();");
} else {
throw new OnceClickException("Does not support more than one parameter");
}
builder.append("\n }\n")
.append(" }")
.append("});\n }\n");
} builder.append(" }\n");
} TypeElement getTypeElement() {
return typeElement;
} void setTypeElement(TypeElement typeElement) {
this.typeElement = typeElement;
} List<OnceMethod> getMethods() {
return methods == null ? new ArrayList<OnceMethod>() : methods;
} void addMethod(OnceMethod onceMethod) {
if (methods == null) {
methods = new ArrayList<>();
}
methods.add(onceMethod);
}
}
8.3 OnceMethod类
- 需要讲的一点是,每一个使用了@OnceClick注解的Activity或View,都会为其生成一个代理类,而一个代理中有可能有很多个@OnceClick修饰的方法,所以我们专门为每个方法有创建了一个javaBean用于保存方法信息:
public class OnceMethod { private int id;
private String methodName;
private List<String> methodParameters; OnceMethod(int id, String methodName, List<String> methodParameters) {
this.id = id;
this.methodName = methodName;
this.methodParameters = methodParameters;
} int getMethodParametersSize() {
return methodParameters == null ? 0 : methodParameters.size();
} int getId() {
return id;
} String getMethodName() {
return methodName;
} List<String> getMethodParameters() {
return methodParameters;
} }
关于其他内容介绍
01.关于博客汇总链接
02.关于我的博客
- 我的个人站点:www.yczbj.org,www.ycbjie.cn
- github:https://github.com/yangchong211
- 知乎:https://www.zhihu.com/people/yczbj/activities
- 简书:http://www.jianshu.com/u/b7b2c6ed9284
- csdn:http://my.csdn.net/m0_37700275
- 喜马拉雅听书:http://www.ximalaya.com/zhubo/71989305/
- 开源中国:https://my.oschina.net/zbj1618/blog
- 泡在网上的日子:http://www.jcodecraeer.com/member/content_list.php?channelid=1
- 邮箱:yangchong211@163.com
- 阿里云博客:https://yq.aliyun.com/users/article?spm=5176.100- 239.headeruserinfo.3.dT4bcV
- segmentfault头条:https://segmentfault.com/u/xiangjianyu/articles
- 掘金:https://juejin.im/user/5939433efe88c2006afa0c6e
关于apt实践与总结开源库地址
https://github.com/yangchong211/YCApt
APT案例之点击事件的更多相关文章
- 点击DIV触发其他元素的点击事件(案例:点击type="radio" 的input 标签外层DIV,触发内部单选点击选中事件)
方法一: 直接用找到对应dom元素调用.click()方法 $('.user_content').click(function(){ $(this).children()[0].click(); // ...
- CSS实现点击事件及实践
实现原理利用:target,把a标签自身的href以及id设置成一样的. 案例1:实现元素的切换 HTML: <div id="box"> <a href=&qu ...
- Android 高级UI设计笔记20:RecyclerView 的详解之RecyclerView添加Item点击事件
1. 引言: RecyclerView侧重的是布局的灵活性,虽说可以替代ListView但是连基本的点击事件都没有,这篇文章就来详细讲解如何为RecyclerView的item添加点击事件,顺便复习一 ...
- jQuery之防止冒泡事件,冒泡事件就是点击子节点,会向上触发父节点,祖先节点的点击事件。
冒泡事件就是点击子节点,会向上触发父节点,祖先节点的点击事件. 下面是html代码部分: <body> <div id="content"> 外层div元素 ...
- Android viewpager 嵌套 viewpager滑动 点击事件冲突解决方案
为了解决这个问题.可以自定义viewpager,然后在里面监听首饰,自定义点击事件 package com.hpuvoice.view; import android.content.Context; ...
- echarts雷达图点击事件
最近看见别人问的问题,点击雷达图的拐点,获取点击数据的问题,直接上代码. echarts配置问题:https://www.douban.com/note/509404582/ <!doctype ...
- [Android] 点击事件的四种写法
点击事件的必备条件:实现OnClickListener接口,重写onclick(View v)方法 以拨号简单案例为例,如下图效果: 逻辑流程: 获取点击对象,获取数据 给对象设置监听类 实现OnCl ...
- echarts雷达图点击事件 包含(2.x,3.85,4.02)测试
最近看见别人问的问题,点击雷达图的拐点,获取点击数据的问题,直接上代码. echarts 2x 的点击事件 echarts配置问题:https://www.douban.com/note/509404 ...
- JS中点击事件冒泡阻止
JS中点击事件冒泡阻止 解析: 一个div层'out',内含有一个div层'in'.如下: 两个层都绑定了点击事件,但是点击in层的时候,点击事件会出现冒泡现象,同时也会触发out层的点击事件. 但是 ...
- [Android]Java中点击事件的四种写法
点击事件的必备条件:实现OnClickListener接口,重写onclick(View v)方法 以拨号简单案例为例,如下图效果: 逻辑流程: 获取点击对象,获取数据 给对象设置监听类 实现OnCl ...
随机推荐
- Linux(Centos7)升级MySQL 5.7到8.0.31
一.下载MySQL安装包 下载地址:https://downloads.mysql.com/archives/community/ 二.备份 mkdir /home/mysqlback mysqldu ...
- python 学习随笔1121
Python 数据处理几个好用又简单的库: json re string pandas 与系统交互: subprocess os
- Js中Array对象
Js中Array对象 JavaScript的Array对象是用于构造数组的全局对象,数组是类似于列表的高阶对象. 描述 在JavaScript中通常可以使用Array构造器与字面量的方式创建数组. c ...
- ysoserial URLDNS利用链分析
在分析URLDNS之前,必须了解JAVA序列化和反序列化的基本概念.其中几个重要的概念: 需要让某个对象支持序列化机制,就必须让其类是可序列化,为了让某类可序列化的,该类就必须实现如下两个接口之一: ...
- Innodb存储引擎的文件
目录 概述 参数文件 日志文件 错误日志 慢查询日志 查询日志 二进制日志 binary log 二进制日志的配置 二进制日志的作用 二进制日志的保存 socket 套接字文件 pid文件 MySQL ...
- 深入理解Go语言(01): interface源码分析
分析接口的赋值,反射,断言的实现原理 版本:golang v1.12 interface底层使用2个struct表示的:eface和iface 一:接口类型分为2个 1. 空接口 //比如 var i ...
- java+mysql数据库实现的学生管理系统
说明: java+mysql数据库实现的学生管理系统 功能 实现增加学生.删除学生.修改学生.学生列表.查询学生功能 截图: 开发工具/技术 java eclipse 价格:50元,有需要联系 微信 ...
- Flutter学习
常用网址 免费下载 !<AliFlutter 体系化建设和实践> Flutter 开发文档 Flutter实战 Dart 编程语言概览 pub仓库 main函数使用了(=>)符号, ...
- PMP考试之【PMBOK(第六版)49个过程及ITTO】
- Codeforces Round 926 (Div. 2)(A~D)
目录 A B C D A 输出最大值减最小值,或者排序算一下答案 #include <bits/stdc++.h> #define int long long #define rep(i, ...