ARouter系列四:APT实战 之 仿ARouter页面路由
本篇文章的目是:为了熟悉APT的相关开发。特此仿 ARouter 的 apt 生成功能进行代码实践。实战代码中只仿@Route的部分功能:Activity 路由。
如果对于 ARouter 不是很了解的,建议先行阅读:
ARouter系列一:Activity跳转原理详解
ARouter系列二:@Autowired属性注入
ARouter系列三:依赖注入暴露服务
新建空的 Android 项目,结构如下:
项目结构router
:定义了Router的核心库
router-compiler
:定义了 apt 的处理逻辑
router-annotation
:定义了相关注解
app
:使用注解进行相关测试
一、router-annotation
定义了注解:Route
,我们进行了简化,只保留 path
和 group
的设置。
package com.daddyno1.router_annotation;
...
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Route {
String path();
String group() default "";
}
二、router
此模块定义是两个接口:IRouteGroup
、 IRouteRoot
、 以及一个简化版的类:RouteMeta
。我们是为了练习 apt 其余的都忽略(如果到这里看不懂了,请先读一读文章开始的三篇文章)。
package com.daddyno1.router;
public interface IRouteGroup {
void loadInto(Map<String, RouteMeta> map);
}
package com.daddyno1.router;
public interface IRouteRoot {
void loadInto(Map<String, Class<? extends IRouteGroup>> map);
}
package com.daddyno1.router;
public class RouteMeta {
public Class destination;
public String path;
public String group;
public static RouteMeta build(Class destination, String path, String group) {
RouteMeta routeMeta = new RouteMeta();
routeMeta.destination = destination;
routeMeta.path = path;
routeMeta.group = group;
return routeMeta;
}
}
三、router-compiler
定义了一个 RouterProcessor
注解处理器实现类,及一些工具类。因为涉及 源代码辅助类的生成,所以使用了 javapoet,如果不熟悉的请先行了解。
package com.daddyno1.router_compiler.utils;
public class Consts {
public static final String KEY_MODULE_NAME = "ROUTER_MODULE_NAME";
public static final String CLASS_OF_ROUTE_GROUP = "Router$$Group$$";
public static final String CLASS_OF_ROOT_GROUP = "Router$$Root$$";
public static final String PACKAGE_OF_ROUTE = "com.route.";
}
package com.daddyno1.router_compiler.utils;
public class Utils {
public static Messager messager;
public static void logger(CharSequence s){
messager.printMessage(Diagnostic.Kind.NOTE, s);
}
}
package com.daddyno1.router_compiler;
...
import static com.daddyno1.router_compiler.utils.Consts.*;
import static com.daddyno1.router_compiler.utils.Utils.*;
/**
* 定义注解处理器,注解处理器是以 module 为单位进行处理。
*/
@AutoService(Processor.class)
public class RouterProcessor extends AbstractProcessor {
// Map<group, Map<path, Element>>
private Map<String, Map<String, Element>> groups = new HashMap<>();
// module 名字
private String moduleName = null;
private Filer filer; //文件工具类
private Elements elementUtils; // Element工具类
private Types typeUtils; //Type 工具类. 如:判断是否同一个类型,是否子类型等等
//设定注解处理器支持的 源代码版本
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
//设定 注解处理器 支持的注解类型
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
for (Class<? extends Annotation> annotation : getSupportedAnnotations()) {
types.add(annotation.getCanonicalName());
}
return types;
}
private Set<Class<? extends Annotation>> getSupportedAnnotations() {
Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>();
annotations.add(Route.class);
return annotations;
}
// 设定 注解处理器 接收的配置参数集合
@Override
public Set<String> getSupportedOptions() {
Set<String> ops = new LinkedHashSet<>();
ops.add(KEY_MODULE_NAME);
return ops;
}
//初始化方法,初始化一些工具类等相关行为
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
Utils.messager = processingEnvironment.getMessager();
filer = processingEnvironment.getFiler();
elementUtils = processingEnvironment.getElementUtils();
typeUtils = processingEnvironment.getTypeUtils();
//尝试获取用户参数 moduleName
Map<String, String> options = processingEnvironment.getOptions();
moduleName = options.get(KEY_MODULE_NAME);
//不为空打印一下module名字
if (!StringUtils.isEmpty(moduleName)) {
logger("module --> " + moduleName);
} else {
//抛出异常让用户去处理
throw new RuntimeException("Router::Compiler>> 没有配置 moduleName,请在 build.gradle中进行配置");
}
}
/**
* 进行注解处理
*
* @param set 一个集合,包含了所有需要处理的注解的集合。
* @param roundEnvironment 本回合的上下文环境
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
messager.printMessage(Diagnostic.Kind.NOTE, "---start---");
//测试 有哪些数据
// for (TypeElement typeElement : set) {
// logger(typeElement.getQualifiedName());
// }
// Set<? extends Element> eles = roundEnvironment.getElementsAnnotatedWith(Route.class);
// for (Element element : eles) {
// Route routeAnn = element.getAnnotation(Route.class);
// logger(element.getSimpleName());
// logger(element.asType().toString());
// logger(routeAnn.path());
// logger(routeAnn.group());
// }
// 需要处理的注解结合不为空才进行解析
if (!CollectionUtils.isEmpty(set)) {
//获取所有被 @Route 注解的Element
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Route.class);
//解析 Element 集合
parseRoute(elements);
}
return false;
}
/**
* 解析所有注解的节点
*/
private void parseRoute(Set<? extends Element> elements) {
if (CollectionUtils.isNotEmpty(elements)) {
logger(moduleName + "模块发现路由,个数为:" + elements.size());
//获取 RouteMeta、IRouteGroup、IRouteRoot 类型
TypeElement type_RouteMeta = elementUtils.getTypeElement("com.daddyno1.router.RouteMeta");
TypeElement type_IRouteGroup = elementUtils.getTypeElement("com.daddyno1.router.IRouteGroup");
TypeElement type_IRouteRoot = elementUtils.getTypeElement("com.daddyno1.router.IRouteRoot");
/**
* 扫描节点,获取有用信息,构建相关集合
*/
for (Element element : elements) {
Route annotation = element.getAnnotation(Route.class);
String path = annotation.path();
String group = getGroupFromAnnotation(annotation);
logger("path: " + path);
logger("group: " + group);
Map<String, Element> routes = groups.get(group);
if (routes == null) {
routes = new HashMap<>();
groups.put(group, routes);
}
routes.put(path, element);
}
logger(groups.toString());
/**
* public class Router$$Group$$group2 implements IRouteGroup {
* @Override
* public void loadInto(Map<String, RouteMeta> routes) {
* routes.put("/group2/second", RouteMeta.build(SecondActivity.class, "/group2/second", "group2"));
* }
* }
*/
//////////////////创建 Router$$Group$$ + group 类
//创建参数类型 Map<String, RouteMeta>
ParameterizedTypeName inputMapTypeOfGroup = ParameterizedTypeName.get(
ClassName.get(Map.class),
ClassName.get(String.class),
ClassName.get(type_RouteMeta)
);
//创建参数 routes
ParameterSpec groupParamSpec = ParameterSpec.builder(inputMapTypeOfGroup,
"routes").build();
// 循环每个组,分别创建不同的 路由组类
for (Map.Entry<String, Map<String, Element>> mapEntry : groups.entrySet()) {
String group = mapEntry.getKey();
Map<String, Element> routes = mapEntry.getValue();
//创建方法 loadInto
MethodSpec.Builder loadIntoMethodOfGroup = MethodSpec.methodBuilder("loadInto")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.addParameter(groupParamSpec);
//循环所有路由表插入
for (Map.Entry<String, Element> typeMirrorEntry : routes.entrySet()) {
String path = typeMirrorEntry.getKey();
Element element = typeMirrorEntry.getValue();
loadIntoMethodOfGroup.addStatement("routes.put($S, $T.build( $T.class, $S, $S))",
path,
type_RouteMeta,
element,
path,
group);
/**
* Element 是所有被注解注释的节点类。(apt api) 包含:
* PACKAGE,
* ENUM,
* CLASS,
* ANNOTATION_TYPE,
* INTERFACE,
* ENUM_CONSTANT,
* FIELD,
* PARAMETER,
* LOCAL_VARIABLE,
* EXCEPTION_PARAMETER,
* METHOD,
* CONSTRUCTOR,
* STATIC_INIT,
* INSTANCE_INIT,
* TYPE_PARAMETER,
* OTHER,
* RESOURCE_VARIABLE;
*
* TypeElement 是 Element 的子类,类型的节点类。(apt api) 如:CLASS、ENUM、INTERFACE
*
* TypeMirror (apt api) 是 Element 这个节点的一种类型表达方式。 可通过 Element#asType 获取。因为TypeMirror 可以从Element 中获取
* 所以一般可以 传 Element 的地方,也有 TypeMirror 参数的方法重载。
*
* Type 是一个接口。(java reflect api) Class 类实现了 Type。如果参数是Type类型的,代表可以传入任何类的Class对象。
*
* TypeName/ClassName (javaPoet api) 是Java 类型的表达方式。如:BOOLEAN 、INT 等等,当然也可以是任何类型。
* ClassName 继承自 TypeName。可以通过 (package + name) 、 Type 、 TypeElement 获得。
*
*/
/**
* $T : TypeName/ClassName 、TypeMirror、 Element/TypeElement、Type
*
* if (o instanceof TypeName) return (TypeName) o;
* if (o instanceof TypeMirror) return TypeName.get((TypeMirror) o);
* if (o instanceof Element) return TypeName.get(((Element) o).asType());
* if (o instanceof Type) return TypeName.get((Type) o);
*
*/
/**
* $N: Object、ParameterSpec、FieldSpec、MethodSpec、TypeSpec
*
* if (o instanceof CharSequence) return o.toString();
* if (o instanceof ParameterSpec) return ((ParameterSpec) o).name;
* if (o instanceof FieldSpec) return ((FieldSpec) o).name;
* if (o instanceof MethodSpec) return ((MethodSpec) o).name;
* if (o instanceof TypeSpec) return ((TypeSpec) o).name;
*/
}
//Router$$Group$$ + group
TypeSpec typeOfGroup = TypeSpec.classBuilder(CLASS_OF_ROUTE_GROUP + group)
.addModifiers(Modifier.PUBLIC)
.addSuperinterface(type_IRouteGroup.asType())
.addMethod(loadIntoMethodOfGroup.build())
.build();
JavaFile javaFileOfGroup = JavaFile.builder("com.route", typeOfGroup)
.build();
try {
javaFileOfGroup.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* public class Router$$Root$$app implements IRouteRoot {
* @Override
* public void loadInto(Map<String, Class<? extends IRouteGroup>> routeGroups) {
* routeGroups.put("group2", Router$$Group$$group2.class);
* routeGroups.put("group1", Router$$Group$$group1.class);
* }
* }
* }
*/
//////////////////创建 Router$$Root$$ + module 类
//创建参数类型 Map<String, Class<? extends IRouteGroup>>
ParameterizedTypeName inputMapTypeOfRoot = ParameterizedTypeName.get(ClassName.get(Map.class),
ClassName.get(String.class),
ParameterizedTypeName.get(
ClassName.get(Class.class),
WildcardTypeName.subtypeOf(ClassName.get(type_IRouteGroup))
));
//创建参数
ParameterSpec rootParamSpec = ParameterSpec.builder(inputMapTypeOfRoot,
"routeGroups")
.build();
MethodSpec.Builder builder = MethodSpec.methodBuilder("loadInto")
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.addParameter(rootParamSpec);
for (Map.Entry<String, Map<String, Element>> mapEntry : groups.entrySet()) {
String group = mapEntry.getKey();
builder.addStatement("routeGroups.put($S, $L.class)", group, CLASS_OF_ROUTE_GROUP + group);
}
TypeSpec typeOfRoot = TypeSpec.classBuilder(CLASS_OF_ROOT_GROUP + moduleName)
.addSuperinterface(type_IRouteRoot.asType())
.addModifiers(Modifier.PUBLIC)
.addMethod(builder.build())
.build();
JavaFile javaFileOfRoot = JavaFile.builder("com.route", typeOfRoot)
.build();
try {
javaFileOfRoot.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 从注解中获取 group 信息
*/
private String getGroupFromAnnotation(Route annotation) {
String group = annotation.group();
if (StringUtils.isNotEmpty(group)) {
return group;
}
group = getGroupFromPath(annotation.path());
return group;
}
//从 path 中获取 group信息
private String getGroupFromPath(String path) {
String group;
if (StringUtils.isEmpty(path)) {
throw new RuntimeException("module: " + moduleName + ". route path : [" + path + "] is empty;");
}
if (!path.startsWith("/")) {
throw new RuntimeException("module: " + moduleName + ". route path : [" + path + "] is must start with /");
}
int index = path.indexOf("/", 1);
if (index == -1) {
throw new RuntimeException("module: " + moduleName + ". route path : [" + path + "] must contains at lease two /");
}
group = path.substring(1, path.indexOf("/", index));
return group;
}
}
其实 apt 不难,主要是对里边的一些概念需要有些了解,具体的代码处理逻辑代码添加了注释。主要干得事情是,通过APT编译时获取 使用了 @Route 注解的 Element 节点信息,通过这些信息生成辅助的处理文件。
四、app
定义四个Activity:MainActivity
、SecondActivity
、ThirdActivity
、FourthActivity
@Route(path = "/group1/main")
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
@Route(path = "/group2/second")
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
}
}
@Route(path = "/group1/activity")
public class ThirdActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_third);
}
}
@Route(path = "/group1/hello")
public class FourthActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fourth);
}
}
编译一下项目,我们看一下结果:
然后看一下 生成的辅助类在ap_generated_sources
目录下。我们生成的辅助类目前的包名是:com.route
生成的辅助类查看:
package com.route;
import com.daddyno1.router.IRouteGroup;
import com.daddyno1.router.IRouteRoot;
import java.lang.Class;
import java.lang.Override;
import java.lang.String;
import java.util.Map;
public class Router$$Root$$app implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routeGroups) {
routeGroups.put("group2", Router$$Group$$group2.class);
routeGroups.put("group1", Router$$Group$$group1.class);
}
}
package com.route;
...
public class Router$$Group$$group1 implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> routes) {
routes.put("/group1/activity", RouteMeta.build( ThirdActivity.class, "/group1/activity", "group1"));
routes.put("/group1/main", RouteMeta.build( MainActivity.class, "/group1/main", "group1"));
routes.put("/group1/hello", RouteMeta.build( FourthActivity.class, "/group1/hello", "group1"));
}
}
package com.route;
...
public class Router$$Group$$group2 implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> routes) {
routes.put("/group2/second", RouteMeta.build( SecondActivity.class, "/group2/second", "group2"));
}
}
完美的按照我们的意愿创建了指定 辅助文件。 apt 只是工具,核心是按照我们的规则套路生成辅助类,以供 route sdk 使用。