关于Apt注解实践与总结【包含20篇博客】

32次阅读

共计 8888 个字符,预计需要花费 23 分钟才能阅读完成。

YCApt 关于 apt 方案实践与总结
目录介绍

00. 注解系列博客汇总
01. 什么是 apt
02.annotationProcessor 和 apt 区别
03. 项目目录结构
04. 该案例作用
05. 使用说明
06. 编译期注解生成代码 [点击事件案例]
07. 运行期注解案例 [setContentView 案例]
08. 使用注解替代枚举

09. 使用注解搭建路由 [综合案例]

9.1 搭建路由条件
9.2 通过注解去实现路由跳转
9.3 自定义路由 Processor 编译器
9.4 利用 apt 生成路由映射文件
9.5 路由框架的设计
9.6 路由参数的传递和接收
9.7 为何需要依赖注入
9.8 Activity 属性注入
9.9 路由开源库的使用

关于 apt 实践与总结开源库地址
https://github.com/yangchong2…
00. 注解系列博客汇总
0.1 注解基础系列博客

01.Annotation 注解详细介绍
1.Annotation 库的简单介绍
2.@Nullable 和 @NonNull
3. 资源类型注释
4. 类型定义注释
5. 线程注释
6.RGB 颜色纸注释
7. 值范围注释
8. 权限注释
9. 重写函数注释
10. 返回值注释
11.@Keep 注释
12.@SuppressWarnings 注解
13. 其他

[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 功能;简单应用了编译器注解,通过注解实现了防暴力点击的功能,同时支持设置时间间隔;使用注解替代枚举;使用注解一步步搭建简单路由案例。结合相应的博客,在来一些小案例,从此应该对注解有更加深入的理解……

0.2 注解系列博客问题答疑

13.0.0.1 什么是注解?系统内置的标准注解有哪些?SuppressWarnings 用过没?Android 中提供了哪些与线程相关的注解?
13.0.0.2 什么是 apt?apt 的难点和优势?什么是注解处理器?抽象处理器中四个方法有何作用?annotationProcessor 和 apt 区别?
13.0.0.3 注解是怎么分类的?自定义注解又是怎么分类的?运行期注解原理是什么?实际注解案例有哪些?
13.0.0.4 在自定义注解中,Annotation 里面的方法为何不能是 private?Annotation 里面的方法参数有哪些?
13.0.0.5 @Inherited 是什么意思?注解是不可以继承的,这是为什么?注解的继承这个概念该如何理解?
13.0.0.6 什么是依赖注入?依赖注入案例举例说明,有哪些方式,具备什么优势?依赖查找和依赖注入有什么区别?
13.0.0.7 路由框架为何需要依赖注入,不用的话行不行?路由用什么方式注入,这些注入方式各具何特点,为何选择注解注入?
13.0.0.8 实际开发中使用到注解有哪些,使用注解替代枚举?如何通过注解限定传入的类型?为何说枚举损耗性能?

01. 什么是 apt

什么是 apt
APT,就是 Annotation Processing Tool 的简称,就是可以在代码编译期间对注解进行处理,并且生成 Java 文件,减少手动的代码输入。注解我们平时用到的比较多的可能会是运行时注解,比如大名鼎鼎的 retrofit 就是用运行时注解,通过动态代理来生成网络请求。编译时注解平时开发中可能会涉及的比较少,但并不是说不常用,比如我们经常用的轮子 Dagger2, ButterKnife, EventBus3 都在用,所以要紧跟潮流来看看 APT 技术的来龙去脉。

编译时注解。
也有人叫它代码生成,其实他们还是有些区别的,在编译时对注解做处理,通过注解,获取必要信息,在项目中生成代码,运行时调用,和直接运行手写代码没有任何区别。而更准确的叫法:APT – Annotation Processing Tool

大概原理
Java API 已经提供了扫描源码并解析注解的框架,开发者可以通过继承 AbstractProcessor 类来实现自己的注解解析逻辑。APT 的原理就是在注解了某些代码元素(如字段、函数、类等)后,在编译时编译器会检查 AbstractProcessor 的子类,并且自动调用其 process() 方法,然后将添加了指定注解的所有代码元素作为参数传递给该方法,开发者再根据注解元素在编译期输出对应的 Java 代码

02.annotationProcessor 和 apt 区别

annotationProcessor 和 apt 区别

Android 官方的 annotationProcessor 同时支持 javac 和 jack 编译方式,而 android-apt 只支持 javac 方式。当然,目前 android-apt 在 Android Gradle 插件 2.2 版本上面仍然可以正常运行,如果你没有想支持 jack 编译方式的话,可以继续使用 android-apt。
目前比如一些常用框架 dagger2,butterKnife,ARouter 等,都支持 annotationProcessor

什么是 jack 编译方式?
Jack (Java Android Compiler Kit) 是新的 Android 编译工具,从 Android 6.0 开始加入, 替换原有的编译工具,例如 javac, ProGuard, jarjar 和 dx。它主要负责将 java 代码编译成 dex 包,并支持代码压缩,混淆等。

Jack 工具的主要优势

完全开放源码,源码均在 AOSP 中,合作伙伴可贡献源码
加快编译源码,Jack 提供特殊的配置,减少编译时间:pre-dexing, 增量编译和 Jack 编译服务器.
支持代码压缩,混淆,重打包和 multidex,不在使用额外单独的包,例如 ProGuard。

03. 项目目录结构

项目目录结构如图:

app:Demo
AptAnnotation:java Library 主要放一些项目中需要用到的自定义注解及相关代码
AptApi:Android Library. 是我们真正对外发布并交由第三方使用的库,它引用了 apt-jar 包
AptCompiler:java Library 主要是应用 apt 技术处理注解,生成相关代码或者相关源文件,是核心所在。

04. 该案例作用

前期仅仅是为了学习,同时先让 demo 运行起来,虽然网上很多讲解 apt 的博客写的很详细,但是还是有必要结合实际案例练习一下。

使用 apt 实现点击事件【编译期注解生成代码】
在一定时间内,按钮点击事件只能执行一次。未到指定时间,不执行点击事件。

使用 apt 实现 setContentView 功能【运行期注解案例】
使用简单的注解,便可以设置布局,等效于 setContentView(R.layout.activity_main)

使用 apt 实现路由【综合型案例】
比较全面的介绍从零起步,一步一步封装简易的路由开源库。一共用 10 篇博客记录了大部分的过程,想要更加深入了解,欢迎 clone 该项目。

05. 使用说明

如下所示
@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”);
}

06. 编译期注解生成代码

如下所示,在 app/build/generated/source/apt/debug/MainActivity$$_Once_Proxy 目录下
// 编译生成的代码,不要修改
// 更多内容: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);
}
}});
}
}

}

07. 运行期注解案例

首先先定义自定义注解
//@Retention 用来修饰这是一个什么类型的注解。这里表示该注解是一个运行时注解。
@Retention(RetentionPolicy.RUNTIME)
//@Target 用来表示这个注解可以使用在哪些地方。比如:类、方法、属性、接口等等。
// 这里 ElementType.TYPE 表示这个注解可以用来修饰:Class, interface or enum declaration。
// 当你用 ContentView 修饰一个方法时,编译器会提示错误。
@Target({ElementType.TYPE})
// 这里的 interface 并不是说 ContentView 是一个接口。
// 就像申明类用关键字 class。申明枚举用 enum。申明注解用的就是 @interface。
public @interface ContentView {
// 返回值表示这个注解里可以存放什么类型值。
int value();
}

然后需要在 activity 中做注解解析
@SuppressLint(“Registered”)
public class ContentActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 注解解析
// 遍历所有的子类
for (Class c = this.getClass(); c != Context.class; c = c.getSuperclass()) {
assert c != null;
// 找到修饰了注解 ContentView 的类
ContentView annotation = (ContentView) c.getAnnotation(ContentView.class);
if (annotation != null) {
try {
// 获取 ContentView 的属性值
int value = annotation.value();
// 调用 setContentView 方法设置 view
this.setContentView(value);
} catch (RuntimeException e) {
e.printStackTrace();
}
return;
}
}
}
}

关于如何使用,注意你写的 Activity 需要实现 ContentActivity,才能让注解生效
@ContentView(R.layout.activity_four)
public class FourActivity extends ContentActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
findViewById(R.id.tv_1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(FourActivity.this,” 运行期注解 ”,Toast.LENGTH_SHORT).show();
}
});
}
}

09. 使用注解搭建路由 [综合案例]

9.1 ARouter 路由解析
比较详细地分析了阿里路由库

9.1 搭建路由条件
为何需要路由?实现路由方式有哪些,这些方式各有何优缺点?使用注解实现路由需要具备的条件以及简单原理分析……

9.2 通过注解去实现路由跳转
自定义 Router 注解,Router 注解里有 path 和 group,这便是仿照 ARouter 对路由进行分组。然后看看注解生成的代码,手写路由跳转代码。

9.3 自定义路由 Processor 编译器
Processor 介绍,重要方法,Element 的作用,修饰方法的注解和 ExecutableElement

9.4 利用 apt 生成路由映射文件

在 Activity 类上加上 @Router 注解之后,便可通过 apt 来生成对应的路由表,那么究竟是如何生成的代码呢?
在组件化开发中,有多个 module,为何要在 build.gradle 配置 moduleName,又是如何通过代码拿到 module 名称?
process 处理方法如何生成代码的,又是如何写入具体的路径,写入文件的?
看完这篇文章,应该就能够理解上面这些问题呢!

9.5 路由框架的设计和初始化

编译期是在你的项目编译的时候,这个时候还没有开始打包,也就是你没有生成 apk 呢!路由框架在这个时期根据注解去扫描所有文件,然后生成路由映射文件。这些文件都会统一打包到 apk 里,app 运行时期做的东西也不少,但总而言之都是对映射信息的处理,如执行执行路由跳转等。那么如何设计框架呢?
生成的注解代码,又是如何把这些路由映射关系拿到手,或者说在什么时候拿到手比较合适?为何注解需要进行初始化操作?
如何得到得到路由表的类名,如何得到所有的 routerAddress—activityClass 映射关系?

[9.6 路由框架设计注意要点]()
需要注意哪些要点?

9.7 为何需要依赖注入
有哪些注入的方式可以解耦,你能想到多少?路由框架为何需要依赖注入?路由为何用注解进行依赖注入,而不是用反射方式注入,或者通过构造方法注入,或者通过接口方式注入?

9.8 Activity 属性注入
在跳转页面时,如何传递 intent 参数,或者如何实现跳转回调处理逻辑?

9.9 路由开源库的使用

不带参数直接跳转
@Router(path = Path.six)
public class SixActivity extends AppCompatActivity {

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_six);
}

}

ARouter.getsInstance().build(Path.six)
.navigation(MainActivity.this, new NavigationCallback() {
@Override
public void onFound(Postcard postcard) {
Log.e(“NavigationCallback”,” 找到跳转页面 ”);
}

@Override
public void onLost(Postcard postcard) {
Log.e(“NavigationCallback”,” 未找到 ”);
}

@Override
public void onArrival(Postcard postcard) {
Log.e(“NavigationCallback”,” 成功跳转 ”);
}
});

带参数跳转
@Router(path = Path.five)
public class FiveActivity extends AppCompatActivity {

@Extra
String title;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_five);
// 添加这行代码,实际上就是自动生成了下面获取参数值的代码
ARouter.getsInstance().inject(this);
// 如果不添加插入注解,则可以直接用下面的代码。
//Intent intent = getIntent();
//String title = intent.getStringExtra(“title”);
Toast.makeText(this, “title=” + title, Toast.LENGTH_SHORT).show();
}

}

Bundle bundle = new Bundle();
bundle.putString(“title”,” 标题 ————-“);
ARouter.getsInstance()
.build(Path.five)
.withBundle(bundle)
.navigation();

路由注解生成的代码位置

10. 其他说明
00. 参考案例

https://www.jianshu.com/p/335…
https://www.jianshu.com/p/200…
https://github.com/joyrun/Act…
https://github.com/BaronZ88/R…
https://github.com/alibaba/AR…
https://github.com/Xiasm/Easy…
https://github.com/chenenyu/R…
https://www.jianshu.com/p/8a3…
https://www.jianshu.com/p/e2d…

01. 关于博客汇总链接

1. 技术博客汇总

2. 开源项目汇总

3. 生活博客汇总

4. 喜马拉雅音频汇总

5. 其他汇总

02. 关于我的博客

我的个人站点:www.yczbj.org,www.ycbjie.cn
github:https://github.com/yangchong211

知乎:https://www.zhihu.com/people/…

简书:http://www.jianshu.com/u/b7b2…

csdn:http://my.csdn.net/m0_37700275

喜马拉雅听书:http://www.ximalaya.com/zhubo…

开源中国:https://my.oschina.net/zbj161…

泡在网上的日子:http://www.jcodecraeer.com/me…

邮箱:yangchong211@163.com
阿里云博客:https://yq.aliyun.com/users/a… 239.headeruserinfo.3.dT4bcV
segmentfault 头条:https://segmentfault.com/u/xi…

掘金:https://juejin.im/user/593943…

关于 apt 实践与总结开源库地址
https://github.com/yangchong2…

正文完
 0