2024年安卓最新面试官,怎样实现 Router 框架?(1),2024-2024字节跳动Android面试真题解析

小结

有了这么多优秀的开发工具,可以做出更高质量的Android应用。

当然了,“打铁还需自身硬”,想要写出优秀的代码,最重要的一点还是自身的技术水平,不然用再好的工具也不能发挥出它的全部实力。

在这里我也分享一份大佬自己收录整理的Android学习PDF+架构视频+面试文档+源码笔记,还有高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料这些都是我闲暇还会反复翻阅的精品资料。在脑图中,每个知识点专题都配有相对应的实战项目,可以有效的帮助大家掌握知识点。

总之也是在这里帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习

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

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

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

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

}

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 框架。有兴趣的话可以关注我的微信公众号,徐公码字,谢谢。

最后

说一千道一万,不如自己去行动。要想在移动互联网的下半场是自己占有一席之地,那就得从现在开始,从今天开始,马上严格要求自己,既重视业务实现能力,也重视基础和原理。基础夯实好了,高楼才能够平地而起,稳如泰山。

最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的24套腾讯、字节跳动、阿里、百度2020-2021面试真题解析,我把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节

还有 高级架构技术进阶脑图、Android开发面试专题资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

一线互联网面试专题

379页的Android进阶知识大全

379页的Android进阶知识大全

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

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

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

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

有兴趣的话可以关注我的微信公众号,徐公码字,谢谢。

最后

说一千道一万,不如自己去行动。要想在移动互联网的下半场是自己占有一席之地,那就得从现在开始,从今天开始,马上严格要求自己,既重视业务实现能力,也重视基础和原理。基础夯实好了,高楼才能够平地而起,稳如泰山。

最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的24套腾讯、字节跳动、阿里、百度2020-2021面试真题解析,我把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节

还有 高级架构技术进阶脑图、Android开发面试专题资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。

[外链图片转存中…(img-VnL4WOAe-1715109799966)]

[外链图片转存中…(img-tAYuOxiO-1715109799967)]

[外链图片转存中…(img-gXr45zur-1715109799967)]

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

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

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

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

  • 25
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值