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 项目的代码结构,如下

ARouter 源码分析

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

ARouter 源码分析

能够发现,ARouter项目首要是围绕着生成和加载及解析路由表来编写的,现在现已对ARouter架构有了根本的形象,下面咱们再看下,ARouter是怎样经过上面的架构来完成路由跳转的,

ARouter 源码分析

从这幅图中,能够愈加明晰的了解ARouter每个模块的职责与联络,当然到这儿,也仅是列出了ARouter项目的架构和模块间的联络,还没有对ARouter全体的作业流程有个基础的知道,下面会介绍一下ARouter的作业流程。

ARouter全体的作业流程

一图胜千言,这儿还是先用图来展现ARouter的作业流程,如下

ARouter 源码分析

这儿仅仅ARouter跳转的首要流程,好多细节方面的知识下文会解说,在深化源码剖析之前,对流程先有个形象,便利下文源码的了解,也避免在源码的大海里迷失方向。

ARouter原了解析

到这儿咱们就正式开端ARouter的源码剖析,为了避免在源码的大海里迷失,剖析源码的次序就按上面画出的作业流程图来一步步进行,首要看下路由文件的生成原理。

路由文件的生成

路由文件的生成是经过APT技能来完成的,假如不了解APT技能能够先去了解一下,否则这部分代码可能看不懂,不过最终会画一张这部分的流程图,便利了解记忆。

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

ARouter 源码分析

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,这个文件的位置如下图

ARouter 源码分析

能够看到生成的代码,是创立了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文件截图,如下

ARouter 源码分析

能够看到经过插件编译后,路由表现已插入到源码中。从源码中能够看出,加载路由表是有两种办法的,第一种便是刚才讲到的经过插件加载,第二种便是经过dex文件加载,经过dex文件加载路由表的办法现已 在上面的源码中进行注释了,简略了解下即可。

再回想下,看这部分的源码是为了什么,是为了了解ARouter怎样加载路由文件的,上面现现已过代码了解了怎样加载的路由文件,为了便利了解和记忆这儿还是用图来总结一下这部分的内容,如下

ARouter 源码分析

好了,路由表的加载原理到这儿就结束了,下面开端研讨路由表的跳转。

路由表的跳转

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

ARouter 源码分析

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

ARouter 源码分析

持续跟进,最终调用到的是_ARouternavigation办法,如下

ARouter 源码分析

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

ARouter 源码分析

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

Postcard是什么

Postcard翻译过来的意思是明信片,它的效果也和明信片的效果类似,里边保存的都是路由跳转的一些信息,能够看下它的成员变量

ARouter 源码分析

每个成员变量的效果,如下表

成员变量 释义
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办法的代码,如下

ARouter 源码分析

要点需求看的地方,现已标出来了,先看标示1的代码做了什么。

标示1的效果

首要代码如下

ARouter 源码分析
这儿标出了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的原理。