前言
网络上给大家介绍如何使用ARouter的很多,对ARouter进行分析的也很多,这篇文章更多的是记录我自己的理解,谈谈它的设计思想及解决项目中的什么问题(如果这篇文章能给他人带来哪怕一点帮助,那它也是有价值的----来自一个无证程序员)
ARouter的根基--------APT
很多框架或者现在流行的90%的框架都离不开apt(annotation process tool),那么他到底是个什么东西
它是一个帮助程序在编译期进行部分程序或者代码生成的工具,它的作用就像是jdk 动态代理 只不过动态代理是在后期运行期进行字节码的生成,而apt的文件生成是在javac这一时刻,所以在jvm里面更准确的定位叫"编译器的前端"(这里不会更多的提及后期的JIT)。回想起来现在的框架都是在原有java的基础上扩散开来的,就说编译期生成文件吧其实javac才是编译期的鼻祖,同时aop的思想更多的是jdk的动态代理,ioc的思想更多是来自java spi...(至于是不是真的源自于java都不重要)
javac 都做了什么动作?首先javac 的程序是java写的,在openjdk里面可以看到javac 的过程源码
1 插入式注解处理过程
2 词法语法分析
3 分析及字节码生成
OpenJDK ----langtools:
com.sun.tools.javac.main.JavaCompile
查看compile函数:
public void compile(List sourceFileObjects,List classnames,Iterable processors)
throws IOException// TODO: temp, from JavacProcessingEnvironment
{
if (processors !=null && processors.iterator().hasNext())
explicitAnnotationProcessingRequested =true;
// as a JavaCompiler can only be used once, throw an exception if
// it has been used before.
if (hasBeenUsed)
throw new AssertionError("attempt to reuse JavaCompiler");
hasBeenUsed =true;
start_msec =now();
try {
initProcessAnnotations(processors);
// These method calls must be chained to avoid memory leaks
delegateCompiler = processAnnotations(enterTrees(stopIfError(CompileState.PARSE,parseFiles(sourceFileObjects),classnames);
delegateCompiler.realCompile();
delegateCompiler.close();
elapsed_msec =delegateCompiler.elapsed_msec;
}catch (Abort ex) {
if (devVerbose)
ex.printStackTrace();
}finally {
if (procEnvImpl !=null)
procEnvImpl.close();
}
}
initProcessAnnotations(processors); 插入注解处理器---->这里是不是想到android里面要自己实现AbstractProcessor,
里面包含对应函数:
public synchronized void init(ProcessingEnvironment processingEnv)
public abstract boolean process(Set annotations,RoundEnvironment roundEnv);
接下来processAnnotations 执行真正的注解处理,parseFiles执行词法语法分析
delegateCompiler.realCompile();这里realCompile 是我自己源码里修改过的函数名(原函数名:compile2())
void realCompile() {
try {
switch (compilePolicy) {
case ATTR_ONLY:
attribute(todo);
break;
case CHECK_ONLY:
flow(attribute(todo));
break;
case SIMPLE:
generate(desugar(flow(attribute(todo))));
break;
case BY_FILE: {
Queue>> q =todo.groupByFile();
while (!q.isEmpty() && !shouldStop(CompileState.ATTR)) {
generate(desugar(flow(attribute(q.remove()))));
}
}
break;
case BY_TODO:
while (!todo.isEmpty())
generate(desugar(flow(attribute(todo.remove()))));
break;
default:
assert false:"unknown compile policy";
}
}catch (Abort ex) {
if (devVerbose)
ex.printStackTrace();
}
if (verbose) {
elapsed_msec =elapsed(start_msec);
printVerbose("total", Long.toString(elapsed_msec));
}
reportDeferredDiagnostics();
if (!log.hasDiagnosticListener()) {
printCount("error", errorCount());
printCount("warn", warningCount());
}
}
其中switch 是BY_TODO时 进行generate动作。
已经清楚了javac编译的过程,之所以在android里面使用apt这么流畅不过是google真对processor的过程做了封装.
apt技术的实现这样看来不难,最主要的是在知道利用apt技术来解决项目中的问题,举一个例子:
最初项目里面if else很多,都会尝试使用factory模式来尝试优化重构这种现状,if else在某种意义上是取消不掉的。
class Animal
class Person
class Dog
class Factory{
Animal create(String flag){
if(flag.equals("xxxx")) return Person();
else if(flag.equals("ssss")) return Dog();
else if(xxxxxxxx)
.......
else
}
如果有新的Animal子类对象A产生,除了编写A对象逻辑之外还是要修改 Factory对象,这样的话在某种程度打破了ocp原则,引入了新的代码就有引入新bug的风险。我认为这个时候就可以尝试使用Apt 通过注解,在编译阶段 搜集被注解标记的java,经过自己编写的procssor 组装自己的factory实现,代替手动修改Factory。
结构可能如下:
@Animal
class Person{}
@Animal
class Dog{}
class MyProcessor extends AbstractProcessor{
init(){xxxxx}
process(xxxxx){
生成MyFactory逻辑
}
}
新增的A类 只需要使用@Animal注解标记即可
@Animal
class A{}
以上在经历过 前端编译 之后自然就会把A对象的判断添加到MyFactory的逻辑判断里面
截止到这里为止还是没有提到ARouter的使用(文章开头已经提到了 这不是讲解ARouter使用及原理的文章,而是我在阅读ARouter源码之后的一些思考)。如果只在Android的一个module或者一个app里面确实对设计或者代码进行了优化(最初的app 也都是从小工程衍生)随着业务的扩增,整个app就伴随模块化,组件化,插件化。。。。
那么针对模块化、组件化,ARouter做了一个很巧妙处理(与其说ARouter不如说java) java的先知们使用了getOptions(Map<String,String>)来处理这个问题(不得不佩服java的伟大设计者)。
在ARouter之前我个人确实写了一个类似ARouter的框架但是就在我刚要完成的时候却又实现不了,代码的生成以及模块化代码生成的问题都已经解决了但是在却最后进行合并java的时候除了问题,刚开始的时候想尝试使用gradle在合并编译阶段进行搜索文件,理论上是可行的要注意的一个问题无非就是gradle的后期兼容性问题,然而随着现实中项目的情况就暂行搁置以至于到了后来ARouter的出现也确实让我很自责(我也曾自我反省是不是做什么事都是在最后即将完成的时候总是松懈,高考是这样,自己的很多想法也是这样,不确定这算不算我人性弱点中的一部分),后来我也就是翻看了ARouter的源码,主要是想看看他的设计者是如何处理这个问题的,当看到在
ARouter.init(){
xxxxx
ClassUtils.getFileNameByPackageName(xxxx,xxx)
xxxxx
}
Set<String> getFileNameByPackageName(){
for(xxxxx){
DexFile file = new DexFile(path);
针对 file下的文件根据APT生成的文件名规则进行查找,并放入WareHouse缓存中
}
}
看到了关键了就是DexFile这个操作,而不是想我的想法一样使用gradle在编译阶段进行合并收集这里很佩服ARouter的设计者,在事后我进行反思思维的局限让我根本没有把想法结合到Android这里没有想到使用Android本身的机制来解决问题,或者说我偏离了方向(当然不知道ARouter在阿里进行设计的时候是一群人还是一个人,像我一个人和他人的沟通较少又受限于自己的想法)
给自己以后的建议:有想法还是要和他人分享,让自己广角看设计(非兵不利,战不善,弊在视角)