面试官,怎样实现 Router 框架?(1),软件开发面试题目100及最佳答案

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注Android)
img

正文

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);

}

public void navigation(Activity context, int requestCode, Callback callback) {

beforeOpen(context);

boolean isFind = false;

try {

Activity activity = (Activity) context;

Intent intent = new Intent();

intent.setComponent(new ComponentName(context.getPackageName(), mActivityName));

intent.putExtras(mBundle);

getFragment(activity)

.setCallback(callback)

.startActivityForResult(intent, requestCode);

isFind = true;

} catch (Exception e) {

errorOpen(context, e);

tryToCallNotFind(e, context);

}

if (isFind) {

afterOpen(context);

}

}

private void tryToCallNotFind(Exception e, Context context) {

if (e instanceof ClassNotFoundException && mRouterCallback != null) {

mRouterCallback.notFind(context, mUri);

}

}

主要看 navigation 方法,在跳转 activity 的时候,首先会会调用

beforeOpen 方法回调 RouterCallback#beforeOpen。接着 catch exception 的时候,如果发生错误,会调用 errorOpen 方法回调 RouterCallback#errorOpen 方法。同时调用 tryToCallNotFind 方法判断是否是 ClassNotFoundException,是的话回调 RouterCallback#notFind。

如果没有发生 eception,会回调 RouterCallback#afterOpen。

Activity 的 startActivityForResult 回调

可以看到我们的 Router 也是支持 startActivityForResult 的

Router.getInstance().build(“activity/two”).navigation(this, new Callback() {

@Override

public void onActivityResult(int requestCode, int resultCode, Intent data) {

Log.i(TAG, “onActivityResult: requestCode=” + requestCode + “;resultCode=” + resultCode + “;data=” + data);

}

});

它的实现原理其实很简单,是借助一个空白 fragment 实现的,原理的可以看我之前的这一篇文章。

Android Fragment 的妙用 - 优雅地申请权限和处理 onActivityResult


小结


如果觉得效果不错的话,请到 github 上面 star, 谢谢。 Router

我们的 Router 框架,流程大概是这样的。


题外话


看了上面的文章,文章一开头提到的三个问题,你懂了吗,欢迎在评论区留言评论。

  1. 注解的处理

  2. 怎样解决多个 module 之间的依赖问题,以及如何支持多 module 使用

  3. router 跳转及 activty startActivityForResult 的处理

其实,现在很多 router 框架都借助 gradle 插件来实现。这样有一个好处,就是在多 moudle 使用的时候,我们只需要 apply plugin 就 ok,对外屏蔽了一些细节。但其实,他的原理跟我们上面的原理都是差不多的。

接下来,我也会写 gradle plugin 相关的文章,并借助 gradle 实现 Router 框架。有兴趣的话可以关注我的微信公众号,徐公码字,谢谢。

总结:

各行各样都会淘汰一些能力差的,不仅仅是IT这个行业,所以,不要被程序猿是吃青春饭等等这类话题所吓倒,也不要觉得,找到一份工作,就享受安逸的生活,你在安逸的同时,别人正在奋力的向前跑,这样与别人的差距也就会越来越遥远,加油,希望,我们每一个人,成为更好的自己。

  • BAT大厂面试题、独家面试工具包,

  • 资料包括 数据结构、Kotlin、计算机网络、Framework源码、数据结构与算法、小程序、NDK、Flutter,


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

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
img

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

tivty startActivityForResult 的处理

其实,现在很多 router 框架都借助 gradle 插件来实现。这样有一个好处,就是在多 moudle 使用的时候,我们只需要 apply plugin 就 ok,对外屏蔽了一些细节。但其实,他的原理跟我们上面的原理都是差不多的。

接下来,我也会写 gradle plugin 相关的文章,并借助 gradle 实现 Router 框架。有兴趣的话可以关注我的微信公众号,徐公码字,谢谢。

总结:

各行各样都会淘汰一些能力差的,不仅仅是IT这个行业,所以,不要被程序猿是吃青春饭等等这类话题所吓倒,也不要觉得,找到一份工作,就享受安逸的生活,你在安逸的同时,别人正在奋力的向前跑,这样与别人的差距也就会越来越遥远,加油,希望,我们每一个人,成为更好的自己。

  • BAT大厂面试题、独家面试工具包,

  • 资料包括 数据结构、Kotlin、计算机网络、Framework源码、数据结构与算法、小程序、NDK、Flutter,

    [外链图片转存中…(img-HqHils3O-1713383132402)]
    [外链图片转存中…(img-sWjWbU2o-1713383132402)]

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

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-8NO8zDdy-1713383132403)]

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

  • 16
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值