动手实现一个路由框架
前言
组件化或者模块化开发模式,已逐渐成为热浪的形式,使用这些模式可以让我们程序更容易的扩展、更方便的测试与维护
ARouter
是:阿里巴巴自研路由框架,主要解决组件间、模块间的 界面跳转 问题。我们将自己简单实现一个路由
这个项目对你有什么帮助?
让我更加熟悉注解与注解编译器的过程,也让我们对以后项目也有了更大的可能延展性
构思
让我们自己先想着如何实现,本身实现并不复杂。 新建一个Arouter类
Arouter
- 既然在使用Arouter的时候我们的调用方式是根据Path来的,那么我们肯定需要一个Map<String,Class<? extend Activity> class> 来装载这个集合
- 然后我们要对外开启一个方法来把Activity Put进来。
- 处理跳转,根据map.get(Path) 来跳到相应的Activity 中去。
就这三步,是不是看起来很容易。 问题就只在于如何生成一个自动添加Activity的工具类,然后自动的去执行这个方法。
项目结构
分为4个模块
annotation 模块:用来定义注解
compiler 模块:用来定义Annotation Processor
app 模块 : 主模块,也是Activity测试模块
-
Arouter模块: 路由模块
为什么要分这么多模块?其中 app 模块是用来测试的,测试新定义的 Annotation Processor 能否成功运行。两者的目的并不一样,annotation 是专门定义注解的,而 compiler 是处理注解的。最重要的是,annotation(RetentionPolicy.CLASS,RetentionPolicy.RUNTIME)是需要被编译到 app 项目的 class 文件中的,而 compiler 没有必要进入 app 中。所以在gradle中依赖为compileOnly
- 在annotation模块中创建一个注解文件,通过这个注解获取path
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface BindPath {
String value();
}
- 有了注解我们还需要一个compiler 替我们去生成对应的代码, 我们在创建一个AnotationCompiler类. 编写注解处理器,在编译期找到加入注解的类文件,进行处理
@AutoService(Processor.class) //注册注解处理器
public class AnotationCompiler extends AbstractProcessor {
private Filer mFiler;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mFiler = processingEnvironment.getFiler();
System.out.println("jsc");
}
/**
* 声明这个注解处理器要识别处理的注解
* @return
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new HashSet<>();
types.add(BindPath.class.getCanonicalName());
return types;
}
/**
* 支持java的源版本
* @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return processingEnv.getSourceVersion();
}
/**
* 核心方法
* @param set
* @param roundEnvironment
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
System.out.println("123");
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindPath.class);
Map<String,String> map =new HashMap<>();
for (Element element : elementsAnnotatedWith) {
TypeElement typeElement = (TypeElement) element;
BindPath bindPath = typeElement.getAnnotation(BindPath.class);
//对应的key
String value = bindPath.value();
//获取带包名的类名
String activityName = typeElement.getQualifiedName().toString();
map.put(value,activityName);
}
if (map.size()>0) {
Writer writer=null;
String activityNmae = "ActivityUtil"+System.currentTimeMillis();
try {
JavaFileObject classFile = mFiler.createSourceFile("com.example.util." + activityNmae);
writer = classFile.openWriter();
writer.write("package com.example.util;\n" +
"\n" +
"import com.example.arouter2.ARouter;\n" +
"import com.example.arouter2.IARouter;\n" +
"\n" +
"/**\n" +
" * @author AlienChao\n" +
" * @date 2019/11/19 17:34\n" +
" */\n" +
"public class "+activityNmae+" implements IARouter {\n" +
" @Override\n" +
" public void putActivity() {");
Iterator<String> iterator = map.keySet().iterator();
while (iterator.hasNext()) {
String path = iterator.next();
String value = map.get(path);
writer.write("\n ARouter.getInstance().putActivity(\""+path+"\","+value +".class);");
}
writer.write(" \n}\n" +
"}");
} catch (Exception e) {
e.printStackTrace();
}finally {
if (null!=writer) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return false;
}
}
如代码中所示,要想在编译期对注解做处理,就需要AnnotationCompiler继承自AbstractProcessor并通过@AutoService注解进行注册,然后实现process()方法。process()方法里的set集合就是编译期扫描代码得到的加入了BindPath注解的文件集合
这里需要注意的是谷歌的 auto service, 在As 3.4- 之前是用1.0-rc3 在As3.4.1+ gradle5.1.1 之后用1.0-rc4。
- compileOnly 所需的只是 @AutoService 这个注解。表示只参与编译过程并不打包到最终产物 jar 文件中,而注解 @AutoService 的 RetentionPolicy 就是 SOURCE,不会编译生成到 class 文件中。即便使用 implementation依赖,最终生成的 jar 包中只会多了 auto service 提供的注解,其他 class 文件部分不受影响,因此可以使用 compileOnly 只参与编译过程。
- annotationProcessor 这个是新版 gradle 提供的 java plugin 内置的配置项,代替了早期第三方提供的 android-apt 插件。而且,敲重点,在 gradle 5.+ 中将 Annotation Processor 从编译期 classpath 中去除了,javac 也就无法发现 Annotation Processor。此处如果按照 gradle 4.+ 的写法,只写一个 compileOnly 是无法使用 auto service 的 Annotation Processor 的。必须要使用 annotationProcessor 来配置 Annotation Processor 使其生效。
生成完了,那怎么去执行这个代码呢??
这里就比较粗暴一点, 直接 根据完整路径获取dex,遍历dex获取所有带包名的类名, 有了类名我们就好办了,可以根据类名 判断是否是IARouter 的实现类,去调用 putActivity() 方法
private List<String> getClassName(String packageName) {
//创建一个class对象的集合
List<String> classList = new ArrayList<>();
String path = null;
try {
//通过包管理器 获取到应用信息类然后获取到APK的完整路径
path = context.getPackageManager().getApplicationInfo(
context.getPackageName(), 0).sourceDir;
//根据APK的完整路径获取到编译后的dex文件目录
DexFile dexfile = new DexFile(path);
// 获得编译后的dex文件中的所有class
Enumeration entries = dexfile.entries();
//然后进行遍历
int index = 0;
while (entries.hasMoreElements()) {
index ++;
//通过遍历所有的class的包名
String name = (String) entries.nextElement();
// 判断类的包名是否符合 com.dongnao.util
if(name.contains(packageName)){
//如果符合 就添加到集合中
classList.add(name);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return classList;
}
public void init(Application application){
context = application;
List<String> className = getClassName("com.example.util");
//遍历
for (String s : className) {
try {
Class<?> aClass = Class.forName(s);
//判断这个类是不是IRouter这个接口的子类
if(IARouter.class.isAssignableFrom(aClass)){
//通过接口的引用 指向子类的实例
IARouter iRouter = (IARouter) aClass.newInstance();
iRouter.putActivity();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
参考资料: