博客主页
上一篇中讲解了 类加载 、 全局Map记录 实现组件化模块之间的交互,慢慢衍生APT技术。
那么在组件化架构中,我们需要思考通过APT+javapoet技术生成什么样的类文件呢?
从组件化路由架构设计图需要思考,使用APT生成文件为什么需要分组? 生成这些文件干什么用?
设计思路:在初始化只加载分组表数据,在使用的时候,如果使用A分组就去加载A分组下的所有路由信息,而不去加载B分组数据。比如:A组有100个路由信息,B组有200个路由信息,如果不分组,Map中就需要加载300个路由信息,当用户可能根本就不需要进入B分组页面,加载B分组的路由信息,会导致内存和时间的浪费。
todo_modular项目:使用路由组件化项目
RouteMeta路由信息封装类,路由地址,路由分组名,原始的类元素等。
import javax.lang.model.element.Element; public class RouteMeta { public enum RouteType { ACTIVITY } // 路由类型,支持Activity private RouteType type; // 原始的类元素 private Element element; // 注解使用的类对象 private Class<?> clazz; // 路由地址 private String path; // 路由分组名 private String group; }
思考:为什么Element类节点需要存起来?
因为要在注解处理器中,循环拿到每个类节点,方便赋值和调用。Element它是javax.lang包下的,不属于Android Library。
Map<String, Class<? extends IRouteGroup>>
key: 组名,如:app , value:该组名下所有的路径文件名,如: ARouter$$Group$$app.class
注意:需要生成路由路径文件后 ARouter$$Group$$app.class
,才能生成路由组文件,使用接口方式容易扩展
/** * 路由组加载数据接口 */ public interface IRouteRoot { /** * 加载路由组数据 * 如:key:"app", value: ARouter$$Group$$app.class(实现了IRouteGroup接口) */ void loadInto(Map<String, Class<? extends IRouteGroup>> routes); }
Map<String, RouteMeta>
key:路径名,如:"/app/MainActivity", value:该路径名下对应的路由信息
OOP思想,让单纯的targetClass变成更灵活的RouteMeta对象,接口方式容易扩展
/** * 路由组对应的详细Path路径加载数据接口 * 如:app组下对应有哪些类需要加载 */ public interface IRouteGroup { /** * 加载路由组中的Path详细数据 * 如:key:"/app/MainActivity", value:MainActivity信息封装到RouteMeta对象中 */ void loadInto(Map<String, RouteMeta> atlas); }
下面以购物模块shop为例,生成 ARouter$$Group$$shop
和 ARouter$$Root$$shop
文件,涉及到的技术点:APT + javapoet
在使用APT + javapoet 生成我们想要的代码之前,首先要设计好我们想要的代码模版,如: ARouter$$Root$$shop
分组的模版代码
public final class ARouter$$Root$$shop implements IRouteRoot { @Override public void loadInto(final Map<String, Class<? extends IRouteGroup>> routes) { routes.put("shop", ARouter$$Group$$shop.class);; } }
ARouter$$Group$$shop
该分组下的所有的路由信息代码模版
public class ARouter$$Group$$shop implements IRouteGroup { @Override public void loadInto(Map<String, RouteMeta> atlas) { atlas.put("/shop/ShopActivity", RouteMeta.build(RouteMeta.RouteType.ACTIVITY, ShopActivity.class, "/shop/ShopActivity", "shop"));; atlas.put("/shop/ShopDetailActivity", RouteMeta.build(RouteMeta.RouteType.ACTIVITY, ShopDetailActivity.class, "/shop/ShopDetailActivity", "shop"));; } }
为了生成上面的模版代码,首先第一个@Router注解。实现Activity跳转,就要用到该注解标记Activity,通过注解处理器处理这些注解,然后在编译器生成代码:
@Target(ElementType.TYPE) // 该注解作用在类之上 @Retention(RetentionPolicy.CLASS) // 要在编译时进行一些预处理操作。注解会在class文件中存在 public @interface Router { // 详细路由路径(必填),如:"app/MainActivity" String path(); // 路由组名(选填,如果不填写,可以从path中截取) String group() default ""; }
然后创建路由注解处理器,类名为ARouterProcessor,该类继承AbstractProcessor。注解处理器需要注册,借助AutoService可以帮助我们自动注册到META-INF中。同时还需要指定注解处理器支持的注解类型、JDK编译的版本、注解处理器接收的参数等。
// 允许/支持的注解类型,让注解处理器处理(新增annotation module) @SupportedAnnotationTypes({"com.example.modular.annotations.Router"}) // 指定JDK编译版本 @SupportedSourceVersion(SourceVersion.RELEASE_7) // 注解处理器接收的参数 @SupportedOptions({"moduleName"}) // AutoService则是固定的写法,加个注解即可 // 通过auto-service中的@AutoService可以自动生成AutoService注解处理器,用来注册 // 用来生成 META-INF/services/javax.annotation.processing.Processor 文件 @AutoService(Processor.class) public class ARouterProcessor extends AbstractProcessor { }
接下来实现process抽象方法,该方法相当于main方法,注解处理器的入口方法,用来处理被@Router标记的元素。
/** * 相当于main函数,开始处理注解 * 注解处理器的核心方法,处理具体的注解,生成Java文件 * * @param set 支持注释类型的集合,如:@ARouter注解 * @param roundEnvironment 当前或是之前的运行环境,可以通过该对象查找找到的注解 * @return true 表示后续处理器不会再处理(已经处理完成) */ @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { // set集合,就是支持的注解集合,如:ARouter注解 if (set.isEmpty()) return false; // 获取所有被@ARouter注解注释的元素 Set<? extends Element> elementsAnnotatedWithARouter = roundEnvironment.getElementsAnnotatedWith(Router.class); if (elementsAnnotatedWithARouter != null && !elementsAnnotatedWithARouter.isEmpty()) { try { parseElements(elementsAnnotatedWithARouter); } catch (IOException e) { messager.printMessage(Diagnostic.Kind.NOTE, "异常发生:" + e.getMessage()); } } return true; }
在处理被@Router注解标记的元素之前,需要初始化一些工具,重写AbstractProcessor类的init方法,通过processingEnvironment获取辅助工具,如:打印Log的工具、 文件生成工具等。
// 操作Element的工具类(如:类、函数、属性都是Element) private Elements elementUtils; // Messager用来报告错误,警告和其他提示信息 private Messager messager; // type(类信息)工具类,包含用于操作TypeMirror的工具方法 private Types typeUtils; // 文件生成器,类/资源,Filter用来创建新的源文件,class文件以及辅助文件 private Filer filer; // 模块名,通过getOptions获取build.gradle传过来 private String moduleName; // 缓存路由信息,key:组名 value:该组名下的所有路由信息 private Map<String, List<RouteMeta>> cacheRouteMetaMap = new HashMap<>(); // 缓存分组的信息,key:组名 value:该组名下对应的路由类名,如:Router$$Group$$shop private Map<String, String> cacheGroupMap = new HashMap<>(); /** * 该方法主要用于一些初始化工作,通过该方法的processingEnvironment参数可以获取一些工具 */ @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); elementUtils = processingEnvironment.getElementUtils(); messager = processingEnvironment.getMessager(); typeUtils = processingEnvironment.getTypeUtils(); filer = processingEnvironment.getFiler(); messager.printMessage(Diagnostic.Kind.NOTE, "<<<<< ARouterProcessor::init >>>>>"); Map<String, String> options = processingEnvironment.getOptions(); if (options != null && !options.isEmpty()) { moduleName = options.get("moduleName"); messager.printMessage(Diagnostic.Kind.NOTE, "moduleName=" + moduleName); } if (Utils.isEmpty(moduleName)) { throw new RuntimeException("ARouter注解处理器传入moduleName参数不能为空,清查看build.gradle配置文件配置是否正确"); } }
解析所有被@Router注解标记的元素,将满足条件元素中路由信息封装到RouteMeta并缓存起来。用于生成分组文件和该组下的路由文件。
private void parseElements(Set<? extends Element> elements) throws IOException { TypeElement activityElement = elementUtils.getTypeElement("android.app.Activity"); TypeMirror activityMirror = activityElement.asType(); for (Element element : elements) { TypeMirror elementMirror = element.asType(); messager.printMessage(Diagnostic.Kind.NOTE, "遍历元素信息:" + elementMirror.toString()); // 遍历元素信息:com.example.modular.shop.ShopActivity // 通过类节点获取包节点,(全路径名,如:com.example.modular.shop) String packageName = elementUtils.getPackageOf(element).getQualifiedName().toString(); // 获取被@ARouter注解的简单类名 String simpleName = element.getSimpleName().toString(); // 注: 包名:com.example.modular.shop 被注解的类名:ShopActivity messager.printMessage(Diagnostic.Kind.NOTE, "包名:" + packageName + " 被注解的类名:" + simpleName); Router aRouter = element.getAnnotation(Router.class); RouteMeta routeMeta = new RouteMeta(element, aRouter.group(), aRouter.path()); // 判断第一类型是否是第二类型的子类型,如果是返回true if (typeUtils.isSubtype(elementMirror, activityMirror)) { // 满足条件:被@ARouter注解的元素是Activity的子类型 routeMeta.setType(RouteMeta.RouteType.ACTIVITY); } else { throw new RuntimeException("@ARouter注解目前只能应用与Activity类上"); } fillMapWithRouteMeta(routeMeta); } createGroupFile(); createRootFile(); }
fillMapWithRouteMeta方法用于缓存路由信息
// 缓存分组的信息,key:组名 value:该组名下对应的路由类名,如:Router$$Group$$shop private Map<String, String> cacheGroupMap = new HashMap<>(); private void fillMapWithRouteMeta(RouteMeta routeMeta) { if (checkRouterPath(routeMeta)) { messager.printMessage(Diagnostic.Kind.NOTE, "routeMeta>>>> " + routeMeta.toString()); List<RouteMeta> routeMetas = cacheRouteMetaMap.get(routeMeta.getGroup()); if (routeMetas == null) { routeMetas = new ArrayList<>(); cacheRouteMetaMap.put(routeMeta.getGroup(), routeMetas); } routeMetas.add(routeMeta); } else { messager.printMessage(Diagnostic.Kind.NOTE, "routeMeta检查失败,不满足规范"); } }
先创建路由文件,createGroupFile具体实现如下:
private void createGroupFile() throws IOException { if (cacheRouteMetaMap.isEmpty()) return; TypeElement routeGroupElement = elementUtils.getTypeElement("com.example.modular.api.IRouteGroup"); for (Map.Entry<String, List<RouteMeta>> entry : cacheRouteMetaMap.entrySet()) { ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get( ClassName.get(Map.class), ClassName.get(String.class), ClassName.get(RouteMeta.class) ); // Map<String, RouteMeta> atlas ParameterSpec atlasParameter = ParameterSpec.builder(parameterizedTypeName, "atlas").build(); // public void loadInto(Map<String, RouteMeta> atlas) MethodSpec.Builder loadIntoMethodBuilder = MethodSpec.methodBuilder("loadInto") .addAnnotation(Override.class) .addParameter(atlasParameter) .addModifiers(Modifier.PUBLIC); List<RouteMeta> routeMetas = entry.getValue(); for (RouteMeta routeMeta : routeMetas) { // atlas.put("/shop/ShopActivity", RouteMeta.build(RouteMeta.RouteType.ACTIVITY, ShopActivity.class, "/shop/ShopActivity", "shop")); loadIntoMethodBuilder.addStatement( "$N.put($S, $T.build($T.$L, $T.class, $S, $S));", "atlas", routeMeta.getPath(), ClassName.get(RouteMeta.class), ClassName.get(RouteMeta.RouteType.class), routeMeta.getType(), ClassName.get((TypeElement) routeMeta.getElement()), routeMeta.getPath(), routeMeta.getGroup() ); } MethodSpec loadIntoMethod = loadIntoMethodBuilder.build(); String groupName = entry.getKey(); String packageName = "com.example.android.arouter.routers"; String finalClassName = "ARouter$$Group$$" + groupName; messager.printMessage(Diagnostic.Kind.NOTE, "最终生成的路径的文件名:" + packageName + "." + finalClassName); // public class Router$$Group$$shop implements IRouteGroup TypeSpec finalClass = TypeSpec.classBuilder(finalClassName) .addMethod(loadIntoMethod) .addModifiers(Modifier.PUBLIC) .addSuperinterface(ClassName.get(routeGroupElement)) .build(); // 利用文件生成器生成文件 JavaFile.builder(packageName, finalClass).build().writeTo(filer); cacheGroupMap.put(groupName, finalClassName); } }
再创建分组文件,createRootFile方法具体实现如下:
private void createRootFile() throws IOException { if (cacheGroupMap.isEmpty()) return; TypeElement routeRootElement = elementUtils.getTypeElement("com.example.modular.api.IRouteRoot"); TypeElement routeGroupElement = elementUtils.getTypeElement("com.example.modular.api.IRouteGroup"); // Map<String, Class<? extends IRouteGroup>> ParameterizedTypeName parameterizedTypeName = ParameterizedTypeName.get( ClassName.get(Map.class), ClassName.get(String.class), ParameterizedTypeName.get(ClassName.get(Class.class), WildcardTypeName.subtypeOf(ClassName.get(routeGroupElement))) ); // Map<String, Class<? extends IRouteGroup>> routes ParameterSpec routesParameter = ParameterSpec.builder(parameterizedTypeName, "routes", Modifier.FINAL).build(); // public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) MethodSpec.Builder loadIntoMethodBuilder = MethodSpec.methodBuilder("loadInto") .addModifiers(Modifier.PUBLIC) .addAnnotation(Override.class) .addParameter(routesParameter); String packageName = "com.example.android.arouter.routers"; for (Map.Entry<String, String> entry : cacheGroupMap.entrySet()) { // 如:shop String groupName = entry.getKey(); // 如:Router$$Group$$shop String finalGroupClassName = entry.getValue(); // routes.put("shop", Router$$Group$$shop.class); loadIntoMethodBuilder.addStatement( "$N.put($S, $T.class);", "routes", groupName, ClassName.get(packageName, finalGroupClassName) ); } MethodSpec loadIntoMethod = loadIntoMethodBuilder.build(); String finalClassName = "ARouter$$Root$$" + moduleName; messager.printMessage(Diagnostic.Kind.NOTE, "最终生成的组文件名:" + packageName + "." +finalClassName); // public final class Router$$Root$$shop implements IRouteRoot TypeSpec finalClass = TypeSpec.classBuilder(finalClassName) .addMethod(loadIntoMethod) .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addSuperinterface(ClassName.get(routeRootElement)).build(); JavaFile.builder(packageName, finalClass).build().writeTo(filer); }
比如Activity跳转,需要携带了参数,我们需要通过Activity.getIntent()获取参数。这些模版代码也可以注解处理器帮我们生成。
public final class ShopActivity$$ARouter$$Parameter implements IParameter { @Override public void inject(final Object object) { ShopActivity target = (ShopActivity) object;; target.name = target.getIntent().getStringExtra("name");; target.age = target.getIntent().getIntExtra("shopAge", target.age);; target.isOpen = target.getIntent().getBooleanExtra("isOpen", target.isOpen);; } }
创建ParameterProcessor注解处理器,用来生成获取参数的模板代码,该类同样继承AbstractProcessor
@Target(ElementType.FIELD) @Retention(RetentionPolicy.CLASS) public @interface Parameter { String name() default ""; } // 允许/支持的注解类型,让注解处理器处理(新增annotation module) @SupportedAnnotationTypes({"com.example.modular.annotations.Parameter"}) // 指定JDK编译版本 @SupportedSourceVersion(SourceVersion.RELEASE_7) // 注解处理器接收的参数 @SupportedOptions({"moduleName"}) // AutoService则是固定的写法,加个注解即可 // 通过auto-service中的@AutoService可以自动生成AutoService注解处理器,用来注册 // 用来生成 META-INF/services/javax.annotation.processing.Processor 文件 @AutoService(Processor.class) public class ParameterProcessor extends AbstractProcessor {}
实现process方法,处理被@Parameter标记的元素。
@Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { if (set.isEmpty()) return false; Set<? extends Element> elementsAnnotatedWithParameter = roundEnvironment.getElementsAnnotatedWith(Parameter.class); if (elementsAnnotatedWithParameter != null && !elementsAnnotatedWithParameter.isEmpty()) { fillMapWithElement(elementsAnnotatedWithParameter); try { createParameterFile(); } catch (IOException e) { messager.printMessage(Diagnostic.Kind.NOTE, "ParameterProcessor发生异常:" + e.getMessage()); } return true; } return false; }
其中fillMapWithElement用于缓存所有满足条件的元素
private final Map<TypeElement, List<Element>> cacheMap = new HashMap<>(); private void fillMapWithElement(Set<? extends Element> elementsAnnotatedWithParameter) { for (Element element : elementsAnnotatedWithParameter) { // @Parameter注解作用在字段上,获取字段的父元素 TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); messager.printMessage(Diagnostic.Kind.NOTE, "@Parameter遍历父元素信息:" + enclosingElement.getSimpleName() + " && 遍历元素信息: " + element.getSimpleName()); List<Element> elements = cacheMap.get(enclosingElement); if (elements == null) { List<Element> fields = new ArrayList<>(); fields.add(element); cacheMap.put(enclosingElement, fields); } else { elements.add(element); } } }
接下来用于创建生成文件
private void createParameterFile() throws IOException { if (cacheMap.isEmpty()) return; TypeElement activityElement = elementUtils.getTypeElement("android.app.Activity"); ParameterSpec objectParameter = ParameterSpec.builder(TypeName.OBJECT, "object", Modifier.FINAL).build(); TypeElement parameterType = elementUtils.getTypeElement("com.example.modular.api.IParameter"); for (Map.Entry<TypeElement, List<Element>> entry : cacheMap.entrySet()) { TypeElement typeElement = entry.getKey(); if (!typeUtils.isSubtype(typeElement.asType(), activityElement.asType())) { throw new RuntimeException("@Parameter注解目前只能应用于Activity上"); } ClassName className = ClassName.get(typeElement); // public void inject(Object object) MethodSpec.Builder injectMethodBuilder = MethodSpec.methodBuilder("inject") .addAnnotation(Override.class) .addModifiers(Modifier.PUBLIC) .addParameter(objectParameter); // MainActivity target = (MainActivity) object; injectMethodBuilder.addStatement( "$T target = ($T) $N;", className, className, "object" ); List<Element> elements = entry.getValue(); for (Element element : elements) { TypeMirror typeMirror = element.asType(); int type = typeMirror.getKind().ordinal(); String fieldName = element.getSimpleName().toString(); // 获取注解中定义的字段名 String name = element.getAnnotation(Parameter.class).name(); // 如果注解中没有定义,则使用默认的字段名 String finalName = Utils.isEmpty(name) ? fieldName : name; String finalValue = "target." + fieldName; String format = finalValue + " = target.getIntent()."; if (type == TypeKind.INT.ordinal()) { // target.age = target.getIntent().getIntExtra("appAge", target.age); format += "getIntExtra($S, " + finalValue + ");"; } else if (type == TypeKind.BOOLEAN.ordinal()) { // target.isOpen = target.getIntent().getBooleanExtra("isOpen", target.isOpen); format += "getBooleanExtra($S, " + finalValue + ");"; } else { if (typeMirror.toString().equals("java.lang.String")) { // target.name = target.getIntent().getStringExtra("name"); format += "getStringExtra($S);"; } } if (format.endsWith(";")) { // target.name = target.getIntent().getStringExtra("name"); injectMethodBuilder.addStatement(format, finalName); } else { messager.printMessage(Diagnostic.Kind.ERROR, "目前仅仅支持String int Boolean类型参数"); } } MethodSpec injectMethod = injectMethodBuilder.build(); String finalClassName = typeElement.getSimpleName() + "$$ARouter$$Parameter"; messager.printMessage(Diagnostic.Kind.NOTE, "最终生产的参数类文件:" + className.packageName() + "." + finalClassName); // public final class MainActivity$$Router$$Parameter implements IParameter TypeSpec finalClass = TypeSpec.classBuilder(finalClassName) .addMethod(injectMethod) .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addSuperinterface(ClassName.get(parameterType)) .build(); JavaFile.builder(className.packageName(), finalClass).build().writeTo(filer); } }