ARouter 源码分析2

简介: ARouter 源码分析

路由表的加载

先从ARouter初始化开始看,就是这句代码ARouter.init(this),看下init方法做了什么,代码如下

 public static void init(Application application) {
        if (!hasInit) { // 首次执行会到这里
            ...
              // 进一步调用了_ARouter的init方法
            hasInit = _ARouter.init(application);
            if (hasInit) {
                _ARouter.afterInit();
            }
  ...
        }
    }

从代码中可以看到,调用了_ARouterinit方法,接着跟下去,代码如下

protected static synchronized boolean init(Application application) {
        ...
        // 主要是这句代码
        LogisticsCenter.init(mContext, executor);
        ...
        return true;
    }

跟到这里,出现了LogisticsCenter这个类,这个类是什么呢?根据类的名称翻译过来是 “物流中心” 的意思,继续看下它的init方法做了什么

public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
        try {
           ...
            // 首先从插件中加载路由表
            loadRouterMap();
            if (registerByPlugin) {
                logger.info(TAG, "Load router map by arouter-auto-register plugin.");
            } else {
                Set<String> routerMap;
                if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
                  // 调试模式或则是新的版本,会重建路由表
                    // 这里就是从dex文件中查找“com.alibaba.android.arouter.routes”包下的类,放到map中
                    routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
                    if (!routerMap.isEmpty()) {
                      // routerMap有内容的话,就把内容存到sp中
                        context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
                    }
                    // 保存新的版本号
                    PackageUtils.updateVersion(context);    
                } else {
                    // 直接从sp文件中拿路由表,就是前面保存到sp文件中的路由表
                    routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
                }
              // 根据包目录,来实例化不同的对象并调用loadInto方法。
                for (String className : routerMap) {
                    if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                        // This one of root elements, load root.
                        ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                    } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                        // Load interceptorMeta
                        ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                    } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                        // Load providerIndex
                        ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                    }
                }
            }
  ...
        } 
    }

从上面的代码可以看出,这段代码就是加载路由表的核心代码,上面有注释标出了一些代码的业务逻辑,这里再挑出几个比较难理解的地方重点讲解一下,首先是这句代码loadRouterMap();注释上说的是从插件中加载路由表,什么意思呢?就是如果我们想缩短ARouter初始化的时间,可以用 ARouter 的 Gradle 插件,这个插件能自动加载路由表,这样 ARouter 初始化的时候就不需要读取类的信息,从而缩短初始化时间。

插件的工作原理就是在代码编译的时候,插件会找到LogisticsCenter类的loadRouterMap方法,然后在方法中插入路由相关的代码,这样初始化的时候就不会从dex文件中扫描路由表了。

为了证明我不是胡诌的,这里贴出LogisticsCenter代码编译后的class文件截图,如下

f01ae120014abf5d174f05670ae4084.png

可以看到通过插件编译后,路由表已经插入到源码中。从源码中可以看出,加载路由表是有两种方式的,第一种就是刚才讲到的通过插件加载,第二种就是通过dex文件加载,通过dex文件加载路由表的方式已经 在上面的源码中进行注释了,简单了解下即可。

再回忆下,看这部分的源码是为了什么,是为了了解ARouter如何加载路由文件的,上面已经通过代码了解了如何加载的路由文件,为了方便理解和记忆这里还是用图来总结一下这部分的内容,如下

711995b04d2020b969da9834d8691a2.png

好了,路由表的加载原理到这里就结束了,下面开始研究路由表的跳转。

路由表的跳转

还是从官方的demo开路由表的跳转

f562d579838267dd5b19e6453860e11.png

跟进navigation方法,发现调用到了下面的代码

8e5bf762b58d6d5322d677bbae30036.png

继续跟进,最终调用到的是_ARouternavigation方法,如下

72472f52a8e1584086b63acd37df8db.png

现在看下_ARouternavigation方法的代码,如下

32d1107415a33819077c3b7bd1dda92.png

这里我把Postcard给标记出来了,为了更够更好的理解代码的原理,这里很有必要先搞清楚Postcard是什么。

Postcard是什么

Postcard翻译过来的意思是明信片,它的作用也和明信片的作用类似,里面保存的都是路由跳转的一些信息,可以看下它的成员变量

2f2da82c2ca0072edcf9d6fbe84a82a.png

每个成员变量的作用,如下表

成员变量 释义
uri 统一资源标识符,可以用uri作为路径的跳转
tag 用于在 NavigationCallback 的 interrupt() 方法中获取异常信息
mBundle 调用 withString() 等方法设置要传递给跳转目标的数据时,这个数据就是放在 mBundle 中的
flags 调用 withFlag() 设定 Activity 的启动标志时,这个标志就会赋值给 flags 字段
timeout 拦截器链处理跳转事件是放在 CountDownLatch 中执行的,超时时间默认为 300 秒
provider 当我们实现了自定义服务时,参数注解处理器 AutowiredProcessor 会为各个路径创建一个实现注射器 ISyringe 接口的类,在这个类的 inject() 方法中,调用了 ARouter.getInstance().navigation(XXXService.class) ,当 LogisticsCenter 发现这是一个 Provider 时,就会通过反射创建一个 Provider 实例,然后设置给 Postcard ,再进行跳转。
greenChannel 所谓绿色通道,就是不会被拦截器链处理的通道,自定义服务 IProvider 和 Fragment 就是走的绿色通道
serializationService 当我们调用 withObject() 方法时,ARouter 就会获取我们自己自定义的序列化服务 SerializationService,然后调用该服务的 object2Json() 方法,再把数据转化为 String 放入 bundle 中
optionsCompat 转场动画
enterAnim/exitAnim 进入与退出动画

理解了Postcard的作用后,再看navigation方法的代码,就比较容易理解了。

接着看navigation方法的代码,如下

02991a8b78ec21b4b0ca3d0384e41fb.png

重点需要看的地方,已经标出来了,先看标注1的代码做了什么。

标注1的作用

主要代码如下

a1cea5ca205cb21586dd259fad11ed9.png

这里标出了3处,还是一点点的解释

  • 标注1:从routeMeta中取值,设置到postcard属性中,还记的routeMeta是什么吗?就是在路由文件生成的时候生成的路由元数据,忘记的话,可以到前文再看下。
  • 标注2:解析Uri中的参数,设置到Bundle里。
  • 标注3:主要看绿色框的部分,当类型是PROVIDER和FRAGMENT的的时候,设置postcard的greenChannel。

这个方法的作用,总结起来就是完善postcard对象的属性。

标注2的原理

标注2其实比较简单的,就是判断是否是greenChannel,不是greenChannel的话,就进入拦截器中调用onInterrupt方法,是greenChannel的话就继续进_navigation(postcard, requestCode, callback)方法,这个方法的代码如下,

private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        final Context currentContext = postcard.getContext();
        switch (postcard.getType()) {
            case ACTIVITY:
                // Build intent
                final Intent intent = new Intent(currentContext, postcard.getDestination());
                intent.putExtras(postcard.getExtras());
                // Set flags.
                int flags = postcard.getFlags();
                if (0 != flags) {
                    intent.setFlags(flags);
                }
                // Non activity, need FLAG_ACTIVITY_NEW_TASK
                if (!(currentContext instanceof Activity)) {
                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                }
                // Set Actions
                String action = postcard.getAction();
                if (!TextUtils.isEmpty(action)) {
                    intent.setAction(action);
                }
                // Navigation in main looper.
                runInMainThread(new Runnable() {
                    @Override
                    public void run() {
                        startActivity(requestCode, currentContext, intent, postcard, callback);
                    }
                });
                break;
            case PROVIDER:
                return postcard.getProvider();
            case BOARDCAST:
            case CONTENT_PROVIDER:
            case FRAGMENT:
                Class<?> fragmentMeta = postcard.getDestination();
                try {
                    Object instance = fragmentMeta.getConstructor().newInstance();
                    if (instance instanceof Fragment) {
                        ((Fragment) instance).setArguments(postcard.getExtras());
                    } else if (instance instanceof android.support.v4.app.Fragment) {
                        ((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
                    }
                    return instance;
                } catch (Exception ex) {
                    logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
                }
            case METHOD:
            case SERVICE:
            default:
                return null;
        }
        return null;
    }

这部分代码也很好理解,拿到类型之后,分别对对应的类型做处理是ACTIVITY是的话最后在主线程中调用startActivity跳转,是FRAGMENT的话就利用反射创建出Fragment的实例并返回。

总结

本篇文章首先介绍了ARouter的基本使用,然后整体的看了一下Arouter代码的框架,最后对ARouter的路由跳转功能进行原理分析,文章的主要内容也是对ARouter的跳转进行分析,ARouter的功能还是比较多的,感兴趣的话可以自己阅读源码,详细的了解下ARouter的原理。

相关文章
|
存储 算法
TreadLocal源码分析
TreadLocal源码分析
|
ARouter
ARouter 源码分析1
ARouter 源码分析
vivid源码分析
vivid源码分析
116 0
|
存储 Java 应用服务中间件
SpringMVC源码分析 RequestContextHolder使用与源码分析
SpringMVC源码分析 RequestContextHolder使用与源码分析
SpringMVC源码分析 RequestContextHolder使用与源码分析
|
ARouter Java Android开发
给 Arouter 优化的一些小建议
给 Arouter 优化的一些小建议
285 0
给 Arouter 优化的一些小建议
|
iOS开发
fishhook源码分析
最早了解到[fishhook](https://github.com/facebook/fishhook)是看了下面两篇文章之后,顿时让我觉得这是一个非常好的东西。总共210行代码,收获了1500+个star,神作啊。 1. [iOS Lazy Binding](http://www.atatech.org/articles/68014),使用fishhook拦截NSSetUncaughtE
2447 0
|
移动开发 Java 开发者
Stresstester源码分析
stresstester-1.0.jar是早期淘宝的一个压力测试工具,很方便开发人员进行本地代码的压力测试,其他专门压力测试工具也有很多,如:jmeter loadrunner 等等,本篇文章主要讲一下stresstester的源码设计
10619 0
|
大数据 DataX 分布式计算
gobblin 源码分析
最近,开始搞些大数据相关的内容,遇到的第一个问题,就是数据入库,小白刚入手,又不想写太多代码,于是从网上找,入库手段很多: DataX,Sqoop,以及Flume 等以及直接使用 Spark 进行入库,想了下当下的场景(不是简单的倒库,要从kafka拉...
1419 0