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实践与总结开源库地址
00.注解系列博客汇总
0.1 注解基础系列博客
- 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区别
03.项目目录结构
-
项目目录结构如图:
- app:Demo
- AptAnnotation:java Library主要放一些项目中需要用到的自定义注解及相关代码
- AptApi:Android Library. 是我们真正对外发布并交由第三方使用的库,它引用了apt-jar包
- AptCompiler:java Library主要是应用apt技术处理注解,生成相关代码或者相关源文件,是核心所在。
04.该案例作用
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.参考案例
01.关于博客汇总链接
02.关于我的博客
关于apt实践与总结开源库地址