一、背景
早期的项目基本上都采用的单一分层模式,但这种模式,无论分包怎么做,随着项目的增大,项目会失去层次感,代码耦合性高,非常难维护,多人开发的时候,很容易出现代码覆盖冲突的问题。所有模块代码都编写在一个项目中,测试某个模块或功能,需要编译运行整个项目,有些项目非常巨大,编译可能就要编译十分钟,非常不方便。组件化应运而生。
组件化,按照App的功能,将App划分成一个个不同的模块,彼此之间不相互依赖,但可以互相通信,可以任意组合,高度解耦,自由拆卸,自由组合,重复利用,分层独立化。
二、各组件之间早期的通信方式
组件化的原则是:各个组件之间不能互相依赖,但却可以互相通信,如何实现呢? 早期的时候,组件化之间的互相通信有五种方式:
1.EventBus (缺点:不好管理)
2.广播 (缺点:不好管理)
3.使用隐式意图 (缺点:AndroidManife.xml中要写的内容太多)
4.使用类加载的方式 (缺点:容易写错包名)
5.使用全局Map的方式 (缺点:要注册很多对象)
这里讲解一下第四和第五种方式,这两种方式和ARouter的原理以及非常近似了,理解了这两种通信方式,能更好的理解ARouter原理。
2.1 使用类加载的方式进行组件之间的通信
具体来说,这个方式是通过全类名找到类,实现跳转。
/**
* 跳转到Personal
* @param view
*/
public void JumpPersonal(View view) {
try {
Class<?> personalClass = Class.forName("com.kimliu.personal.Person_MainActivity");
Intent intent = new Intent(this,personalClass);
intent.putExtra("name","kimliu");
startActivity(intent);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
使用类加载的原理是,当打成apk包的时候,所有的组件和类都打包在了一起。如下图所示:从order往personal跳当然是可以通过全类名找到相应的类的。其实ARouter的原理也跟这一样。
2.2 使用全局Map的方式进行组件之间的通信
2.2.1 准备工作
要使用全局Map的跳转,可想而知,需要一个全局的仓库来装所有的类和它们的路径。路径应该由组件名+类名组成,每一个组件中的所有类放在一起,方便查找。想好了这些,我们创建了一个PathBean和RouteManager,其中的PathBean用来封装路径名和对应的Class,而RouteManager用来管理这些类的路径。
PathBean:
/**
* 创建一个全局的path类 通过这个类 可以找到所有的class
*/
public class PathBean {
private String path; // 类的路径
private Class clazz; // path对应的class类
public PathBean() {
}
public PathBean(String path, Class clazz) {
this.path = path;
this.clazz = clazz;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public Class getClazz() {
return clazz;
}
public void setClazz(Class clazz) {
this.clazz = clazz;
}
}
RouteManager:包含两个功能:将Class存入仓库和从仓库取出Class
/**
* 全局路径的记录器
*
* 组名 : app order personal
*
* 使用全局Map保存
*/
public class RoutePathManager {
/**
* 仓库
* Key -> Group : app order personal
* value -> 在这个group中的所有类的path
*/
private static Map<String, List<PathBean>> pathMaps = new HashMap<>();
/**
* 存入仓库
* @param groupName 组名:如personal
* @param pathName 路径:如personal/Personal_MainActivity
* @param clazz
*/
public static void addGroupInfo(String groupName,String pathName,Class<?> clazz){
List<PathBean> list = pathMaps.get(groupName);
if(null == list){
list = new ArrayList<>();
list.add(new PathBean(pathName,clazz));
}
// 存入仓库
pathMaps.put(groupName,list);
}
/**
* 从仓库取出
* 通过组名和路径 找到对应的Class类
* @param groupName
* @param pathName
* @return
*/
public static Class<?> startTargetActivity(String groupName,String pathName){
List<PathBean> list = pathMaps.get(groupName);
if(null == list){
throw new RuntimeException("没有找到这个组名");
}
for (PathBean pathBean : list) {
if(pathName.equalsIgnoreCase(pathBean.getPath())){
return pathBean.getClazz();
}
}
return null;
}
}
2.2.2 在App初始化时,把所有要跳转的类,放入仓库中
Application中:
...
@Override
public void onCreate() {
super.onCreate();
// 把所有要跳转的类存入仓库
RoutePathManager.addGroupInfo("app","MainActivity",MainActivity.class);
RoutePathManager.addGroupInfo("order","order_MainActivity", order_MainActivity.class);
RoutePathManager.addGroupInfo("personal","personal_MainActivity", Person_MainActivity.class);
}
...
2.2.3 跳转
Class<?> personalClass = RoutePathManager.startTargetActivity("personal", "personal_MainActivity");
Intent intent = new Intent(this,personalClass);
startActivity(intent);
这两种方式,都可以实现组件之间的通信,但是这两种方式,要么就是要写的代码很多,要么就是一不小心会写错导致类找不到,能不能创建一个框架,让这些工作都在框架里做完了,用户只需要写很少的代码就可以实现组件化之间的通信呢? ARouter就这样诞生了。
三、手撸一个ARouter框架
组件化的通信,有个很重要的概念就是路由表,ARouter中的路由表就相当于上一节中的RouteManager。
ARouter框架,属于编译期框架,也就是说,需要在编译期生成代码,具体来说,就是在编译期就把各个模块中使用注解标记过的类信息放入路由表中去。要实现这一需求,就需要用到在上一篇博客中讲到的APT技术。APT在上一篇博客中已经详细的讲过,注解处理器。它在编译时,就能拿到被注解的类和数据,根据规则使用JavaPoet生成我们想要的类。这里还有个重要的工具:JavaPoet,这个工具在整个ARouter中起到了至关重要的作用,后面会讲到。
其它编译期的框架:ButterKnife、EventBus等。
3.1 认识JavaPoet
JavaPoet是JakeWharton写的一个自动生成Java文件的开源框架。
JavaPoet相关的API:
JavaPoet写代码的方式是倒序写代码,先写方法,再写类,再写包。
要使用JavaPoet,首先,我们要清楚,Java源文件也是一种结构体语言,如下:
package xxx.xxx.xxx // PackageElement 包元素/节点
public class Main{ // TypeELement 类元素/节点
private int x; // VariableElement 属性元素/节点
public static void main(){ // ExecuteableElement 方法元素/节点
}
}
...
在APT中,PackageElement 、TypeELement、VariableElement、ExecuteableElement等都是非常重要的元素,在生成代码时,非常有用,在本节内容的第五小节:“在注解处理器中生成代码”,使用的就是JavaPoet生成代码,相关API的使用在代码注释中写的非常清楚。
3.2 手写ARouter框架
ARouter框架的基本流程就是:将要跳转的类加入路由表,加入的方法就是,在类上使用特定的注解标记如@ARouter,让用户按照规范填写Path,解析Path,找到这个类,进行跳转或者传值等。
3.2.1 将被@ARouter标记的类注册进路由表
3.2.1.1 搭建环境
分别创建Java Library : arouter-annotation(注解模块)和arouter-compiler(注解处理器模块)
arouter-annotation(注解模块)的build.gradle 关键是规定JDK版本。
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
arouter-compiler(注解处理器模块)的build.gradle
...
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// 背后的服务,用来监听是否在编译中 注意 这里需要使用1.0-rc7的版本
compileOnly'com.google.auto.service:auto-service:1.0-rc7'
annotationProcessor'com.google.auto.service:auto-service:1.0-rc7'
// 使用JavaPoet 帮助我们通过类调用的形式来生成Java代码
implementation "com.squareup:javapoet:1.9.0"
// 引入annotation,处理@ARouter注解
implementation project(':arouter_annotations')
}
...
注解处理器模块,必须添加服务,才可以在编译期工作。
3.2.1.2 定义两个仓库,group仓库和path仓库
这两个仓库,是两个Map,具体实现如下:
// 创建仓库一(缓存) : Path仓库
// Map<"personal", List<RouterBean>> Key =>组名 Value=>这个组中,所有被@ARouter标记的Activity封装类RouteBean的集合
private Map<String, List<RouteBean>> mAllPathMap = new HashMap<>();
// 创建仓库二(缓存):Group仓库
// Map<"personal", "ARouter$$Path$$personal.class"> key => 组名 value => Path类类名的字符串
private Map<String,String> mAllGroupMap = new HashMap<>();
注释里面已经写的很清楚了,Path仓库:里面装了每个组中对应的被@ARouter注解的类;Group仓库:里面装了封装了每个组的Path的封装类ARouterxxx.class
3.2.1.3 理清要生成的类具体的写法
ARouter框架的目的,是为了找到那些被@ARouter标记的Activity,所以,类似于上面的使用全局Map跳转的方式一样,我们生成的类,必须要通过用户输入的参数,找到要跳转的Activity。
//要生成的类:Path
public class ARouter$$Path$$personal implements ARouterPath {
@Override
public Map<String, RouterBean> getPathMap() {
Map<String, RouterBean> pathMap = new HashMap<>();
pathMap.put("/personal/Personal_Main2Activity", RouterBean.create(RouterBean.TypeEnum.ACTIVITY, Personal_Main2Activity.class, "/personal/Personal_Main2Activity", "personal"));
pathMap.put("/personal/Personal_Main3Activity", RouterBean.create(RouterBean.TypeEnum.ACTIVITY, Personal_Main3Activity.class, "/personal/Personal_Main3Activity", "personal"));
return pathMap;
}
}
// Group
public class ARouter$$Group$$personal implements ARouterGroup {
@Override
public Map<String, Class<? extends ARouterPath>> getGroupMap() {
Map<String, Class<? extends ARouterPath>> groupMap = new HashMap<>();
groupMap.put("personal", ARouter$$Path$$personal.class);
return groupMap;
}
}
3.2.1.4 创建注解@ARouter
ARouter用来标记需要放入路由表中的类
@Target(ElementType.TYPE) // 作用在类上
@Retention(RetentionPolicy.CLASS) // 编译期注解
public @interface ARouter {
// 被标记的类的路径名 不可以为空
String path();
// 被标记的类的组名,如果为空,从path中截取
String group() default "";
}
3.2.1.5 在注解处理器中编写代码,让注解处理器动态生成我们想要的代码
ARouterProcesser.java的工作大致上就是,首先,要找到被@ARouter标记的类,并将该类使用RouteBean封装,根据用户输入的组名(如果用户没有输入,可以自己截取),将这些RouteBean按照组名放在同一个List中,再把这个List放入Key为组名的pathMap(也就是Path仓库)中。同时,会生成对应的ARouterxxx.java,将生成的ARouterxxx.java再放入key为组名的groupMap(也就是Group仓库)中。 具体的步骤,代码中的注释以及写的非常清楚了,如下:
// AutoService则是固定的写法,加个注解即可
// 通过auto-service中的@AutoService可以自动生成AutoService注解处理器,用来注册
// 用来生成 META-INF/services/javax.annotation.processing.Processor 文件
@AutoService(Processor.class)
@SupportedSourceVersion(SourceVersion.RELEASE_7) //指定的JDK版本
@SupportedAnnotationTypes({ProcessorConfig.AROUTE_PACKAGE}) // 作用于哪个注解
@SupportedOptions({ProcessorConfig.OPTIONS, ProcessorConfig.PACKAGENAMEFORAPT}) // 接收由别的组件传递过来的组名
public class ARouterProcesser extends AbstractProcessor {
// 以下为固定写法 用来生成Java文件
// 操作Element的工具类 (类、函数、属性其实都是Element)
private Elements elementTool;
// type(类)的工具类,包含用于操作TypeMirror的工具方法
private Types typeTool;
// 打印日志
private Messager messager;
// 文件生成器 生成类等
private Filer filer;
private String mOptions;
private String mPackageNameForAPT;
// 创建仓库一(缓存) : Path仓库
// Map<"personal", List<RouterBean>>
private Map<String, List<RouteBean>> mAllPathMap = new HashMap<>();
// 创建仓库二(缓存):Group仓库
// Map<"personal", "ARouter$$Path$$personal.class">
private Map<String,String> mAllGroupMap = new HashMap<>();
/**
* 做初始化的工作,就像Activity中的oncreate方法一样
* @param processingEnvironment
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
elementTool = processingEnvironment.getElementUtils();
messager = processingEnvironment.getMessager();
filer = processingEnvironment.getFiler();
typeTool = processingEnvironment.getTypeUtils();
/*
接收从APP传递过来的数据 在要传值的module的build.gradle文件中写上
javaCompileOptions{
annotationProcessorOptions{
arguments = [moduleName:project.getName(),packageNameForAPT:packageNameForAPT]
}
}
*/
mOptions = processingEnvironment.getOptions().get(ProcessorConfig.OPTIONS);
mPackageNameForAPT = processingEnvironment.getOptions().get(ProcessorConfig.PACKAGENAMEFORAPT);
messager.printMessage(Diagnostic.Kind.NOTE,"==> options:"+ mOptions);
messager.printMessage(Diagnostic.Kind.NOTE,"==> packageName:"+ mPackageNameForAPT);
if (mOptions != null && mPackageNameForAPT != null) {
messager.printMessage(Diagnostic.Kind.NOTE, "APT Completed....");
} else {
messager.printMessage(Diagnostic.Kind.NOTE, "APT NOT Completed...");
}
}
/**
* 注解处理器的关键方法,在这里处理注解,生成Java文件
* @param set
* @param roundEnvironment
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
if(set.isEmpty()){
messager.printMessage(Diagnostic.Kind.NOTE,"未发现@ARouter");
return false;
}
// 1. 获取所有被 @ARouter 注解的元素的集合 这里得到的值 是被ARouter注解的类的集合
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
// 获取Activity的类信息
TypeElement typeElement = elementTool.getTypeElement(ProcessorConfig.ACTIVITY_PACKAGENAME);
TypeMirror activityTypeMirror = typeElement.asType();
// 2. 遍历所有的类元素
for (Element element : elements) {
// 获取类节点的包名
String packageName = elementTool.getPackageOf(element).getQualifiedName().toString();
messager.printMessage(Diagnostic.Kind.NOTE,"packageName:"+packageName);
// 获取简单类名
String className = element.getSimpleName().toString();
messager.printMessage(Diagnostic.Kind.NOTE,"===> @ARouter Annotation:"+className);
// 拿到注解
ARouter aRouter = element.getAnnotation(ARouter.class);
// 对用户的使用进行规则的校验
// 对路由对象进行封装
RouteBean routeBean = new RouteBean.Builder()
.addGroup(aRouter.group())
.addPath(aRouter.path())
.addElement(element)
.build();
// 1. 校验1 : @ARouter只可以用于Activity之上
// 获取每一个element的具体详情,如继承了哪个类等
TypeMirror typeMirror = element.asType();
// typeMirror 是否是Activity的子类
if(typeTool.isSubtype(typeMirror,activityTypeMirror)){
// 如果是 填入类型
routeBean.setTypeEnum(RouteBean.TypeEnum.ACTIVITY);
}else{
// 抛出异常
throw new RuntimeException("@ARouter just for Activity");
}
// 校验二: 校验用户是否按照规则编写path
if(checkRoutePath(routeBean)){
// 说明routeBean赋值成功
messager.printMessage(Diagnostic.Kind.NOTE,"RouteBean check success:"+routeBean.toString());
// 存入仓库
List<RouteBean> routeBeans = mAllPathMap.get(routeBean.getGroup());
if(ProcesserUtils.isEmpty(routeBeans)){
routeBeans = new ArrayList<>();
}
routeBeans.add(routeBean);
mAllPathMap.put(routeBean.getGroup(),routeBeans);
}else{
// 未按照规范填写Path 规范:/app/MainActivity
messager.printMessage(Diagnostic.Kind.ERROR,"@ARouter未按照规范填写Path");
}
}
TypeElement pathType = elementTool.getTypeElement(ProcessorConfig.AROUTER_API_PATH);
TypeElement groupType = elementTool.getTypeElement(ProcessorConfig.AROUTER_API_GROUP);
messager.printMessage(Diagnostic.Kind.NOTE,"==> pathType:"+pathType);
messager.printMessage(Diagnostic.Kind.NOTE,"==> groupType:"+groupType);
createPathClass(pathType);
createGroupClass(pathType,groupType);
return true;
}
/**
* 生成路由组Group文件,如:ARouter$$Group$$app
* @param groupType ARouterLoadGroup接口信息
* @param pathType ARouterLoadPath接口信息
*/
private void createGroupClass(TypeElement pathType, TypeElement groupType) {
// 仓库二 缓存二 判断是否有需要生成的类文件
if (ProcesserUtils.isEmpty(mAllGroupMap) || ProcesserUtils.isEmpty(mAllPathMap)) return;
// 返回值 这一段 Map<String, Class<? extends ARouterPath>>
TypeName methodReturns = ParameterizedTypeName.get(
ClassName.get(Map.class), // Map
ClassName.get(String.class), // Map<String,
// Class<? extends ARouterPath>> 难度
ParameterizedTypeName.get(ClassName.get(Class.class),
// ? extends ARouterPath
WildcardTypeName.subtypeOf(ClassName.get(pathType))) // ? extends ARouterLoadPath
// WildcardTypeName.supertypeOf() 做实验 ? super
// 最终的:Map<String, Class<? extends ARouterPath>>
);
// 1.方法 public Map<String, Class<? extends ARouterPath>> getGroupMap() {
MethodSpec.Builder methodBuidler = MethodSpec.methodBuilder(ProcessorConfig.GROUP_METHOD_NAME) // 方法名
.addAnnotation(Override.class) // 重写注解 @Override
.addModifiers(Modifier.PUBLIC) // public修饰符
.returns(methodReturns); // 方法返回值
// Map<String, Class<? extends ARouterPath>> groupMap = new HashMap<>();
methodBuidler.addStatement("$T<$T, $T> $N = new $T<>()",
ClassName.get(Map.class),
ClassName.get(String.class),
// Class<? extends ARouterPath> 难度
ParameterizedTypeName.get(ClassName.get(Class.class),
WildcardTypeName.subtypeOf(ClassName.get(pathType))), // ? extends ARouterPath
ProcessorConfig.GROUP_VAR1,
ClassName.get(HashMap.class));
// groupMap.put("personal", ARouter$$Path$$personal.class);
// groupMap.put("order", ARouter$$Path$$order.class);
for (Map.Entry<String, String> entry : mAllGroupMap.entrySet()) {
methodBuidler.addStatement("$N.put($S, $T.class)",
ProcessorConfig.GROUP_VAR1, // groupMap.put
entry.getKey(), // order, personal ,app
ClassName.get(mPackageNameForAPT, entry.getValue()));
}
// return groupMap;
methodBuidler.addStatement("return $N", ProcessorConfig.GROUP_VAR1);
// 最终生成的类文件名 ARouter$$Group$$ + personal
String finalClassName = ProcessorConfig.GROUP_FILE_NAME + mOptions;
messager.printMessage(Diagnostic.Kind.NOTE, "APT生成路由组Group类文件:" +
mPackageNameForAPT + "." + finalClassName);
// 生成类文件:ARouter$$Group$$app
try {
JavaFile.builder(mPackageNameForAPT, // 包名
TypeSpec.classBuilder(finalClassName) // 类名
.addSuperinterface(ClassName.get(groupType)) // 实现ARouterLoadGroup接口 implements ARouterGroup
.addModifiers(Modifier.PUBLIC) // public修饰符
.addMethod(methodBuidler.build()) // 方法的构建(方法参数 + 方法体)
.build()) // 类构建完成
.build() // JavaFile构建完成
.writeTo(filer); // 文件生成器开始生成类文件
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 生成Path类
* @param pathType ARouterPath 高层标准
*/
private void createPathClass(TypeElement pathType) {
if(ProcesserUtils.isEmpty(mAllPathMap)){
messager.printMessage(Diagnostic.Kind.NOTE,"Path仓库为空,没有要生成的类");
return;
}
// 使用JavaPoet生成Class
/*
要生成的代码:
@Override
public Map<String, RouterBean> getPathMap() {
Map<String, RouterBean> pathMap = new HashMap<>();
pathMap.put("/personal/Personal_Main2Activity", RouterBean.create();
pathMap.put("/personal/Personal_MainActivity", RouterBean.create());
return pathMap;
}
*/
// 1. 生成返回值 Map 类型 : Map<String,RouteBean> 使用ParameterizedTypeName.get()
TypeName methodReturn = ParameterizedTypeName.get(
ClassName.get(Map.class),
ClassName.get(String.class),
ClassName.get(RouteBean.class)
);
// 2. 遍历仓库 app personal order
for(Map.Entry<String,List<RouteBean>> entry : mAllPathMap.entrySet()){
// 假如是personal
// 1. 生成方法
// 1.方法
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(ProcessorConfig.PATH_METHOD_NAME)
.addAnnotation(Override.class) // 给方法上添加注解 @Override
.addModifiers(Modifier.PUBLIC) // public修饰符
.returns(methodReturn) // 把Map<String, RouterBean> 加入方法返回
;
// Map<String, RouterBean> pathMap = new HashMap<>(); // $N == 变量 为什么是这个,因为变量有引用 所以是$N
methodBuilder.addStatement("$T<$T, $T> $N = new $T<>()",
ClassName.get(Map.class), // Map
ClassName.get(String.class), // Map<String,
ClassName.get(RouteBean.class), // Map<String, RouterBean>
ProcessorConfig.PATH_VAR1, // Map<String, RouterBean> pathMap
ClassName.get(HashMap.class) // Map<String, RouterBean> pathMap = new HashMap<>();
);
// 必须要循环,因为有多个
// pathMap.put("/personal/Personal_Main2Activity", RouterBean.create(RouterBean.TypeEnum.ACTIVITY,
// Personal_Main2Activity.class);
// pathMap.put("/personal/Personal_MainActivity", RouterBean.create(RouterBean.TypeEnum.ACTIVITY));
List<RouteBean> pathList = entry.getValue();
/**
$N == 变量 变量有引用 所以 N
$L == TypeEnum.ACTIVITY
*/
// personal 的细节
for (RouteBean bean : pathList) {
methodBuilder.addStatement("$N.put($S, $T.create($T.$L, $T.class, $S, $S))",
ProcessorConfig.PATH_VAR1, // pathMap.put
bean.getPath(), // "/personal/Personal_Main2Activity"
ClassName.get(RouteBean.class), // RouterBean
ClassName.get(RouteBean.TypeEnum.class), // RouterBean.Type
bean.getTypeEnum(), // 枚举类型:ACTIVITY
ClassName.get((TypeElement) bean.getElementType()), // MainActivity.class Main2Activity.class
bean.getPath(), // 路径名
bean.getGroup() // 组名
);
} // TODO end for
// return pathMap;
methodBuilder.addStatement("return $N", ProcessorConfig.PATH_VAR1);
// TODO 注意:不能像以前一样,1.方法,2.类 3.包, 因为这里面有implements ,所以 方法和类要合为一体生成才行,这是特殊情况
// 最终生成的类文件名 ARouter$$Path$$personal
String finalClassName = ProcessorConfig.PATH_FILE_NAME + entry.getKey();
messager.printMessage(Diagnostic.Kind.NOTE, "APT生成路由Path类文件:" +
mPackageNameForAPT + "." + finalClassName);
// 生成类文件:ARouter$$Path$$personal
try {
JavaFile.builder(mPackageNameForAPT, // 包名 APT 存放的路径
TypeSpec.classBuilder(finalClassName) // 类名
.addSuperinterface(ClassName.get(pathType)) // 实现ARouterLoadPath接口 implements ARouterPath==pathType
.addModifiers(Modifier.PUBLIC) // public修饰符
.addMethod(methodBuilder.build()) // 方法的构建(方法参数 + 方法体)
.build()) // 类构建完成
.build() // JavaFile构建完成
.writeTo(filer); // 文件生成器开始生成类文件
} catch (IOException e) {
e.printStackTrace();
}
// 仓库二 缓存二 非常重要一步,注意:PATH 路径文件生成出来了,才能赋值路由组mAllGroupMap
mAllGroupMap.put(entry.getKey(), finalClassName);
}
}
/**
* 校验用户是否按照规则填写 @ARouter中的path
*/
private boolean checkRoutePath(RouteBean routeBean) {
String groupName = routeBean.getGroup();
String path = routeBean.getPath();
//path 必须以 “/” 开头
if(ProcesserUtils.isEmpty(path) || !path.startsWith("/")){
// messager 打印信息 如果类型时ERROR 那么 程序必崩
messager.printMessage(Diagnostic.Kind.NOTE,"@ARouter中的Path必须要以 / 开头");
return false;
}
// "/"最后一次出现的位置 如果是第一位 没写组名
if(path.lastIndexOf("/") == 0){
messager.printMessage(Diagnostic.Kind.NOTE,"@ARouter中的Path未按规范填写,应如:/app/MainActivity");
return false;
}
// 下面的情况就是按照规范填写了Path的情形
// 如果Group没有赋值,才需要截取吧
if(ProcesserUtils.isEmpty(groupName)){
// 截取出组名 从第一个"/" 到第二个 "/" 中截取
groupName = path.substring(1, path.indexOf("/"));
}
if(!ProcesserUtils.isEmpty(groupName)){
if(!groupName.equals(mOptions)){
messager.printMessage(Diagnostic.Kind.NOTE,"@ARouter中的group必须和组件名一致");
return false;
}
routeBean.setGroup(groupName);
}else{
messager.printMessage(Diagnostic.Kind.ERROR,"@ARouter中的group不能为空,请检查path是否按照如:/app/MainActivity 填写");
return false;
}
// 如果返回true 说明routeBean 的Group 赋值成功
return true;
}
}
编译之后,生成了相关的类。以Module App为例: 最后生成的ARouterapp:
public class ARouter$$Group$$app implements ARouterGroup {
@Override
public Map<String, Class<? extends ARouterPath>> getGroupMap() {
Map<String, Class<? extends ARouterPath>> groupMap = new HashMap<>();
groupMap.put("app", ARouter$$Path$$app.class);
return groupMap;
}
}
生成的ARouterapp:
public class ARouter$$Path$$app implements ARouterPath {
@Override
public Map<String, RouteBean> getPathMap() {
Map<String, RouteBean> pathMap = new HashMap<>();
pathMap.put("/app/MainActivity", RouteBean.create(RouteBean.TypeEnum.ACTIVITY, MainActivity.class, "/app/MainActivity", "app"));
return pathMap;
}
}
3.2.2 组件之间的传参
组件之间的传参,原理也是编译期在APT中获取到注解以及其参数的值,进行传参,和上面一节3.2.1的原理是一样的。 具体一些讲就是,
步骤:
1.添加注解Parameter,用来标记要传递值的参数。
2.ParameterGet标准规则的制定。
3.注解处理器,处理被标记的参数,并生成需要的代码。
4.参数管理器的编写 5.使用注解,跳转Activity时,传值
3.2.2.1 添加注解Parameter
@Target(ElementType.FIELD) // 作用在字段上
@Retention(RetentionPolicy.CLASS) // 保留到编译时期 便于在编译时做一些预处理的动作
public @interface Parameter {
String name() default "";
}
3.2.2.2 定义一个接口,APT自定生成的类要继承这个接口,在这个接口里面定义规则
/**
* 定义一个接口,APT自定生成的类要继承这个接口,在这个接口里面定义规则
*/
public interface ParameterGet {
/**
* 目标对象.属性名 = getIntent().属性类型 完成赋值操作
* @param targetParameter 目标对象 例如 MainActivity中的那些属性
*/
void getParameter(Object targetParameter);
}
3.2.2.3 在注解处理器里面,处理被注解标记的参数,并生成实现功能的类
@AutoService(Processor.class) // 开启AutoService
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedAnnotationTypes({ProcessorConfig.PARAMETER_ANNOTATION_PATH}) // 这个注解处理器服务的注解
public class ParameterGetProcessor extends AbstractProcessor {
// 操作Element的工具类 (类、函数、属性其实都是Element)
private Elements elementTool;
// type(类)的工具类,包含用于操作TypeMirror的工具方法
private Types typeTool;
// 打印日志
private Messager messager;
// 文件生成器 生成类等
private Filer filer;
// 创建仓库 :根据Activity 对所有被@Parameter注解的属性进行分类
// key : Activity类 value : 该类中所有被标注的Parameter的集合
private Map<TypeElement, List<Element>> tempParameterMap = new HashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
elementTool = processingEnvironment.getElementUtils();
messager = processingEnvironment.getMessager();
filer = processingEnvironment.getFiler();
typeTool = processingEnvironment.getTypeUtils();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if(!ProcesserUtils.isEmpty(annotations)){
// 获取所有被 @ParameterGet 标记的Parameter
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Parameter.class);
if(!ProcesserUtils.isEmpty(elements)){
for (Element element : elements) {
// 获取属性的父节点 属性的父节点是类节点 : 该属性所在的类
TypeElement typeElement = (TypeElement) element.getEnclosingElement();
if(tempParameterMap.containsKey(typeElement)){
// 如果仓库中已经有了这个类,那么直接添加进去
tempParameterMap.get(typeElement).add(element);
}else{
// 没有 则创建
List<Element> fields = new ArrayList<>();
fields.add(element);
tempParameterMap.put(typeElement,fields);
}
} // end for 此时,缓存中有值了
// 如果缓存为空,说明,没有使用@Parameter 返回
if(ProcesserUtils.isEmpty(tempParameterMap)){
return true;
}
// 如果有使用@Parameter ,那么生成相对应的类, 实现xx功能
TypeElement activityType = elementTool.getTypeElement(ProcessorConfig.ACTIVITY_PACKAGENAME);
TypeElement parameterType = elementTool.getTypeElement(ProcessorConfig.PARAMETER_GET_PATH);
// 生成方法
ParameterSpec parameterSpec =ParameterSpec.builder(TypeName.OBJECT,ProcessorConfig.PARAMETER_NAME).build();
//
for (Map.Entry<TypeElement,List<Element>> entry:tempParameterMap.entrySet()){
// key : 类 如:Personal_MainActivity
// value : 被@ParameterGet标记的属性
TypeElement typeElement = entry.getKey();
// 非Activity类直接报错
if(!typeTool.isSubtype(typeElement.asType(),activityType.asType())){
throw new RuntimeException("@Parameter注解目前仅限用于Activity类之上");
}
// 获取类名
ClassName className = ClassName.get(typeElement);
ParameterFactory factory = new ParameterFactory.Builder(parameterSpec)
.setMessager(messager)
.setClassName(className)
.build();
factory.addFristStatement();
// 循环生成后续代码 根据被@Parameter标记的参数个数循环
for (Element element : entry.getValue()) {
factory.buildStatement(element);
}
// 生成类文件 类名$$Parameter 如Personal_MainActivity$$Parameter
String finalClassName = typeElement.getSimpleName() + ProcessorConfig.PARAMETER_FILE_NAME;
messager.printMessage(Diagnostic.Kind.NOTE,"APT生成的类文件:"+ className.packageName() + " . " + finalClassName);
// 开始生成文件
try {
JavaFile.builder(className.packageName(), //包名
TypeSpec.classBuilder(finalClassName) // 类名
.addSuperinterface(ClassName.get(parameterType)) // implements ParameterGet 实现ParameterGetLoad接口
.addModifiers(Modifier.PUBLIC) // public 修饰符
.addMethod(factory.build()) // 方法的构建 (方法参数 + 方法体)
.build()) // 类构建完成
.build() //JavaFile 构建完成
.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return false;
}
}
生成getParameter方法中的代码:
/**
* 目的 生成以下代码:
* @Override
* public void getParameter(Object targetParameter) {
* Personal_MainActivity t = (Personal_MainActivity) targetParameter;
* t.name = t.getIntent().getStringExtra("name");
* t.sex = t.getIntent().getStringExtra("sex");
* }
*/
public class ParameterFactory {
// 方法的构建
private MethodSpec.Builder method;
// 类名,如:MainActivity / Personal_MainActivity
private ClassName className;
// Messager用来报告错误,警告和其他提示信息
private Messager messager;
private ParameterFactory(Builder builder){
this.messager = builder.messager;
this.className = builder.className;
method = MethodSpec.methodBuilder(ProcessorConfig.PARAMETER_METHOD_NAME)
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.addParameter(builder.parameterSpec);
}
/**
* 生成第一行代码
* Personal_MainActivity t = (Personal_MainActivity) targetParameter;
*/
public void addFristStatement(){
method.addStatement("$T t = ($T)" + ProcessorConfig.PARAMETER_NAME ,className , className);
}
/**
* 生成下面的循环体
* t.name = t.getIntent().getStringExtra("name");
*/
public void buildStatement(Element element){
// 遍历注解的属性节点,生成函数体
TypeMirror typeMirror = element.asType();
// 获取 TypeKind 枚举类型的序列号
int type = typeMirror.getKind().ordinal();
messager.printMessage(Diagnostic.Kind.NOTE,"type:"+type);
// 获取属性名 name sex age
String fieldName = element.getSimpleName().toString();
// 获取注解的值
String annotationName = element.getAnnotation(Parameter.class).name();
// 如果注解的值为空,那么使用属性值
annotationName = ProcesserUtils.isEmpty(annotationName)? fieldName:annotationName;
String finalValue = "t." + fieldName;
/* t.s = t.getIntent().getStringExtra("name") */
String methodContent = finalValue +" = t.getIntent().";
// 添加后面的getIntExtra、 getStringExtra等 使用TypeKind
if(type == TypeKind.INT.ordinal()){
// int 类型
// t.s = t.getIntent().getIntExtra("age", t.age);
methodContent += "getIntExtra($S " + finalValue + ")";
}else if(type == TypeKind.BOOLEAN.ordinal()){
// boolean 类型
// t.s = t.getIntent().getBooleanExtra("isSuccess", t.age);
methodContent += "getBooleanExtra($S" + finalValue + ")";
}else if(type == TypeKind.DOUBLE.ordinal()){
// Double类型
methodContent += "getDoubleExtra($S" + finalValue + ")";
}else if(type == TypeKind.FLOAT.ordinal()){
// Float类型
methodContent += "getFloatExtra($S" + finalValue + ")";
}else if(type == TypeKind.SHORT.ordinal()){
// short类型
methodContent += "getShortExtra($S" + finalValue + ")";
}else{
// String 类型 t.s = t.getIntent.getStringExtra("s");
// typeMirror.toString() java.lang.String
if(typeMirror.toString().equalsIgnoreCase(ProcessorConfig.STRING)){
// String 类型
methodContent += "getStringExtra($S)"; //没有默认值
}
}
if(methodContent.endsWith(")")){
method.addStatement(methodContent,annotationName);
}else{
messager.printMessage(Diagnostic.Kind.ERROR, "目前暂不支持byte、char类型参数");
}
}
public MethodSpec build(){
return method.build();
}
public static class Builder{
// Messager用来报告错误,警告和其他提示信息
private Messager messager;
// 类名,如:MainActivity
private ClassName className;
// 方法参数体
private ParameterSpec parameterSpec;
public Builder(ParameterSpec parameterSpec) {
this.parameterSpec = parameterSpec;
}
public Builder setMessager(Messager messager) {
this.messager = messager;
return this;
}
public Builder setClassName(ClassName className) {
this.className = className;
return this;
}
public ParameterFactory build() {
if (parameterSpec == null) {
throw new IllegalArgumentException("parameterSpec方法参数体为空");
}
if (className == null) {
throw new IllegalArgumentException("方法内容中的className为空");
}
if (messager == null) {
throw new IllegalArgumentException("messager为空,Messager用来报告错误、警告和其他提示信息");
}
return new ParameterFactory(this);
}
}
}
3.2.2.4 使用@Parameter注解,编写管理类ParameterManager,用于查找使用生成的代码
/**
* 参数管理器
* 第一步 :查找生成的类
* 第二步: 使用生成的类
*
*/
public class ParameterManager {
// 单例
private static ParameterManager instance;
public static ParameterManager getInstance(){
if(instance == null){
synchronized (ParameterManager.class){
if(instance == null){
instance = new ParameterManager();
}
}
}
return instance;
}
private LruCache<String,ParameterGet> cache;
private ParameterManager(){
cache = new LruCache<>(100);
}
// 用于拼接类名,便于查找
static final String FILE_SUFFIX_NAME = "$$Parameter";
/**
* 这个方法就是用于调用 生成的类中的getParameter方法
* @param activity
*/
public void loadParameter(Activity activity){
String className = activity.getClass().getName();
ParameterGet parameterGet = cache.get(className);
if(null == parameterGet){
try {
Class<?> aClass = Class.forName(className + FILE_SUFFIX_NAME);
parameterGet = (ParameterGet) aClass.newInstance();
cache.put(className,parameterGet);
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
}
parameterGet.getParameter(activity);
}
}
3.2.3 使用前面的@ARouter注解
穿插一点组件化相关的Gradle的知识:通过Gradle实现集成环境和组件环境的自动部署
上面讲过,单层分包的项目,如果很大的话,改一个功能,就要编译运行一整个项目,耗费很多的时间,组件化很好的解决了这个问题。组件化的一大特色,是每个模块可以独立运行,很方便。在正式环境中,所有的组件依赖App运行,在测试环境中,所有的组件作为一个Module独立运行,听起来很棒,其实实现起来也很简单,通过Gradle,只需要修改一个变量值就可以实现。
有关于Gradle的知识,会在以后的(还没写)《LCODER系列之Gradle》中详细讲解
a) 集成环境和组件环境的区别
Module :
apply plugin: 'com.android.application'
applicationId "com.kimliu.customarouter"
Library:
apply plugin: 'com.android.library'
applicationId 无
b) 创建一个全局的公共文件Config.gradle
这个文件中,ext扩展块中,提供一个boolean类型的变量,这个变量用于控制是在集成环境还是组件化环境。
// 配置gradle
// 扩展块
ext{
// 区分 正式环境 和 测试环境
isRelease = false
// 正式环境和测试环境的BaseUrl
url=[
"debug":"",
"release":""
]
// 建立Map存储 key和value都是自定义的 记录版本号
androidID = [
compileSdkVersion:30,
buildToolsVersion:"30.0.2",
applicationId:"com.kimliu.todoapp",
minSdkVersion:21,
targetSdkVersion:30,
versionCode:1,
versionName:"1.0",
testInstrumentationRunner: "androidx.test.runner.AndroidJUnitRunner"
]
// 每个Library的包名
appID=[
app:"com.kimliu.todoapp",
note:"com.kimliu.note",
task:"com.kimliu.task",
setting:"com.kimliu.setting",
account:"com.kimliu.account"
]
dependenciesID=[
"appcompat" : "androidx.appcompat:appcompat:1.2.0",
"constraintlayout": "androidx.constraintlayout:constraintlayout:2.0.1",
"material" : "com.google.android.material:material:1.1.0",
"vectordrawable" : "androidx.vectordrawable:vectordrawable:1.1.0",
"fragment" : "androidx.navigation:navigation-fragment:2.2.2",
"ui" : "androidx.navigation:navigation-ui:2.2.2",
"extensions" : "androidx.lifecycle:lifecycle-extensions:2.2.0",
]
}
c) 创建一个Library使用的公共文件LibraryConfig.gradle,讲Library的Gradle文件中的内容都抽取出来。
在这个文件中,要对apply plugin进行处理。
// Library 公用的gradle
// 在这里动态的切换集成化环境和组件化环境
if (isRelease) {
// 如果是发布版本时,各个模块都不能独立运行
apply plugin: 'com.android.library' // 正式环境 library不能独立运行
} else {
apply plugin: 'com.android.application' // 测试环境 application独立运行
}
android {
compileSdkVersion androidID.compileSdkVersion
buildToolsVersion androidID.buildToolsVersion
defaultConfig {
minSdkVersion androidID.minSdkVersion
targetSdkVersion androidID.targetSdkVersion
versionCode androidID.versionCode
versionName androidID.versionName
testInstrumentationRunner androidID.testInstrumentationRunner
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
sourceSets{
main{
// 可以用于切换正式环境和测试环境的AndroidManifest.xml文件
// 创建不同的AndroidManifest.xml文件 位于不同的路径中
if(isRelease){
// 正式环境 使用正式环境的AndroidManifest.xml文件
manifest.srcFile "src/main/AndroidManifest.xml"
}else{
// 测试环境,debug的AndroidManifest.xml文件生效
manifest.srcFile 'src/main/debug/AndroidManifest.xml'
}
java{
// release 时,目录下的代码不要打包进apk
exclude "**/debug/**" // debug文件夹下的所有文件不要打包进apk
}
}
}
}
dependencies {
// Groovy中的for循环 依赖Config.gradle文件中dependenciesID数组中的所有
dependenciesID.each {k,v -> implementation v}
implementation project(":common") //依赖公共工具库
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
d) 对这两个公共文件的使用
在项目的build.gradle文件中引入Config.gradle,在文件的最上方引用即可
// 根目录下 引入公共的配置Gradle
apply from : 'config.gradle'
在Library的build.gradle文件中引入Library.gradle,在文件最上方引用即可
// 填写正确的文件路径
apply from : '../libraryConfig.gradle'
e)在Library中处理applicationId
apply from : '../libraryConfig.gradle'
android {
defaultConfig {
// applicationId "" // 有appid 能够独立运行
if (!isRelease) {
// 测试环境 能够独立运行 必须要有appID
applicationId appID.account // 组件化模式能独立运行才能有applicationId
}else{
// 正式环境,不能够独立运行,不需要有appid
}
}
}
f) 在app的build.gradle文件中处理依赖关系
apply plugin: 'com.android.application'
def androidID = rootProject.ext.androidID
android {
compileSdkVersion androidID.compileSdkVersion
buildToolsVersion androidID.buildToolsVersion
defaultConfig {
applicationId appID.app
minSdkVersion androidID.minSdkVersion
targetSdkVersion androidID.targetSdkVersion
versionCode androidID.versionCode
versionName androidID.versionName
testInstrumentationRunner androidID.testInstrumentationRunner
//让Java代码也可以用 给Java代码标记,正式环境和测试环境的标记
buildConfigField("boolean","isRelease",String.valueOf(isRelease))
}
buildTypes {
// 切换debug 和 release
debug{
buildConfigField("String","debug","\"${url.debug}\"")
}
release {
buildConfigField("String","release","\"${url.release}\"")
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
// 循环获取 config.gradle中的 dependenciesID
dependenciesID.each {k,v -> implementation v}
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
if(isRelease){
// 正式环境 需要让组件们 依附app
implementation project(":order")
implementation project(":personal")
}else{
// 测试环境 不需要依附app 每一个module都是一个独立的存在 可以独立运行
}
}