面试官,Android 怎样实现 Router 框架?(二)

简介: 面试官,Android 怎样实现 Router 框架?

Route 注解的处理


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


// 扫描 Route 自己注解
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Route.class);
List<TargetInfo> 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<TargetInfo> 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() 方法,从而完成初始化


f4a989b3a4a18045844c41be4a3e9a3d_format,png.png

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 框架,流程大概是这样的。



1161c1c677ac693e0cab12f4c4334daf_format,png.png

f4a989b3a4a18045844c41be4a3e9a3d_format,png.png


题外话


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


  1. 注解的处理
  2. 怎样解决多个 module 之间的依赖问题,以及如何支持多 module 使用
  3. router 跳转及 activty startActivityForResult 的处理


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


相关文章
|
2天前
|
XML Android开发 数据格式
ConstraintLayout 2,Android高级开发面试
ConstraintLayout 2,Android高级开发面试
|
2天前
|
测试技术 Android开发
Android开发规范,性能优化,Android面试2024
Android开发规范,性能优化,Android面试2024
|
2天前
|
JSON Android开发 数据格式
Android框架-Google官方Gson解析,android开发实验报告总结
Android框架-Google官方Gson解析,android开发实验报告总结
|
2天前
|
XML 前端开发 Android开发
Android架构设计——MVC,滴滴 战略 面试
Android架构设计——MVC,滴滴 战略 面试
|
2天前
|
Android开发 异构计算 前端开发
Android显示原理,安卓自定义view面试
Android显示原理,安卓自定义view面试
|
2天前
|
数据库 Android开发
Android数据库框架-GreenDao入门,2024年最新flutter 页面跳转动画
Android数据库框架-GreenDao入门,2024年最新flutter 页面跳转动画
Android数据库框架-GreenDao入门,2024年最新flutter 页面跳转动画
|
2天前
|
存储 Android开发
Android插件化-Broadcast篇,2024年最新安卓面试自我介绍
Android插件化-Broadcast篇,2024年最新安卓面试自我介绍
|
2天前
|
XML 存储 Android开发
Android技能树 — Fragment总体小结,2024年最新腾讯面试gm
Android技能树 — Fragment总体小结,2024年最新腾讯面试gm
|
2天前
|
架构师 网络协议 算法
Android高级架构师整理面试经历发现?(大厂面经+学习笔记(1)
Android高级架构师整理面试经历发现?(大厂面经+学习笔记(1)
|
2天前
|
设计模式 网络协议 算法
9次Android面试经验总结,已收字节,阿里(1),费时6个月成功入职阿里
9次Android面试经验总结,已收字节,阿里(1),费时6个月成功入职阿里
http://www.vxiaotou.com