改造Navigation
目标:
- 摒弃xml文件,用注解的方式管理路由节点。利用映射关系,动态生成路由节点配置文件
- 改造FragmentNavigator,替换replace(),使用show(),hint()方式,路由Fragement
自定义注解处理器
1. 配置
gradle配置
//生成Json文件工具类
api 'com.alibaba:fastjson:1.2.59'
//注解处理器配置工具
api 'com.google.auto.service:auto-service:1.0-rc6'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
如果想要注解处理器能够在编译器生成代码,需要做一个配置说明,这里有两种配置方法:
具体参考这篇文章:Java AbstractProcessor实现自定义ButterKnife
注解处理器基本用法
//auto.service:auto-service使用时要添加这个注解
@AutoService(Processor.class)
// 项目配置 当前正在使用的Java版本
@SupportedSourceVersion(SourceVersion.RELEASE_8)
//要处理的注解类型的名称(这里必须是完整的包名+类名
@SupportedAnnotationTypes({
"org.devio.hi.nav_annotation.Destination"})
public class NavProcessor extends AbstractProcessor {
@Override
void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
//处理器被初始化的时候被调用
}
boolean process(Set annotations, RoundEnvironment roundEnv)
//处理器处理自定义注解的地方
return false
}
注解处理器的引用
//Kotkin项目用 kapt Java项目用 annotationProcessor
kapt project(path:'nav-compiler')
api project(path:'nav-annotations')
下面会将用的方法做介绍, 关于更多注解处理器和相关知识,可参考这几篇文章:
Java AbstractProcessor实现自定义ButterKnife
2. 创建项目
这个工程会默认生成Navigation+BottomNavigationView项目结构。项目内容比较简单。这里不过多介绍。我们就改造这个项目。
创建两个Java lib :
为什么需要创建Java库? 创建Java库是因为在使用自定义AbstractProcessor需要使用到javax包中的相关类和接口,这个在android库中并不存在,所以需要使用到Java库。
在nav_compiler
module下的build.gradle:
dependencies {
implementation fileTree(dir: 'libs', includes: ['*,jar'])
//自定义注解处理器相关依赖
//Json工具类
api 'com.alibaba:fastjson:1.2.59'
//让自定义处理器在编译时 能够被唤醒 能够执行
api 'com.google.auto.service:auto-service:1.0-rc6'
//添加我们定义的注解lib依赖
implementation project(path: ':nav_annotation')
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
}
在nav_annotation
module下创建注解文件:
/**
* 自定义注解,将这个注解
* 注释到我们的要路由的类上面
* 这样我们就可以获取配置的节点(e.g Activity/Fragment/Dialog)
* 然后利用代码生成节点配置,替换掉nav_graph.xml;
*/
@Target(ElementType.TYPE)//类作用域
@Retention(RetentionPolicy.CLASS)//编译期生效
public @interface Destination {
/**
* 页面在路由中的名称
*/
String pareUrl();
/**
* 节点是不是默认首次启动页
*/
boolean asStarter() default false;
}
在这里我们有必要认识一下什么是Element。 在Java语言中,Element是一个接口,表示一个程序元素,它可以指代包、类、方法或者一个变量。Element已知的子接口有如下几种:
- PackageElement 表示一个包程序元素。提供对有关包及其成员的信息的访问。
- ExecutableElement 表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注释类型元素。
- TypeElement 表示一个类或接口程序元素。提供对有关类型及其成员的信息的访问。注意,枚举类型是一种类,而注解类型是一种接口。
- VariableElement 表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数。
注解解释器具体代码如下:
/**
* @Author :ggxz
* @Date: 2022/3/5
* @Desc:
*/
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes({
"org.devio.hi.nav_annotation.Destination"})
public class NavProcessor extends AbstractProcessor {
private static final String PAGE_TYPE_ACTIVITY = "Activity";
private static final String PAGE_TYPE_FRAGMENT = "Fragment";
private static final String PAGE_TYPE_DIALOG = "Dialog";
private static final String OUTPUT_FILE_NAME = "destination.json";
private Messager messager;
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
//日志打印工具类
messager = processingEnv.getMessager();
messager.printMessage(Diagnostic.Kind.NOTE, "enter init...");
//创建打印文件
filer = processingEnv.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//获取代码中所有使用@Destination 注解的类或字段
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(Destination.class);
if (!elementsAnnotatedWith.isEmpty()) {
Map<String, JSONObject> destMap = new HashMap<>();
handleDestination(elementsAnnotatedWith, destMap, Destination.class);
try {
//创建资源文件
FileObject resource = filer.createResource(StandardLocation.CLASS_OUTPUT, "", OUTPUT_FILE_NAME);
// 获取创建资源文件默认路径: .../app/build/intermediates/javac/debug/classes/目录下
// 希望存放的目录为: /app/main/assets/
String resourcePath = resource.toUri().getPath();
// 获取 .../app 之前的路径
String appPath = resourcePath.substring(0, resourcePath.indexOf("app") + 4);
String assetsPath = appPath + "src/main/assets"