Android Apt之Activity Route的示例
作者:三十二蝉 发布时间:2022-03-31 17:05:18
前言
什么是Apt
APT从原理上讲是一个编译期的注解处理工具(Annotation Processing Tool)。一些主流的三方库(ButterKnife,Glide)都用到了这个技术来生成代码。
Apt有什么好处
自动生成模板代码,提高了开发效率
编译期对注解的处理,相对于运行期对注解的处理,性能上要好的多。
Gradle脚本中的apt和annotationProcessor
这两个从广义上说都是编译期的注解处理工具。只不过android-apt(其实是一个gradle插件,apt是插件命令)是早期的github的一个开源项目,annotationProcessor是gradle build tools 2.2之后自带的编译期注解工具(官方支持的,可替代开源的gradle插件android-apt)。android-apt的作者已经发表声明表示Android Studio插件已经支持annotationProcessor,并且会警告和阻止使用android-apt。总的来说,看你的gradle build tools的版本,低版本用android-apt(需要引入插件),高版本用annotationProcessor(无需引入插件)
代码设计
需求分析
这里将route模块分成三部分(一个android library,两个java library)
1、router-annotation(java library)
这里java工程里面只放注解的声明类。这里只实现了两个注解RouterActivity、RouterField。
2、router-compiler (java library)
这个工程是编译期依赖的工程,作用是编译期扫描代码,根据RouterActivity、RouterField这两个注解的使用,生成相关代码。这里需要讲下如何扫描代码并且生成代码的。这部分功能的实现主要依赖两个库:Google的auto-service(扫描代码),Squareup的javapoet(生成代码)
3、router (android library)
主要逻辑代码。在这个模块中会定义一些功能类和接口。router-compiler模块可以根据这些接口和功能类generate逻辑代码。需要注意的是router-compiler是不需要依赖router的,router-compiler是根据包名+类名的方式获取类的。
代码实现
router-annotation
RouterActivity是一个注解,用此注解修饰的Activity根据指定的路由地址,会自动添加到路由表中,当系统挂载了路由表之后,就可根据指定的路由地址来访问特定的Activity了。代码如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface RouterActivity {
String[] value();
}
这里Activity可用多个路由地址修改。
RouterField是一个用于表示Activity跳转时参数传递的注解,用这个注解修饰的成员变量,表示为接收Intent参数的变量。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RouterField {
String[] value();
}
router-compiler
这个模块只包含一个类RouterProcessor,这个类的大致结构如下:
//此处用AutoService注解,就可实现编译期自动扫描代码
@AutoService(Processor.class)
public class RouterProcessor extends AbstractProcessor{
private Elements elementUtils;
private String targetModuleName = "";
@Override
public Set<String> getSupportedAnnotationTypes() {
//支持的注解类型
return Collections.singleton(RouterActivity.class.getCanonicalName());
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//处理代码扫描结果的关键函数
...
return true;
}
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
//在扫描代码之前可从build.gradle中读取一些配置项
}
@Override
public SourceVersion getSupportedSourceVersion() {
//表示支持的Jdk版本
return SourceVersion.RELEASE_7;
}
}
下面分别讲解一下函数的实现:
1、init函数
我们的项目大多都是多module的形式,这时候我们就需要为每个module创建一个Activity路由注册表,然后在Application初始化的时候将所有的路由注册表挂载上,达到Activity路由跳转的目的。这里我们在init函数中,配置每个模块路由表的前缀名称。
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
elementUtils = processingEnvironment.getElementUtils();
Map<String, String> map = processingEnvironment.getOptions();
Set<String> keys = map.keySet();
for (String key: keys) {
if ("targetModuleName".equals(key)) {
this.targetModuleName = map.get(key);
}
System.out.println(key + " + " + map.get(key));
并在module的build.gradle文件下配置如下代码:
apt {
arguments {
targetModuleName 'moduleName'
}
}
2、process函数
这个函数的大致流程如下:找到所有被RouterActivity修饰的Activity;实现router模块中的RouterInitializer接口,将每个Activity的路由地址加入路由表中;同时为每个Activity创建一个XXXActivityHelper(用于更友好的Activity调整),并将每个XXXAcitivyHelper放入RouterHelper中,提供get方法获取。process函数的具体实现,可详见项目源码(都是一些代码生成的语句,没有多少逻辑)。
router
RouterInitializer接口,用于每个module注册表的实现
ActivityHelper,封装了一些参数解析逻辑,更方便的Activity跳转
SafeBundle, 对Activity的参数进行了封装
Router, 路由核心类,支持url跳转,解析url,并实现跳转。
'RouterCenterActivity', 可被外部浏览器唤起的中转Activity(外面根据url scheme唤醒RouterCenterActivity,RouterCenterActivity分发路由地址)
代码使用
初始化Router
public class DemoApp extends Application {
@Override
public void onCreate() {
super.onCreate();
Router.init("demo"); //自定义scheme协议
}
}
Activity跳转
@RouterActivity({"main"})
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn_second).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
RouterHelper.getSecondActivityHelper().start(MainActivity.this);
}
});
}
}
@RouterActivity({"second"})
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
}
}
build目录生成的代码如下:
详细代码可查看:Github项目。
现阶段代码还不完善,后期会添加更多功能。
来源:https://www.jianshu.com/p/d363d2ac58a5
猜你喜欢
- 突然学到了,所以就放到博客上来共享一下,权当是学习日记吧。首先说明一下,数组是引用类型的,所以注意不要在复制时复制了地址而没有复制数值哦!其
- JAVA 枚举单例模式及源码分析的实例详解 单例模式的实现有很多种,网上也分析了
- 本文实例为大家分享了java实现登录窗口的具体代码,供大家参考,具体内容如下登录窗口主类package ccnu.paint;import
- 对于ApplicationListener使用Spring的应该也熟悉,因为这就是我们平时学习的观察者模式的实际代表。Spring基于Jav
- 目录@CachePut设置的key值无法与@CacheValue的值匹配缓存注解key的基本数据类型要求必须要统一Spring-Cache
- Java的外部类为什么不能使用private和protected进行修饰对于这个问题,一直没有仔细思考,今天整理一下:对于顶级类(外部类)来
- 一、基本概念:线程、进程1.1、进程与线程的具体介绍线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。若一个进程同
- session对象用于在会话范围内,记录每个客户端的访问状态,以便于跟踪每个客户端的操作状态,在会话存储的信息,在浏览器发出后续请求时可以获
- 网易Java程序员两轮面试题,请作答。part 1:网易JAVA程序员一面1.volatile有什么用?2.Minor GC和Full GC
- 什么事读写分离读写分离,基本的原理是让主数据库处理事务性增、改、删操作(INSERT、UPDATE、DELETE),而从数据库处理SELEC
- android原生的Spinner提供了下拉列表选项框,但在一些流行的APP中,原生的Spinner似乎不太受待见,而通常会有下图所示的下拉
- 之前使用的那台电脑有点旧了,稍微跑一下程序就报内存不够。本来想考虑入手一台带GPU的新电脑,在商品浏览里的时候,考虑到钱包不够厚实。就选了家
- 公司的研发管理平台实现了Gitlab+Kubernetes的Devops,在ToB和ToC场景中,由于用户量大,且预发布环境和生产环境或多或
- 1、Buffer的继承体系如上图所示,对于Java中的所有基本类型,都会有一个具体的Buffer类型与之对应,一般我们最经常使用的是Byte
- 在前面一篇Java Comparable和Comparator对比详解中,对于java中的排序方法进行比较和具体剖析,主要是针对 Compa
- 最近接触了Android自定义控件,涉及到自定义xml中得属性(attribute),其实也很简单,但是写着写着,发现代码不完美了,就是在a
- spring cloud 配置中心客户端启动先启动了配置中心,然后启动客户端,发现打印的日志是这样的2020-04-29 11:13:02.
- 介绍本次设计的是一个有33个按钮的科学计算器。可以进行加,减,乘,除,开根号,阶乘,次方,百分号,对数,三角函数的计算。实现思路通过点击按钮
- 在电商上购买商品后,如果在下单而又没有支付的情况下,一般提示30分钟完成支付,否则订单自动。比如在京东下单为完成支付:超过24小时,就会自动
- 1.嵌套函数业务开发中,我们可能会遇到这样一个场景:一个函数只会被某一处多次调用,且不想让这个函数在该类的其他地方调用,这个时候就需要对这个