ARouter源码探究

ARouter源码探究

1. 疑问

  1. 如何做到支持直接解析标准URL进行跳转,并自动注入参数到目标页面中?
  2. 如何做到支持Multidex、InstantRun
  3. 如何做到映射关系按组分类、多级管理,按需初始化?
  4. 依赖注入是如何实现的?
  5. 如何做到支持添加多个拦截器,自定义拦截顺序?
  6. 页面、拦截器、服务等组件如何自动注册到框架?
  7. 如何做到支持多模块工程使用?
  8. 如何做到支持获取Fragment?

源码是最好的老师,我们带着这些疑问去源码中寻找答案;

其实ARouter有上述功能我总结有二个关键点:APT、反射

下面我们从编译期,ARouter初始化,route三个方面进行分析

2. 编译期间做了什么?

https://github.com/alibaba/ARouter.git

我们以官方源码为例进行剖析,下载源码后终端输入如下命令

cd ARouter && ./gradlew clean assembleDebug

构建成功后你看到的目录结构如下

这里写图片描述

App:主工程

arouter-annotation:存放ARouter注解的Module

arouter-compiler:编译期间动态生成代码的逻辑

arouter-api: ARouter大部分的内部实现设计

arouter-register:支持第三方加固

module-java:跨模块调用演示

module-kotlin:支持Kotlin


我们先熟悉下几个类

Route注解

这里写图片描述

可以看到Route注解中的path就是跳转的路由,group是其对应的分组(按需加载用到);需要说明的是ARouter除了支持跳转Activity外还支持其他类型

Arouter的跳转类型

这里写图片描述

从上面可推测目前应该支持挑转Activity,Service,ContentProvider以及Java接口

APT(注解处理器)

APT(Annotation Processing Tool)即注解处理器,是一种处理注解的工具,确切的说它是javac的一个工具,它用来在编译时扫描和处理注解。注解处理器以Java代码(或者编译过的字节码)作为输入,生成.java文件作为输出。简单来说就是在编译期,通过注解生成.java文件。用过Dagger、EventBus等注解框架同学都能感受到,使用这些框架只要写一些注解就可以了,使用起来非常方便;其实它们都是通过读取注解,生成了一些代码而已;

Arouter的参数的自动注入,支持拦截,path和类如何建立映射,都是和注解处理器分不开的;

我们来看app工程中自动生成的代码有哪些

这里写图片描述

我挑选分组为test、service代码来看看,可以看到下面二个类名的末尾都是使用自身的组名来拼接而成的;因为ARouter是运行期间按组加载路由而不是一次性载入,而下面的二个类也正是基于这一考虑而生成的;每个类只负责加载自己组内路由,一个类只做一件事情

这里写图片描述

这里写图片描述

既然ARouter要做到按组加载路由,那肯定有一个存放所有分组映射的类,以便于快速定位分组类,我们发现有个Arouter$$Root$app类,它就是我们猜想的那个类,key存放组名,value存放其对应的映射类

这里写图片描述

拦截器生成类:

这里写图片描述

可以看到该类中包含了声明的所有拦截器类,key对应就是其优先级

我们用一张图概括编译期间做了啥

这里写图片描述

从上图看出编译器做了二件事情

  1. APT扫描指定注解的java类文件
  2. 通过扫描分析生成对应的java类

3. 初始化做了什么?

一张图总结如下:

这里写图片描述

下面我们具体分析下

//ARouter.java
/**
 * Init, it must be call before used router.
 */
public static void init(Application application) {
    if (!hasInit) {
        logger = _ARouter.logger;
        _ARouter.logger.info(Consts.TAG, "ARouter init start.");
        hasInit = _ARouter.init(application);

        if (hasInit) {
            _ARouter.afterInit();
        }

        _ARouter.logger.info(Consts.TAG, "ARouter init over.");
    }
}

//_ARouter.java
protected static synchronized boolean init(Application application) {
        mContext = application;
        LogisticsCenter.init(mContext, executor);
        logger.info(Consts.TAG, "ARouter init success!");
        hasInit = true;

        // It's not a good idea.
        // if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
   
        //     application.registerActivityLifecycleCallbacks(new AutowiredLifecycleCallback());
        // }
        return true;
    }

我们看到Arouter的初始化其实最后还是由LogisticsCenter这个类来做的

这里写图片描述

我们直接从第一个else语句后面看源代码如下

//LogisticsCenter.java

//public static final String ROUTE_ROOT_PAKCAGE = "com.alibaba.android.arouter.routes";
//public static final String DOT = ".";
//public static final String SDK_NAME = "ARouter";

if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
    logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
    // These class was generated by arouter-compiler.

    //1、读取com.alibaba.android.arouter.routes包名下所有类
    routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);

    if (!routerMap.isEmpty()) {

        //2、把指定包名下的类都保存到本地了,便于下次直接从文件读取
        context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE)
            .edit()
            .putStringSet(AROUTER_SP_KEY_MAP, routerMap)
            .apply();
    }
    // Save new version name when router map update finishes.
    PackageUtils.updateVersion(context);    
} else {
    logger.info(TAG, "Load router map from cache.");

    //3、从sp中直接读取类信息
    routerMap = new HashSet<>(context
                              .getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE)
                              .getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
}

logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");
startInit = System.currentTimeMillis();

//4、将指定前缀的包名的类加载到内存中(Warehouse)
for (String className : routerMap) {
    if (className.startsWith("com.alibaba.android.arouter.routes.ARouter$$Root")) {

        // This one of root elements, load root.
        ((IRouteRoot) (Class.forName(className).getConstructor().newInstance()))
        .loadInto(Warehouse.groupsIndex);

    } else if (className.startsWith("com.alibaba.android.arouter.routes.ARouter$$Interceptors")) {
        // Load interceptorMeta
        ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance()))
        .loadInto(Warehouse.interceptorsIndex);

    } else if (className.startsWith("com.alibaba.android.arouter.routes.ARouter$$Providers")) {

        // Load providerIndex
        ((IProviderGroup) (Class.forName(className).getConstructor().newInstance()))
        .loadInto(Warehouse.providersIndex);

    }
}

小结上面代码,主要做了二件事情,

  1. 寻找指定包名的所有类

    通过线程池读取所有dex中指定包名类,此处也就解释了为什么ARouter支持InstantRunMultiDex

  2. 通过反射创建对象,并加载到内存(Warehouse)

    可以看到初始化过程其实加载到Warehouse类中有groupsIndexinterceptorsIndexprovidersIndex

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值