ARouter 源码分析

1,410 阅读13分钟

ARouter 源码分析

ARouter基本使用

在开始分析源码之前,先了解一下ARoute如何使用的,使用ARoute可以概括为以下3步:

  1. 项目中引入ARouter 及配置
  2. 初始化ARouter
  3. 开始使用

下面详细的看下每一步怎么操作

项目中引入ARouter及配置

其实这一步就是导包,将ARouter 下载到本地,在app module下的build.gradle文件中 添加以下代码

plugins {
    ...
    id 'kotlin-kapt'
}


android {
    ...

    kapt {
        arguments {
            arg("AROUTER_MODULE_NAME", project.getName())
        }
    }

...
}

dependencies {
    implementation 'com.alibaba:arouter-api:x.x.x'
    kapt 'com.alibaba:arouter-compiler:x.x.x'
    ...
}

初始化ARouter

初始化很简单,只需要在项目的application类(我的是MyApplication)中添加下面代码即可

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()

        // 这两行必须写在init之前,否则这些配置在init过程中将无效
        ARouter.openLog()     // 打印日志
        ARouter.openDebug()   // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
        ARouter.init(this) // 尽可能早,推荐在Application中初始化

    }
}

开始使用

只是界面跳转,使用起来还是挺简单地,我这里写了1个Activity,Test1Activity,要实现的功能就是从MainActivity跳转到Test1Activity。这里也可以分为两步:

  1. Test1Activity添加注解。
  2. MainActivity添加跳转代码。

Test1Activity添加的注解如下

// 在支持路由的页面上添加注解(必选)
// 这里的路径需要注意的是至少需要有两级,/xx/xx
@Route(path = "/test/Test1Activity")
class Test1Activity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_test1)
    }
}

MainActivity添加的跳转代码如下

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        findViewById<TextView>(R.id.tv_test).setOnClickListener {
            // 1. 应用内简单的跳转(通过URL跳转在'进阶用法'中)
            ARouter.getInstance().build("/test/Test1Activity").navigation()
        }
    }
}

做完了上面的三步,就可以实现利用ARoute进行界面跳转的功能了。ARouter 的功能还是比较多的,可以点击这里,进行详细了解。

ARouter概览

经过上面的步骤,我们已经可以在项目中使用ARoute了,下面我们来看下ARouter项目的整体架构和实现路由跳转的主要流程。

ARouter 整体架构

ARouter 项目的代码结构,如下

image-20211031103747723.png

红框内的是ARouter的核心代码,为了方便理解,我画了一个ARouter代码架构图,如下

ARouter架构.png

可以发现,ARouter项目主要是围绕着生成和加载及解析路由表来编写的,现在已经对ARouter架构有了基本的印象,下面我们再看下,ARouter是怎么通过上面的架构来实现路由跳转的,

各个模块的联系.png

从这幅图中,可以更加清晰的理解ARouter每个模块的职责与联系,当然到这里,也仅是列出了ARouter项目的架构和模块间的联系,还没有对ARouter整体的工作流程有个基础的认识,下面会介绍一下ARouter的工作流程。

ARouter整体的工作流程

一图胜千言,这里还是先用图来展示ARouter的工作流程,如下

ARoute流程分析.png

这里只是ARouter跳转的主要流程,好多细节方面的知识下文会讲解,在深入源码分析之前,对流程先有个印象,方便下文源码的理解,也防止在源码的大海里迷失方向。

ARouter原理解析

到这里我们就正式开始ARouter的源码分析,为了防止在源码的大海里迷失,分析源码的顺序就按上面画出的工作流程图来一步步进行,首先看下路由文件的生成原理。

路由文件的生成

路由文件的生成是通过APT技术来实现的,如果不了解APT技术可以先去了解一下,不然这部分代码可能看不懂,不过最后会画一张这部分的流程图,方便理解记忆。

生成路由文件的主要包目录如图

image.png

APT技术就是对特定的注解来做一些逻辑处理和自动生成文件,上图标出的Route就是注解,RouteProcessor就是用来处理注解的注解处理器,现在看下RouteProcessor主要代码,从程序入口开始看

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    if (CollectionUtils.isNotEmpty(annotations)) {
    //拿到所有Route注解修饰的类
        Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);
        try {
            this.parseRoutes(routeElements);
        } catch (Exception e) {
            logger.error(e);
        }
        return true;
    }

    return false;
}

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

private void parseRoutes(Set<? extends Element> routeElements) throws IOException {
    ... 
    // Write root meta into disk.
    String rootFileName = NAME_OF_ROOT + SEPARATOR + moduleName;
    JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
            TypeSpec.classBuilder(rootFileName)
                .addJavadoc(WARNING_TIPS)
                .addSuperinterface(ClassName.get(elementUtils.getTypeElement(ITROUTE_ROOT)))
                .addModifiers(PUBLIC)
                .addMethod(loadIntoMethodOfRootBuilder.build())
                .build()
    ).build().writeTo(mFiler);
    ...
}

上面的是parseRoutes方法的部分代码,此方法的主要作用就是生成路由文件,生成的路由文件的格式如下

public class ARouter$$Group$$test implements IRouteGroup {
  @Override
  public void loadInto(Map<String, RouteMeta> atlas) {
    atlas.put("/test/activity1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/activity1", "test", new java.util.HashMap<String, Integer>(){{put("ser", 9); put("ch", 5); put("fl", 6); put("dou", 7); put("boy", 0); put("url", 8); put("pac", 10); put("obj", 11); put("name", 8); put("objList", 11); put("map", 11); put("age", 3); put("height", 3); }}, -1, -2147483648));
    atlas.put("/test/activity2", RouteMeta.build(RouteType.ACTIVITY, Test2Activity.class, "/test/activity2", "test", new java.util.HashMap<String, Integer>(){{put("key1", 8); }}, -1, -2147483648));
    atlas.put("/test/activity3", RouteMeta.build(RouteType.ACTIVITY, Test3Activity.class, "/test/activity3", "test", new java.util.HashMap<String, Integer>(){{put("name", 8); put("boy", 0); put("age", 3); }}, -1, -2147483648));
    atlas.put("/test/activity4", RouteMeta.build(RouteType.ACTIVITY, Test4Activity.class, "/test/activity4", "test", null, -1, -2147483648));
    atlas.put("/test/fragment", RouteMeta.build(RouteType.FRAGMENT, BlankFragment.class, "/test/fragment", "test", new java.util.HashMap<String, Integer>(){{put("ser", 9); put("pac", 10); put("ch", 5); put("obj", 11); put("fl", 6); put("name", 8); put("dou", 7); put("boy", 0); put("objList", 11); put("map", 11); put("age", 3); put("height", 3); }}, -1, -2147483648));
    atlas.put("/test/webview", RouteMeta.build(RouteType.ACTIVITY, TestWebview.class, "/test/webview", "test", null, -1, -2147483648));
  }
}

我用的是官方的demo,这个文件的位置如下图

image.png

可以看到生成的代码,是创建了RouteMeta实例,然后放到map中,那么这个RouteMeta是什么呢?在路由跳转中又有什么作用呢?

RouteMeta是什么?

从名字上来看是路由的元数据,可以猜测此类包含了路由的元信息,那么这个类是不是这样呢?看下代码

public class RouteMeta {
    private RouteType type;         // Type of route
    private Element rawType;        // Raw type of route
    private Class<?> destination;   // Destination
    private String path;            // Path of route
    private String group;           // Group of route
    private int priority = -1;      // The smaller the number, the higher the priority
    private int extra;              // Extra data
    private Map<String, Integer> paramsType;  // Param type
    private String name;

    private Map<String, Autowired> injectConfig;  // Cache inject config.

上面的代码是RouteMeta的成员变量,这里我做了一个表格来解释每个成员变量的作用,如下

成员变量释义
type路线类型:路线类型 RouteType 是一个枚举类,类型有这几个:Activity、Service、Provider、ContentProvider、Fragment、Broadcast、Method、Unknown
rawType路线原始类型:路由处理器 RouteProcessor 设定
destination终点:声明了 @Route 的跳转目标的 Class ,比如目标 Activity 和 Fragment 的 Class,由 RouteProcessor 设定的。
path路径:比如 path = /goods/details ,那么 goods 就是 group ,details 就是路径 path
group路线组:如果在@Route注解中没有设置,那么就从设置的path中取值,设置的话就用设置的
priority优先级:优先级在 @Route 中无法设定,是给拦截器用的,priority 的值越小,拦截器的优先级就越高
extra标志:路由文档 RouteDoc 的标志
paramsType参数类型:对于我们跳转时设定的参数,ARouter 会根据不同的类型给它们一个枚举值,然后取值时,再根据不同的类型调用 Intent 的 getXXXExtra() 等方法
name路线名称
injectConfig注入配置

从上面的表格中,可以看出RouteMeta确实是用来保存路由的元信息的,这里大家熟悉一下每个成员变量的作用,下文源码分析的时候还会出现。路由文件的生成原理就到这里,接着看下代码在运行时是如何加载路由表以及如何根据路由表的信息来做跳转的。

路由表的加载

先从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文件截图,如下

image-20211127230840733.png

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

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

路由表加载原理.jpeg

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

路由表的跳转

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

image-20220227104923701.png

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

image-20220227105019093.png

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

image-20220227105132308.png

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

image-20220227105402962.png

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

Postcard是什么

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

image-20220227110231925.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方法的代码,如下

image-20220303213016391.png

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

标注1的作用

主要代码如下

image-20220303214303186.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的原理。