Android高工面试:如果需要实现一个-路由(Router)框架,讲讲你的思路

本文详细解析了Android中的router-compiler工具,介绍了其如何处理@Route,@Modules,@Module注解,生成代码,以及router-api对外API的工作原理。重点讲解了RouterProcessor的运作机制和编译时注解的使用。
摘要由CSDN通过智能技术生成

router-compiler 主要是用来处理注解的,自动帮我们生成代码

router-api 是对外的 api,用来处理跳转的。

stub 这个是存放一些空的 java 文件,提前占坑。不会打包进 jar。

router-annotion

主要定义了三个注解

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {
String path();
}

@Retention(RetentionPolicy.CLASS)
public @interface Modules {
String[] value();
}

@Retention(RetentionPolicy.CLASS)
public @interface Module {
String value();
}

Route 注解主要是用来注明跳转的 path 的。

Modules 注解,注明总共有多少个 moudle。

Module 注解,注明当前 moudle 的名字。

Modules,Module 注解主要是为了解决支持多 module 使用的。


router-compiler

router-compiler 只有一个类 RouterProcessor,他的原理其实也是比较简单的,扫描那些类用到注解,并将这些信息存起来,做相应的处理。这里是会生成相应的 java 文件。

主要包括以下两个步骤

  1. 根据是否有 @Modules @Module 注解,然后生成相应的 RouterInit 文件
  2. 扫描 @Route 注解,并根据 moudleName 生成相应的 java 文件

注解基本介绍

在讲解 RouterProcessor 之前,我们先来了解一下注解的基本知识。

如果对于自定义注解还不熟悉的话,可以先看我之前写的这两篇文章。Android 自定义编译时注解1 - 简单的例子Android 编译时注解 —— 语法详解

public class RouterProcessor extends AbstractProcessor {
private static final boolean DEBUG = true;
private Messager messager;
private Filer mFiler;

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
messager = processingEnv.getMessager();
mFiler = processingEnv.getFiler();
UtilManager.getMgr().init(processingEnv);
}

/**

  • 定义你的注解处理器注册到哪些注解上
    */
    @Override
    public Set getSupportedAnnotationTypes() {
    Set annotations = new LinkedHashSet<>();
    annotations.add(Route.class.getCanonicalName());
    annotations.add(Module.class.getCanonicalName());
    annotations.add(Modules.class.getCanonicalName());
    return annotations;
    }

/**

  • java版本
    */
    @Override
    public SourceVersion getSupportedSourceVersion() {
    return SourceVersion.latestSupported();
    }

}

首先我们先来看一下 getSupportedAnnotationTypes 方法,这个方法返回的是我们支持扫描的注解。

注解的处理

接下来我们再一起来看一下 process 方法

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 注解为 null,直接返回
if (annotations == null || annotations.size() == 0) {
return false;
}

UtilManager.getMgr().getMessager().printMessage(Diagnostic.Kind.NOTE, “process”);
boolean hasModule = false;
boolean hasModules = false;
// module
String moduleName = “RouterMapping”;
Set<? extends Element> moduleList = roundEnv.getElementsAnnotatedWith(Module.class);
if (moduleList != null && moduleList.size() > 0) {
Module annotation = moduleList.iterator().next().getAnnotation(Module.class);
moduleName = moduleName + “_” + annotation.value();
hasModule = true;
}
// modules
String[] moduleNames = null;
Set<? extends Element> modulesList = roundEnv.getElementsAnnotatedWith(Modules.class);
if (modulesList != null && modulesList.size() > 0) {
Element modules = modulesList.iterator().next();
moduleNames = modules.getAnnotation(Modules.class).value();
hasModules = true;
}

debug(“generate modules RouterInit annotations=” + annotations + " roundEnv=" + roundEnv);
debug(“generate modules RouterInit hasModules=” + hasModules + " hasModule=" + hasModule);
// RouterInit
if (hasModules) { // 有使用 @Modules 注解,生成 RouterInit 文件,适用于多个 moudle
debug(“generate modules RouterInit”);
generateModulesRouterInit(moduleNames);
} else if (!hasModule) { // 没有使用 @Modules 注解,并且有使用 @Module,生成相应的 RouterInit 文件,使用与单个 moudle
debug(“generate default RouterInit”);
generateDefaultRouterInit();
}

// 扫描 Route 注解
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Route.class);
List targetInfos = new ArrayList<>();
for (Element element : elements) {
System.out.println(“elements =” + elements);
// 检查类型
if (!Utils.checkTypeValid(element)) continue;
TypeElement typeElement = (TypeElement) element;
Route route = typeElement.getAnnotation(Route.class);
targetInfos.add(new TargetInfo(typeElement, route.path()));
}

// 根据 module 名字生成相应的 java 文件
if (!targetInfos.isEmpty()) {
generateCode(targetInfos, moduleName);
}
return false;
}

,首先判断是否有注解需要处理,没有的话直接返回 annotations == null || annotations.size() == 0

接着我们会判断是否有 @Modules 注解(这种情况是多个 moudle 使用),有的话会调用 generateModulesRouterInit(String[] moduleNames) 方法生成 RouterInit java 文件,当没有 @Modules 注解,并且没有 @Module (这种情况是单个 moudle 使用),会生成默认的 RouterInit 文件。

private void generateModulesRouterInit(String[] moduleNames) {
MethodSpec.Builder initMethod = MethodSpec.methodBuilder(“init”)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC);
for (String module : moduleNames) {
initMethod.addStatement(“RouterMapping_” + module + “.map()”);
}
TypeSpec routerInit = TypeSpec.classBuilder(“RouterInit”)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(initMethod.build())
.build();
try {
JavaFile.builder(Constants.ROUTE_CLASS_PACKAGE, routerInit)
.build()
.writeTo(mFiler);
} catch (Exception e) {
e.printStackTrace();
}
}

假设说我们有"app",“moudle1” 两个 moudle,那么我们最终生成的代码是这样的。

public final class RouterInit {
public static final void init() {
RouterMapping_app.map();
RouterMapping_moudle1.map();
}
}

如果我们都没有使用 @Moudles 和 @Module 注解,那么生成的 RouterInit 文件大概是这样的。

public final class RouterInit {
public static final void init() {
RouterMapping.map();
}
}

这也就是为什么有 stub module 的原因。因为默认情况下,我们需要借助 RouterInit 去初始化 map。如果没有这两个文件,ide 编辑器 在 compile 的时候就会报错。

compileOnly project(path: ‘:stub’)

我们引入的方式是使用 compileOnly,这样的话再生成 jar 的时候,不会包括这两个文件,但是可以在 ide 编辑器中运行。这也是一个小技巧。

Route 注解的处理

我们回过来看 process 方法连对 Route 注解的处理。

// 扫描 Route 自己注解
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Route.class);
List targetInfos = new ArrayList<>();
for (Element element : elements) {
System.out.println(“elements =” + elements);
// 检查类型
if (!Utils.checkTypeValid(element)) continue;
TypeElement typeElement = (TypeElement) element;
Route route = typeElement.getAnnotation(Route.class);
targetInfos.add(new TargetInfo(typeElement, route.path()));
}

// 根据 module 名字生成相应的 java 文件
if (!targetInfos.isEmpty()) {
generateCode(targetInfos, moduleName);
}

首先会扫描所有的 Route 注解,并添加到 targetInfos list 当中,接着调用 generateCode 方法生成相应的文件。

private void generateCode(List targetInfos, String moduleName) {

MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder(“map”)
// .addAnnotation(Override.class)
.addModifiers(Modifier.STATIC)
.addModifiers(Modifier.PUBLIC);

// .addParameter(parameterSpec);
for (TargetInfo info : targetInfos) {
methodSpecBuilder.addStatement(“com.xj.router.api.Router.getInstance().add($S, $T.class)”, info.getRoute(), info.getTypeElement());
}

TypeSpec typeSpec = TypeSpec.classBuilder(moduleName)
// .addSuperinterface(ClassName.get(interfaceType))
.addModifiers(Modifier.PUBLIC)
.addMethod(methodSpecBuilder.build())
.addJavadoc(“Generated by Router. Do not edit it!\n”)
.build();
try {
JavaFile.builder(Constants.ROUTE_CLASS_PACKAGE, typeSpec)
.build()
.writeTo(UtilManager.getMgr().getFiler());
System.out.println(“generateCode: =” + Constants.ROUTE_CLASS_PACKAGE + “.” + Constants.ROUTE_CLASS_NAME);
} catch (Exception e) {
e.printStackTrace();
System.out.println(“generateCode:e =” + e);
}

}

这个方法主要是使用 javapoet 生成 java 文件,关于 javaposet 的使用可以见官网文档,生成的 java 文件是这样的。

package com.xj.router.impl;

import com.xj.arounterdemo.MainActivity;
import com.xj.arounterdemo.OneActivity;
import com.xj.arounterdemo.TwoActivity;

/**

  • Generated by Router. Do not edit it!
    */
    public class RouterMapping_app {
    public static void map() {
    com.xj.router.api.Router.getInstance().add(“activity/main”, MainActivity.class);
    com.xj.router.api.Router.getInstance().add(“activity/one”, OneActivity.class);
    com.xj.router.api.Router.getInstance().add(“activity/two”, TwoActivity.class);
    }
    }

可以看到我们定义的注解信息,最终都会调用 Router.getInstance().add() 方法存放起来。


router-api

这个 module 主要是多外暴露的 api,最主要的一个文件是 Router。

public class Router {

private static final String TAG = “ARouter”;

private static final Router instance = new Router();

private Map<String, Class<? extends Activity>> routeMap = new HashMap<>();
private boolean loaded;

private Router() {
}

public static Router getInstance() {
return instance;
}

public void init() {
if (loaded) {
return;
}
RouterInit.init();
loaded = true;
}
}

当我们想要初始化 Router 的时候,代用 init 方法即可。 init 方法会先判断是否初始化过,没有初始化过,会调用 RouterInit#init 方法区初始化。

而在 RouterInit#init 中,会调用 RouterMap_{@moduleName}#map 方法初始化,改方法又调用 Router.getInstance().add() 方法,从而完成初始化

router 跳转回调

public interface RouterCallback {

/**

  • 在跳转 router 之前
  • @param context
  • @param uri
  • @return
    */
    boolean beforeOpen(Context context, Uri uri);

/**

  • 在跳转 router 之后
  • @param context
  • @param uri
    */
    void afterOpen(Context context, Uri uri);

/**

  • 没有找到改 router
  • @param context
  • @param uri
    */
    void notFind(Context context, Uri uri);

/**

  • 跳转 router 错误
  • @param context
  • @param uri
  • @param e
    */
    void error(Context context, Uri uri, Throwable e);

结尾

我还总结出了互联网公司Android程序员面试涉及到的绝大部分面试题及答案,并整理做成了文档,以及系统的进阶学习视频资料分享给大家。
(包括Java在Android开发中应用、APP框架知识体系、高级UI、全方位性能调优,NDK开发,音视频技术,人工智能技术,跨平台技术等技术资料),希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。

image

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

框架知识体系、高级UI、全方位性能调优,NDK开发,音视频技术,人工智能技术,跨平台技术等技术资料),希望能帮助到你面试前的复习,且找到一个好的工作,也节省大家在网上搜索资料的时间来学习。**

[外链图片转存中…(img-2ppQX9ca-1714474598934)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 23
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值