ARouter 是一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦
https://github.com/alibaba/ARouter/blob/master/README_CN.md
说到路由便不得不提一下Android中的组件化开发思想,组件化是最近比较流行的架构设计方案,它能对代码进行高度的解耦、模块分离等,
极大地提高开发效率。路由和组件化本身没有什么联系,因为路由的责任是负责页面跳转,但是组件化中两个单向依赖的module之间需要互相启动对方的Activity,
因为没有相互引用,startActivity()是实现不了的,必须需要一个协定的通信方式,此时类似ARouter的路由框架就派上用场了。
ARouter实现的核心方式有三个,不了解的查一下资料
- 自定义注解
- 注解处理器(APT,用来在编译时扫描和处理注解)
- JavaPoet(用来生成代码)
不只是ARouter,像如ButterKnife、Dagger、JsBridge等大型框架使用的也是这个方式。
创建注解
Route注解用来声明路由路径,路径至少是两级:
//元注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {
//路由的路径,标识一个路由节点
String path();
//将路由节点进行分组,可以实现按组动态加载
String group() default "";
}
Extra注解用来声明额外信息,实现数据在模块间的传递:
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.CLASS)
public @interface Extra {
String name() default "";
}
RouteMeta用来保存路由信息,通过解析Route注解获取。路由页面有两种类型:1.Activity,代表需要路由的Activity;2.ISERVICE,代表可以传递的ISERVICE对象,实现了ISERVICE的接口,都可以通过路由来传递。
public class RouteMeta {
//路由页面的类型:Activity或者IService
public enum Type {
ACTIVITY,
ISERVICE
}
private Type type;
//节点 (Activity)
private Element element;
//注解使用的类对象
private Class> destination;
//路由地址
private String path;
//路由分组
private String group;
...
}
注解处理
compiler模块使用Javax的Processor来处理注解,使用google的AutoService来自动编译Processor类,使用javapoet来生成Java类。Javax是Java的库,Android不支持,所以compiler模块是Java的library模块。
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
implementation 'com.squareup:javapoet:1.11.1'
implementation project(':router_annotation')
}
// java控制台输出中文乱码
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
//Android对Java1.8的兼容性不好,所以指定1.7版本
sourceCompatibility = "7"
targetCompatibility = "7"
声明一个RouteProcessor继承Processor,用来处理Route注解。
/**
* 在这个类上添加了@AutoService注解,它的作用是用来生成
* META-INF/services/javax.annotation.processing.Processor文件的,
* 也就是我们在使用注解处理器的时候需要手动添加
* META-INF/services/javax.annotation.processing.Processor,
* 而有了@AutoService后它会自动帮我们生成。
* AutoService是Google开发的一个库,使用时需要在
* factory-compiler中添加依赖
*/
@AutoService(Processor.class) //注册注解处理器
/**
* 处理器接收的参数 替代 {@link AbstractProcessor#getSupportedOptions()} 函数
*/
@SupportedOptions(Consts.ARGUMENTS_NAME)
/**
* 指定使用的Java版本 替代 {@link AbstractProcessor#getSupportedSourceVersion()} 函数
* 声明我们注解支持的JDK的版本
*/
@SupportedSourceVersion(SourceVersion.RELEASE_7)
/**
* 注册给哪些注解的 替代 {@link AbstractProcessor#getSupportedAnnotationTypes()} 函数
* 声明我们要处理哪一些注解 该方法返回字符串的集合表示该处理器用于处理哪些注解
*/
@SupportedAnnotationTypes({Consts.ANN_TYPE_ROUTE})
public class RouteProcessor extends AbstractProcessor {
/**
* key:组名 value:类名
*/
private Map rootMap = new TreeMap<>();
/**
* 分组 key:组名 value:对应组的路由信息
*/
private Map> groupMap = new HashMap<>();
/**
* 节点工具类 (类、函数、属性都是节点)
*/
private Elements elementUtils;
/**
* type(类信息)工具类
*/
private Types typeUtils;
/**
* 文件生成器 类/资源
*/
private Filer filerUtils;
/**
* 参数
*/
private String moduleName;
private Log log;
/**
* 初始化 从 {@link ProcessingEnvironment} 中获得一系列处理器工具
*
* @param processingEnvironment
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
//获得apt的日志输出
log = Log.newLog(processingEnvironment.getMessager());
log.i("init()");
elementUtils = processingEnv.getElementUtils();
typeUtils = processingEnvironment.getTypeUtils();
filerUtils = processingEnv.getFiler();
//参数是模块名 为了防止多模块/组件化开发的时候 生成相同的 xx$$ROOT$$文件
Map options = processingEnv.getOptions();
if (!Utils.isEmpty(options)) {
moduleName = options.get(Consts.ARGUMENTS_NAME);
}
log.i("RouteProcessor Parmaters:" + moduleName);
if (Utils.isEmpty(moduleName)) {
throw new RuntimeException("Not set Processor Parmaters.");
}
}
/**
* 相当于main函数,正式处理注解
*
* @param set 使用了支持处理注解 的节点集合
* @param roundEnvironment 表示当前或是之前的运行环境,可以通过该对象查找找到的注解。
* @return true 表示后续处理器不会再处理(已经处理)
*/
@Override
public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
//使用了需要处理的注解
if (!Utils.isEmpty(set)) {
//获取所有被 Route 注解的元素集合
Set extends Element> routeElements = roundEnvironment.getElementsAnnotatedWith
(Route.class);
//处理 Route 注解
if (!Utils.isEmpty(routeElements)) {
try {
log.i("Route Class: ===" + routeElements.size());
parseRoutes(routeElements);
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
return false;
}
private void parseRoutes(Set extends Element> routeElements) throws IOException {
//支持配置路由类的类型
TypeElement activity = elementUtils.getTypeElement(Consts.ACTIVITY);
//节点自描述 Mirror
TypeMirror type_Activity = activity.asType();
log.i("Route Class: ===" + type_Activity);
TypeElement iService = elementUtils.getTypeElement(Consts.ISERVICE);
TypeMirror type_IService = iService.asType();
/**
* groupMap(组名:路由信息)集合
*/
//声明 Route 注解的节点 (需要处理的节点 Activity/IService)
for (Element element : routeElements) {
//路由信息
RouteMeta routeMeta;
// 使用Route注解的类信息
TypeMirror tm = element.asType();
log.i("Route Class: " + tm.toString());
Route route = element.getAnnotation(Route.class);
//是否是 Activity 使用了Route注解
if (typeUtils.isSubtype(tm, type_Activity)) {
routeMeta = new RouteMeta(RouteMeta.Type.ACTIVITY, route, element);
} else if (typeUtils.isSubtype(tm, type_IService)) {
routeMeta = new RouteMeta(RouteMeta.Type.ISERVICE, route, element);
} else {
throw new RuntimeException("[Just Support Activity/IService Route] :" + element);
}
//分组信息记录 groupMap 集合
categories(routeMeta);
}
//生成类需要实现的接口
TypeElement iRouteGroup = elementUtils.getTypeElement(Consts.IROUTE_GROUP);
log.i("---------" + iRouteGroup.getSimpleName());
TypeElement iRouteRoot = elementUtils.getTypeElement(Consts.IROUTE_ROOT);
/**
* 生成Group类 作用:记录 <地址,RouteMeta路由信息(Class文件等信息)>
*/
generatedGroup(iRouteGroup);
/**
* 生成Root类 作用:记录 <分组,对应的Group类>
*/
generatedRoot(iRouteRoot, iRouteGroup);
}
private void generatedGroup(TypeElement iRouteGroup) throws IOException {
//参数 Map
ParameterizedTypeName atlas = ParameterizedTypeName.get(
ClassName.get(Map.class),
ClassName.get(String.class),
ClassName.get(RouteMeta.class)
);
//参数 Map atlas
ParameterSpec groupParamSpec = ParameterSpec.builder(atlas, "atlas")
.build();
//遍历分组,每一个分组创建一个 $$Group$$ 类
for (Map.Entry> entry : groupMap.entrySet()) {
/**
* 类成员函数loadInfo声明构建
*/
//函数 public void loadInfo(Map atlas)
MethodSpec.Builder loadIntoMethodOfGroupBuilder = MethodSpec.methodBuilder
(Consts.METHOD_LOAD_INTO)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(groupParamSpec);
//分组名 与 对应分组中的信息
String groupName = entry.getKey();
List groupData = entry.getValue();
//遍历分组中的条目 数据
for (RouteMeta routeMeta : groupData) {
// 组装函数体:
// atlas.put(地址,RouteMeta.build(Class,path,group))
// $S https://github.com/square/javapoet#s-for-strings
// $T https://github.com/square/javapoet#t-for-types
loadIntoMethodOfGroupBuilder.addStatement(
"atlas.put($S, $T.build($T.$L,$T.class, $S, $S))",
routeMeta.getPath(),
ClassName.get(RouteMeta.class),
ClassName.get(RouteMeta.Type.class),
routeMeta.getType(),
ClassName.get((TypeElement) routeMeta.getElement()),
routeMeta.getPath().toLowerCase(),
routeMeta.getGroup().toLowerCase());
}
// 创建java文件($$Group$$) 组
String groupClassName = Consts.NAME_OF_GROUP + groupName;
JavaFile.builder(Consts.PACKAGE_OF_GENERATE_FILE,
TypeSpec.classBuilder(groupClassName)
.addSuperinterface(ClassName.get(iRouteGroup))
.addModifiers(PUBLIC)
.addMethod(loadIntoMethodOfGroupBuilder.build())
.build()
).build().writeTo(filerUtils);
log.i("Generated RouteGroup: " + Consts.PACKAGE_OF_GENERATE_FILE + "." +
groupClassName);
//分组名和生成的对应的Group类类名
rootMap.put(groupName, groupClassName);
}
}
private void generatedRoot(TypeElement iRouteRoot, TypeElement iRouteGroup) throws IOException {
//类型 Map> routes>
//Wildcard 通配符
ParameterizedTypeName routes = ParameterizedTypeName.get(
ClassName.get(Map.class),
ClassName.get(String.class),
ParameterizedTypeName.get(
ClassName.get(Class.class),
WildcardTypeName.subtypeOf(ClassName.get(iRouteGroup))
)
);
//参数 Map> routes> routes
ParameterSpec rootParamSpec = ParameterSpec.builder(routes, "routes")
.build();
//函数 public void loadInfo(Map> routes> routes)
MethodSpec.Builder loadIntoMethodOfRootBuilder = MethodSpec.methodBuilder
(Consts.METHOD_LOAD_INTO)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(rootParamSpec);
//函数体
for (Map.Entry entry : rootMap.entrySet()) {
loadIntoMethodOfRootBuilder.addStatement("routes.put($S, $T.class)", entry
.getKey(), ClassName.get(Consts.PACKAGE_OF_GENERATE_FILE, entry.getValue
()));
}
//生成 $Root$类
String rootClassName = Consts.NAME_OF_ROOT + moduleName;
JavaFile.builder(Consts.PACKAGE_OF_GENERATE_FILE,
TypeSpec.classBuilder(rootClassName)
.addSuperinterface(ClassName.get(iRouteRoot))
.addModifiers(PUBLIC)
.addMethod(loadIntoMethodOfRootBuilder.build())
.build()
).build().writeTo(filerUtils);
log.i("Generated RouteRoot: " + Consts.PACKAGE_OF_GENERATE_FILE + "." + rootClassName);
}
private void categories(RouteMeta routeMeta) {
if (routeVerify(routeMeta)) {
log.i("Group Info, Group Name = " + routeMeta.getGroup() + ", Path = " +
routeMeta.getPath());
List routeMetas = groupMap.get(routeMeta.getGroup());
//如果未记录分组则创建
if (Utils.isEmpty(routeMetas)) {
List routeMetaSet = new ArrayList<>();
routeMetaSet.add(routeMeta);
groupMap.put(routeMeta.getGroup(), routeMetaSet);
} else {
routeMetas.add(routeMeta);
}
} else {
log.i("Group Info Error: " + routeMeta.getPath());
}
}
/**
* 验证路由信息必须存在path(并且设置分组)
*
* @param meta raw meta
*/
private boolean routeVerify(RouteMeta meta) {
String path = meta.getPath();
String group = meta.getGroup();
//路由地址必须以 / 开头
if (Utils.isEmpty(path) || !path.startsWith("/")) {
return false;
}
//如果没有设置分组,以第一个 / 后的节点为分组(所以必须path两个/)
if (Utils.isEmpty(group)) {
String defaultGroup = path.substring(1, path.indexOf("/", 1));
if (Utils.isEmpty(defaultGroup)) {
return false;
}
meta.setGroup(defaultGroup);
return true;
}
return true;
}
}
在process方法(注解的处理方法)中遍历被Route注解的节点,先判断节点的类型,再使用RouteMeta保存节点信息,然后验证节点的路由地址是否符合规则,然后使用categories() 根据分组名来保存节点信息到groupMap中;在process方法(注解的处理方法)中遍历被Route注解的节点,先判断节点的类型,再使用RouteMeta保存节点信息,然后验证节点的路由地址是否符合规则,然后使用categories() 根据分组名来保存节点信息到groupMap中;
然后,遍历groupMap中的节点信息,使用javapoet工具,根据分组名生成一个继承IRouteGroup接口的Java类,叫做分组信息类,用来保存每个分组的路由信息,比如:
public class ToolRouter$$Group$$demo1 implements IRouteGroup {
@Override
public void loadInto(Map atlas) {
atlas.put("/demo1/activityToolRouterDemo1", RouteMeta.build(RouteMeta.Type.ACTIVITY,ActivityToolRouterDemo1.class, "/demo1/activitytoolrouterdemo1", "demo1"));
atlas.put("/demo1/activityToolRouterDemo2", RouteMeta.build(RouteMeta.Type.ACTIVITY,ActivityToolRouterDemo2.class, "/demo1/activitytoolrouterdemo2", "demo1"));
}
}
根据模块名生成一个继承IRouteRoot接口的Java类,叫做表信息类,用来保存所有的分组信息类,比如:
public class ToolRouter$$Root$$DemoComponent1 implements IRouteRoot {
@Override
public void loadInto(Map> routes) {
routes.put("demo1", ToolRouter$$Group$$demo1.class);
routes.put("main", ToolRouter$$Group$$main.class);
}
}
Extra注解同理,生成下面类:
public class ActivityToolRouterDemo1$$Extra implements IExtra {
@Override
public void loadExtra(Object target) {
ActivityToolRouterDemo1 t = (ActivityToolRouterDemo1) target;
t.testService1 = (TestService) ToolRouter.getInstance().build("/main/service1").navigation();
t.testService2 = (TestService) ToolRouter.getInstance().build("/main2/service2").navigation();
}
}
初始化路由表
定义一个路由表Warehouse,来保存所有的路由信息。
public class Warehouse {
// root 映射表 保存分组信信息类
static Map> groupsIndex = new HashMap<>();
// group 映射表 保存所有的路由页面
static Map routes = new HashMap<>();
// group 映射表 保存所有的IService对象
static Map services = new HashMap<>();
}
然后,遍历所有的dex文件,查找所有的使用javapoet生成的分组信息类和表信息类。查找是一个耗时操作,使用ThreadPoolExecutor维护一个线程池,来查找所有包名匹配的类,并使用使用同步计数器来判断查找是否完成。
public static Set getFileNameByPackageName(Application context, final String
packageName) throws PackageManager.NameNotFoundException, IOException,
InterruptedException {
final Set classNames = new HashSet<>();
List paths = getSourcePaths(context);
//使用同步计数器判断均处理完成
final CountDownLatch parserCtl = new CountDownLatch(paths.size());
ThreadPoolExecutor threadPoolExecutor = DefaultPoolExecutor.newDefaultPoolExecutor(paths
.size());
for (final String path : paths) {
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
DexFile dexfile = null;
try {
//加载 apk中的dex 并遍历 获得所有包名为 {packageName} 的类
dexfile = new DexFile(path);
Enumeration dexEntries = dexfile.entries();
while (dexEntries.hasMoreElements()) {
String className = dexEntries.nextElement();
if (className.startsWith(packageName)) {
classNames.add(className);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
...
//释放1个
parserCtl.countDown();
}
}
});
}
//等待执行完成
parserCtl.await();
return classNames;
}
```
然后调用表信息类的loadInfo方法,将所有的分组信息类存放到Warehouse的groupsIndex中。
```java
private static void loadInfo() {
//获得所有 apt生成的路由类的全类名 (路由表)
Set routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
for (String className : routerMap) {
if (className.startsWith(ROUTE_ROOT_PAKCAGE + "." + SDK_NAME + SEPARATOR +
SUFFIX_ROOT)) {
// root中注册的是分组信息 将分组信息加入仓库中
((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto
(Warehouse.groupsIndex);
}
}
}
创建跳卡
先根据路由地址生成一个继承RouteMeta的跳卡Postcard,在Postcard中保存路由地址、分组名以及要传递的数据。
调用Postcard的withXXX方法,来添加要传递的数据,保存Postcard的一个Bundle对象中,比如:
public Postcard withString(@Nullable String key, @Nullable String value) {
mBundle.putString(key, value);
return this;
}
public Postcard withBoolean(@Nullable String key, boolean value) {
mBundle.putBoolean(key, value);
return this;
}
然后调用Postcard的navigation()进行页面跳转。
跳转页面
调用Postcard的navigation()进行页面跳转时,如果是activity页面,就直接调转;如果是IService页面,就返回这个IService对象。
先根据分组名从Warehouse的groupsIndex中找到分组信息类,创建这个类,调用它的loadInfo(),把这个分组所有的路由页面,按照路由路径添加到Warehouse.routes中。
//创建并调用 loadInto 函数,然后记录在仓库
Class extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(card
.getGroup());
if (null == groupMeta) {
throw new NoRouteFoundException("没找到对应路由: " + card.getGroup() + " " +
card.getPath());
}
IRouteGroup iGroupInstance;
try {
iGroupInstance = groupMeta.getConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException("路由分组映射表记录失败.", e);
}
iGroupInstance.loadInto(Warehouse.routes);
//已经准备过了就可以移除了 (不会一直存在内存中)
Warehouse.groupsIndex.remove(card.getGroup());
然后根据路由路径查找路由页面的的class对象,保存在Postcard中。如果路由页面是IService实现类,还要将它保存在Postcard和Warehouse的services中。
//路由页面的class对象类
card.setDestination(routeMeta.getDestination());
//设置路由页面的类型(activity 或IService实现类)
card.setType(routeMeta.getType());
...
Class> destination = routeMeta.getDestination();
IService service = Warehouse.services.get(destination);
if (null == service) {
try {
service = (IService) destination.getConstructor().newInstance();
Warehouse.services.put(destination, service);
} catch (Exception e) {
e.printStackTrace();
}
}
card.setService(service);
然后,进行跳转。如果路由页面是activity,就根据路由页面的class对象生成Intent,并且添加需要传递的数据,再调用startActivity()就可以实现跳转了。如果路由页面是IService,就返回postcard.getService()。
switch (postcard.getType()) {
case ACTIVITY:
final Context currentContext = null == context ? mContext : context;
final Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());
int flags = postcard.getFlags();
if (-1 != flags) {
intent.setFlags(flags);
} else if (!(currentContext instanceof Activity)) {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
//可能需要返回码
if (requestCode > 0) {
ActivityCompat.startActivityForResult((Activity) currentContext, intent,
requestCode, postcard.getOptionsBundle());
} else {
ActivityCompat.startActivity(currentContext, intent, postcard
.getOptionsBundle());
}
if ((0 != postcard.getEnterAnim() || 0 != postcard.getExitAnim()) &&
currentContext instanceof Activity) {
//老版本
((Activity) currentContext).overridePendingTransition(postcard
.getEnterAnim()
, postcard.getExitAnim());
}
//跳转完成
if (null != callback) {
callback.onArrival(postcard);
}
}
});
break;
case ISERVICE:
return postcard.getService();
default:
break;
}
接受传递数据
使用调用Postcard的withXXX方法,就可以传递数据。接收传递的数据,需要通过Extra注解实现。
首先,在需要接收路由数据的页面,根据withXXX方法中传递key和数据类型,声明一个成员属性,并添加Extra注解,比如:
@Extra
String a;
@Extra
int b;
@Extra
short c;
然后,调用ToolRouter.getInstance().inject(this)来加载Extra信息。根据Activity的类名+Extra,然后创建这个Extra信息类的对象,调用它的loadExtra()方法,给Activity的key1赋值。
public void loadExtra(Object target) {
ActivityToolRouterDemo2 t = (ActivityToolRouterDemo2)target;
t.key1 = t.getIntent().getLongExtra("key1", t.key1);
t.key2 = t.getIntent().getStringExtra("key2");
}