前言:
组件化是安卓目前很流行的一门技术,其目的是避免复杂的业务逻辑交织到一起,相互影响。通过解耦,让每个子项目都是一个独立的工程,即使其余模块出现问题,也不会影响这个子模块的运行。
本篇系“利用APT技术实现安卓组件化的解耦”的下篇,主要讲解如何利用APT技术,实现路由框架的彻底解耦。
一.为什么组件化需要路由功能?
1.1为什么要实现组件化?
首先为什么要组件化?组件化的目的就是为了使各个模块之间相互不依赖,每个模块都是独立的部分,这样大大降低了逻辑复杂度,也在一定程度上降低了一个模块出错后对其它模块的影响。
1.2什么是路由?
由于模块之间相互不依赖了,那么模块之间如果有通讯的需求怎么办?这时候就要通过路由来实现了,就像是浏览器中请求网站,首先通过一个固定的域名请求DNS服务器,DNS返回一个IP地址,然后根据这个IP地址就可以找到对应的网站,然后访问网站中的内容。
而路由就类似于这个DNS服务器,所有模块向其进行注册。模块之间如果发起请求的时候,也向这个路由发起请求,由路由决定其最终由谁来执行。这一点,和系统中ServiceManager的作用很像。
1.3APT为什么能帮助我们实现组件化?
既然需要路由,那么路由和APT又有什么关系呢?APT是为了帮助我们降低代码编写难度,用最简单的代码完成功能,因为代码少了,使用更简单了,自然出问题的可能性就更小了。
为了方便理解,我们先举一个不使用APT技术的路由例子。
首先,我们每个路由类都要进行手动注册,RouterHanlder提供注册方法。如果忘了注册,那么这个注册类就无效了。
Map<String, RouterBase> map2 = new HashMap<>();
public void register(String moduleName,RouterBase routerBase){
map2.put(moduleName,routerBase);
}
其次,因为主模块对于子模块没有依赖关系,所以主模块不能直接调用子模块路由中的方法名。所以,只能通过如下形式进行调用,每个路由当中都实现一个统一的方法handleAction
public void jump2(String moduleName, String actionName, Object args) throws Exception {
RouterBase routerBase = map2.get(moduleName);
routerBase.handleAction(actionName,args);
}
然后,每个路由的handleAction方法当中,在进行具体的分法操作:
public void handleRouter(String action, Object obj) {
if (action.equals("router1_action1")) {
String str = String.valueOf(obj);
handleRouter1Action1(str);
return;
}
if (action.equals("router1_action2")) {
String str = String.valueOf(obj);
handleRouter1Action2(str);
return;
}
}
这些代码都需要手动编译,手动编译就容易出错,而且费时费力。而APT技术就是在编译期帮助我们自动去生成这些代码的。降低了出错的可能性,并且编程编的更便捷。
二.APT实现字节码的动态生成
2.1 如何设计?
首先,我们应该通过注解识别到路由类和路由方法;
然后,我们针对每一个路由类,生成一个路由代理类执行相关功能。
这里为什么要使用路由代理类,而不是直接使用路由类呢?因为代理类的可扩展性更高,我们可以通过切面,进行参数转换,以及各种日志的纪录等等。举个例子,路由类如下:
@Route(moduleName = "b_router")
public class MyRouter2 implements RouterBase {
@RouteMethod(methodKey = "router2_action1")
public void handleRouter2Action1(String text) {
ToastUtil.showCenterToast(text);
Log.i("lxltest", "a_sayhello action,args:" + text);
}
}
而路由代理类可以如下:
public class MyRouter2Proxy implements RouterProxyInter {
MyRouter2 mMyRouter2;
public MyRouter2Proxy(MyRouter2 mMyRouter2){
this.mMyRouter2 = mMyRouter2;
}
public void handleAction(String action, Object args) {
//这里可以实现埋点纪录等等
if("router2_action1".equals(action)) {
//参数转换,转换为代理类所需要的类型
java.lang.String local=String.valueOf(args);
mMyRouter2.handleRouter2Action1(local);
}
}
}
最后,要动态生成路由注册类。
我们要统计一下所有的路由代理类,形成映射关系,汇总到路由注册类上面进行注册。这样,如果有路由的跳转需求,就可以交由路由注册类进行分发了。路由注册类应该是如下的代码:
public class RegisterRouter extends RegisterRouterInter {
@Override
public void init() {
com.xt.client.function.route.MyRouter2Proxy myrouter2 = new com.xt.client.function.route.MyRouter2Proxy(new com.xt.client.function.route.MyRouter2());
map.put("b_router", myrouter2);
com.xt.client.function.route.MyRouter1Proxy myrouter1 = new com.xt.client.function.route.MyRouter1Proxy(new com.xt.client.function.route.MyRouter1());
map.put("a_router", myrouter1);
com.xt.client.function.route.MyRouter3Proxy myrouter3 = new com.xt.client.function.route.MyRouter3Proxy(new com.xt.client.function.route.MyRouter3());
map.put("c_router", myrouter3);
}
}
整个链路流程图如下:
2.1 创建相关注解类
首先,我们创建两个注解Route和RouteMethod,分别作用于路由类和路由方法。
//路由类注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {
String moduleName();
}
//路由方法注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.CLASS)
public @interface RouteMethod {
String methodKey();
}
理论上,可以不需要注解类,凡是被@RouteMethod标记的方法都可以认为是路由的方法,但是实际编写中,我们还是希望模块对外提供的功能有一个收口,所以创建@Route注解,只有被这个注解声明的类,才会扫描其中的@RouteMethod注解。
2.2 路由注册类的生成
首先,我们创建RouterProcessor类来处理所有带@Route注解的路由类。
在process方法中,扫描项目中所有的带@Route标签的类,汇总产生一个集合,然后依次遍历这个集合。
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(Route.class);
Map<String, String> map = new HashMap<>();
for (Element e : elementsAnnotatedWith) {
Route annotation = e.getAnnotation(Route.class);
Element enclosingElement = e.getEnclosingElement();
String packageName = ProcessorUtils.getPackage(enclosingElement);
//生成代理类
generatedProxyClass(packageName, e.getSimpleName().toString(), e);
map.put(annotation.moduleName(), packageName + "." + e.getSimpleName().toString());
}
generatedClass("RegisterRouter", map);
每次遍历的过程中,首先生成这个路由类的代理类(代理类生成介绍在2.3中),然后纪录其注册字和类名路径,最后通过generatedClass方法去生成路由注册类。
一样有几个参数是要动态去获取的,如下:
变量名 | 变量描述 | 获取方式 |
moduleMap | 路由注册字和路由类的映射关系 | 注册字:annotation.moduleName() 路由类地址: packageName + "." + e.getSimpleName().toString() |
完整生成代码如下:
/**
* 针对注解中的内容生成类
* 注册key,类名
*/
private void generatedClass(String fileName, Map<String, String> moduleMap) {
StringBuilder builder = new StringBuilder();
addLine(builder, "package com.xt.client;");
addLine(builder, "import java.util.HashMap;");
addLine(builder, "import java.util.Map;");
addLine(builder, "import java.util.Map;");
addLine(builder, "import com.xt.client.function.route.RouterBase;");
addLine(builder, "import com.xt.client.function.route.RegisterRouterInter;");
for (String value : moduleMap.values()) {
addLine(builder, "import " + value + ";");
}
addLine(builder, "public class " + fileName + " extends RegisterRouterInter {");
addLine(builder, " @Override");
addLine(builder, " public void init() {");
for (String key : moduleMap.keySet()) {
String name = moduleMap.get(key);
String className = name.substring(name.lastIndexOf(".") + 1).toLowerCase();
String createLine = " " + name + "Proxy " + className + " = new " + name + "Proxy(new " + name + "());";
String addLine = " map.put(\"" + key + "\", " + className + ");";
addLine(builder, createLine);
addLine(builder, addLine);
}
addLine(builder, " }");
builder.append("}");
// System.out.println(TAG + ":RouterProcessor:" + builder.toString());
try {
JavaFileObject sourceFile = filer.createSourceFile("RegisterRouter");
Writer writer = sourceFile.openWriter();
writer.append(builder.toString());
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
2.3 路由代理类的生成
我们的目标是生成如下形式的代理类,当然,我们还可以进行添加埋点纪录等等操作,这些都是后续的进一步扩展,这里不展开了,有兴趣的小伙伴可以自己进行相关操作。
public class MyRouter1ProxyDemo implements RouterProxyInter {
MyRouter1 mMyRouter1;
public MyRouter1ProxyDemo(MyRouter1 myRouter1) {
this.mMyRouter1 = myRouter1;
}
public void handleAction(String action, Object args) {
if ("router1_action1".equals(action)) {
String text = String.valueOf(args);
mMyRouter1.handleRouter1Action1(text);
} else if ("router1_action2".equals(action)) {
mMyRouter1.handleRouter1Action2(args);
}
}
}
我们要在APT的过程中,识别到一个@Route声明的类,就去动态生成一个这样的代理类。
这里我们有几个参数是要动态去获取的,如下:
变量名 | 变量描述 | 获取方式 |
packageName | 包名 | 参考ProcessorUtils.getPackage()方法 |
className | 路由类名 | e.getSimpleName() |
proxyName | 代理类名 | className+"proxy" |
paramsType | 方法入参类型 | 下面会讲 |
methodName | 路由方法名 | e.getSimpleName().toString() |
methodKey | 路由关键字 | annotation.methodKey(); |
localName | 方法内变量名 | "m" + className; |
其中我们讲一下如何获取路由方法的参数类型和参数名:
路由方法如下:
@RouteMethod(methodKey = "router1_action1")
public void handleRouter1Action1(String text) {
ToastUtil.showCenterToast(text);
Log.i("lxltest", "a_sayhello action,args:" + text);
}
APT中,所有的类型,比如类,方法,参数等等都是element对象。但是element对象的具体实现类是不一样的。所以,我们首先识别到带有注解的注解类对象classElement,然后通过classElement.getEnclosedElements()方法获到类中所有的方法。然后依次识别方法中是否@RouteMethod注解,如果有,则获取对应方法的参数名和类型。
首先仍然通过方法的Element,方法的Element的最终实现类是Symbol.MethodSymbol对象。强转对象,然后获取其变量params,返回是一个数组,就就代表着参数集合类型,具体代码如下:
private String getType(Element methodElement) {
Symbol.MethodSymbol methodSymbol = (Symbol.MethodSymbol) methodElement;
com.sun.tools.javac.util.List<Symbol.VarSymbol> params = methodSymbol.params;
for (Symbol.VarSymbol varSymbol : params) {
return varSymbol.type.toString();
}
return Object.class.toString();
}
有了各种参数之后,我们就可以做最后的代码拼装了,我这里使用的是createSourceFile的方式,直接使用文本生成java类,完整代码如下:
private void generatedProxyClass(String packageName, String className, Element classElement) {
//获取所有方法
List<? extends Element> enclosedElements = classElement.getEnclosedElements();
StringBuilder builder = new StringBuilder();
String localName = "m" + className;
String proxyName = className + "Proxy";
addLine(builder, "package " + packageName + ";");
addLine(builder, "public class " + proxyName + " implements RouterProxyInter {");
addLine(builder, " " + className + " " + localName + ";");
addLine(builder, " public " + proxyName + "(" + className + " " + localName + "){");
addLine(builder, " " + "this." + localName + " = " + localName + ";");
addLine(builder, " }");
addLine(builder, "");
addLine(builder, " public void handleAction(String action, Object args) {");
boolean isElse = false;
for (Element e : enclosedElements) {
RouteMethod annotation = e.getAnnotation(RouteMethod.class);
if (annotation == null) {
continue;
}
/**
* 参数类型转换
* 首先,获取注解标记的方法类型
*
*/
String paramsType = getType(e);
String typeTransforLine = "";
if (paramsType.equals("java.lang.String")) {
typeTransforLine = paramsType + " local=String.valueOf(args);";
} else if (paramsType.equals("java.lang.Integer")) {
typeTransforLine = paramsType + " local=Integer.parseInt(args);";
} else {
typeTransforLine = "Object local=args;";
}
String methodName = e.getSimpleName().toString();
String methodKey = annotation.methodKey();
//方法中如果带RouteMethod注解,则生成
if (!isElse) {
isElse = true;
builder.append(" if");
} else {
builder.append(" else if");
}
addLine(builder, "(\"" + methodKey + "\".equals(action)) {");
addLine(builder, " " + typeTransforLine);
addLine(builder, " m" + className + "." + methodName + "(local);");
addLine(builder, " }");
}
addLine(builder, " }");
addLine(builder, "}");
// System.out.println(TAG + ",builder:\n" + builder);
JavaFileObject sourceFile = null;
try {
sourceFile = filer.createSourceFile(proxyName);
Writer writer = sourceFile.openWriter();
writer.append(builder.toString());
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
三.实验
完成了通过APT实现代理类的自动生成之后,我们就可以进行实验了。
RouterHandle类注册
首先构建RouterHandle类,由这个类进行模块之间功能的分发,这个功能在主模块中,其余的子模块都应该依赖这个主模块。
这个类中,通过注册反射生成的RegisterRouter类,获取到所有的路由类。
public class RouterHandle {
Map<String, RouterProxyInter> map = new HashMap<>();
RegisterRouterInter routerInter;
public RouterHandle() {
try {
Class<?> aClass = Class.forName("com.xt.client.RegisterRouter");
routerInter = (RegisterRouterInter) aClass.newInstance();
routerInter.init();
map.putAll(routerInter.getRouteMap());
} catch (Exception e) {
e.printStackTrace();
}
}
public void jump(String moduleName, String actionName, Object args) throws Exception {
RouterProxyInter proxyInter = map.get(moduleName);
if (proxyInter == null) {
throw new Exception("RouterBase is unregistered.");
}
routerInter.handleAction(proxyInter, actionName, args);
}
}
我们看一下动态生成的RegisterRouter类:
public class RegisterRouter extends RegisterRouterInter {
@Override
public void init() {
com.xt.client.function.route.MyRouter2Proxy myrouter2 = new com.xt.client.function.route.MyRouter2Proxy(new com.xt.client.function.route.MyRouter2());
map.put("b_router", myrouter2);
com.xt.client.function.route.MyRouter1Proxy myrouter1 = new com.xt.client.function.route.MyRouter1Proxy(new com.xt.client.function.route.MyRouter1());
map.put("a_router", myrouter1);
com.xt.client.function.route.MyRouter3Proxy myrouter3 = new com.xt.client.function.route.MyRouter3Proxy(new com.xt.client.function.route.MyRouter3());
map.put("c_router", myrouter3);
}
}
这个类完成了所有路由类的注册。
RouterHandle中进行事件分发
模块之间进行通讯时,最终都会调用到jump方法,如下:
public void jump(String moduleName, String actionName, Object args) throws Exception {
RouterProxyInter proxyInter = map.get(moduleName);
if (proxyInter == null) {
throw new Exception("RouterBase is unregistered.");
}
routerInter.handleAction(proxyInter, actionName, args);
}
RegisterRouterInter.class:
public void handleAction(RouterProxyInter routerBase, String actionName, Object args) {
routerBase.handleAction(actionName, args);
}
理论上,完全可以去掉moduleName,直接根据actionName的唯一性来找到对应的routerProxy,但是不同模块之间,也允许actionName相同的场景,所以这里添加了moduleName来区分。
最后交给routerProxy的handleAction方法来处理,而RouterProxyInter的实现类,也是动态生成的,如下:
public class MyRouter1Proxy implements RouterProxyInter {
MyRouter1 mMyRouter1;
public MyRouter1Proxy(MyRouter1 mMyRouter1){
this.mMyRouter1 = mMyRouter1;
}
public void handleAction(String action, Object args) {
if("router1_action1".equals(action)) {
java.lang.String local=String.valueOf(args);
mMyRouter1.handleRouter1Action1(local);
}
else if("router1_action2".equals(action)) {
Object local=args;
mMyRouter1.handleRouter1Action2(local);
}
}
}
典型的代理类模式,其持有MyRouter1,最后把请求转交到MyRouter1中的具体方法中,并且还完成了参数类型的转换。当然这里,我们还可以做更多的事,比如增加日志监控,埋点等等。
效果验证:
四个按钮,分别按照下面关系调用
按钮 | 调用代码 | 实现类 | 实现方法 |
button1 | routerHandle.jump("a_router", "router1_action1", "button1 click in a_router") | MyRouter1 | handleRouter1Action1 |
button2 | routerHandle.jump("a_router", "router1_action2", "button2 click in a_router") | MyRouter1 | handleRouter1Action2 |
button3 | routerHandle.jump("b_router", "router2_action1", "button3 click in b_router") | MyRouter2 | handleRouter2Action1 |
button4 | routerHandle.jump("c_router", "router3_action1", "button4 click in c_router") | MyRouter3 | handleRouter3Action1 |
RouteFragment页面显示如下:
点击按钮:执行路由1中方法1后,提示如下:
这个toast正是MyRouter1中的handleRouter1Action1()方法执行的:
@RouteMethod(methodKey = "router1_action1")
public void handleRouter1Action1(String text) {
ToastUtil.showCenterToast(text);
Log.i("lxltest", "a_sayhello action,args:" + text);
}