前语

在我之前的组件化文章《“总算懂了” 系列:Android组件化,全面掌握!》中,说到为了完结组件化要解决的几个问题点,其间 页面跳转组件间通讯 的问题是运用了 ARouter 这个结构来解决的。ARouter确实是专门用于做组件化改造,官方是这么介绍的:

一个用于协助 Android App 进行组件化改造的结构 —— 支撑模块间的路由、通讯、解耦

关于组件化的知识已在上面文章中全面介绍了。那么是时分对 ARouter 这个强壮的结构做一个解析了:它是怎样做到 页面跳转、组件间通讯 的?咱们能从ARrouter中学到哪些东西?

而其时也确实承诺了有时机要写一篇ARouter的文章:

ARouter还有许多进阶用法,有时机我也针对ARouter写一篇全面剖析

由于内容估计较多,分为三篇,别离介绍 ARouter的完结原理、结构运用到的相关通用技能:

  • “总算懂了” 系列:组件化结构 ARouter 完全解析(一)原理全解
  • “总算懂了” 系列:组件化结构 ARouter 完全解析(二)APT—协助类生成
  • “总算懂了” 系列:组件化结构 ARouter 完全解析(二)AGP/Transform—动态代码注入

本篇就先整理ARouter的完结原理,看看它是怎样做到跨组件跳转页面、获取服务。

感谢持续重视和支撑~

一、路由认知

ARouter从命名即可知,这是一个路由结构。那么路由是个啥呢?

路由routing)便是经过互联的网络把信息源地址传输到目的地址的活动。– 百科 可见 路由 是个动词,这是网络传输中的概念,完结路由这个操作的实体设备便是 路由器(Router)。

另外,生活中的 函件邮递 也能够了解为一个 路由进程:邮局把函件从邮递方 传输到接纳人的手上。首要 邮递方 和 接纳人 是无法接触的(无耦合依靠),只能经过 邮局这个第三方 完结邮递;邮局依据信封上的地址,例如 “深圳市 深圳大学粤海校区”,决议分发到 开往深圳的车上,然后深圳的邮递员找到 深圳大学粤海校区 对应的 “南山区南海大路3688号”,终究找到接纳人。

“终于懂了” 系列:组件化框架 ARouter 完全解析(一) 原理详解

对应地, ARouter 也是个“路由器”,也是个“邮政体系”。通行依据组件化介绍的,ARouter 协助 无相互依靠的组件间 进行跳转和通讯。

笼统一下,邮局、ARouter 都是 路由体系 ——— 给 无依靠的两边 供给 通讯和路由的才能。

官方文档 有具体的引进和各种功能运用介绍,包含根底运用步骤、参数解析、拦截器、服务获取等进阶用法。这里不再搬运。

二、原了解析

运用ARouter在进行Activity跳转十分简略:初始化ARouter、增加注解@Route、建议路由。

// 在module app中
//1.初始化SDK
ARouter.init(mApplication); // 尽或许早,推荐在Application中初始化
// moduleA
// 2.在支撑路由的页面上增加注解(必选)
// 途径留意至少需有两级,/xx/xx
@Route(path = "/test/activity")
public class YourActivity extend Activity {
    ...
}
// moduleB(没有依靠 moduleA)
// 3.建议路由跳转
ARouter.getInstance().build("/test/activity").navigation();

这样就使得 没有依靠moduleA的moduleB能跳转到moduleA的Activity了。服务获取也是相似简略的代码就可完结。

那么 ARouter 是怎样做到只经过简略2步 就完结 解耦组件间的路由操作呢?咱们经过源码一步步了解。

2.1 构建PostCard

咱们知道 想要跳转Activity终究必定是走到了 startActivity(intent)办法,而intent是一般需求方针Activity的Class。所以咱们猜测 navigation()中应该是有寻觅方针Activity的Class这一进程的。

下面就来跟踪源码剖析这一进程。先看ARouter.getInstance():

public static ARouter getInstance() {
    if (!hasInit) { // 未初始化则报反常
        throw new InitException("ARouter::Init::Invoke init(context) first!");
    } else {
        if (instance == null) {
            synchronized (ARouter.class) {
                if (instance == null) {
                    instance = new ARouter();
                }
            }
        }
        return instance;
    }
}

获取ARouter单实例,没有初始化则报反常。再看它的build(string)办法:

public Postcard build(String path) {
    return _ARouter.getInstance().build(path);
}

这里是调用了 _ARouter 的同名办法,回来了 Postcard(意为明信片)。ARouter实践是运用了外观形式(规划形式的一种),其一切办法都是调用了_ARouter的同名办法。 进入_ARouter:

protected Postcard build(String path) {
    if (TextUtils.isEmpty(path)) { //path不能为空
        throw new HandlerException(Consts.TAG + "Parameter is invalid!");
    } else {
        //path替换,这是预处理
        PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
        if (null != pService) {
            path = pService.forString(path);
        }
        return build(path, extractGroup(path), true);
    }
}

这里对path做了空校验和预处理替换。如果想重写path,能够写一个PathReplaceService完结类。接着又走到重载办法:

protected Postcard build(String path, String group, Boolean afterReplace) {
    if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
        throw new HandlerException(Consts.TAG + "Parameter is invalid!");
    } else {
        ...
        return new Postcard(path, group);
    }
}

其间参数group是经过extractGroup(path)获取,也便是path的榜首级,即”/test/activity”中的”test”。group的作用是作为路由的默认分组。

路由中的分组概念:

  • SDK中针对一切的途径(/test/1、/test/2)进行分组,分组只要在分组中的某一个途径榜初次被拜访的时分,该分组才会被初始化
  • 能够经过 @Route 注解自动指定分组,不然运用途径中榜首段字符串(/*/)作为分组
  • 留意:一旦自动指定分组之后,应用内路由需求运用 ARouter.getInstance().build(path, group) 进行跳转,手动指定分组,不然无法找到 @Route(path = "/test/1", group = "app")

最终回来创立的Postcard实例。Postcard是明信片的意思,承载了一次跳转/路由 需求的一切信息,它承继自路由元信息 RouteMeta

public final class Postcard extends RouteMeta {
    // Base
    private Uri uri;                //运用Uri办法建议的路由
    private Object tag;             // A tag prepare for some thing wrong. inner params, DO NOT USE!
    private Bundle mBundle;         //发动activity的传入的Bundle
    private int flags = 0;         // 发动activity的Flags
    private int timeout = 300;      // 路由超时时刻
    private IProvider provider;     // 如果是获取provider,就有值
    private boolean greenChannel;  //是绿色通道
    private SerializationService serializationService;
    private Context context;        //context
    private String action;          //发动activity的action
    // activity转场动画相关
    private Bundle optionsCompat;
    private int enterAnim = -1;
    private int exitAnim = -1;
    public Postcard(String path, String group) {
        this(path, group, null, null);
    }
    public Postcard(String path, String group, Uri uri, Bundle bundle) {
        setPath(path);
        setGroup(group);
        setUri(uri);
        this.mBundle = (null == bundle ? new Bundle() : bundle);
    }
    ...
}
public class RouteMeta {
    private RouteType type;         // 路由类型;activity、service、fragment、IProvider等,编译时会依据被@Route注解的类的类型来设置
    private Element rawType;        // 路由原始类型,在编译时用来判别
    private Class<?> destination;   // 目的地:具体的 XxxActivity.class等
    private String path;            // Path
    private String group;           // Group
    private int priority = -1;      // 优先级,越小优先级越高
    private int extra;              // Extra
    private Map<String, Integer> paramsType;  // 参数类型,例如activity中运用@Autowired的参数类型
    private String name; //路由姓名,用于生成javadoc
    private Map<String, Autowired> injectConfig;  // 参数配置(对应paramsType).
}
  • Postcard:路由的信息。 了解为是生活中的明信片。承继自RouteMeta。例如Postcard中的mBundle等则是 明信片上寄件人写的 “你好,xxx 节日快乐!” 这种文字内容。
  • RouteMeta:路由元信息,即根底信息。 了解便是 明信片上的 收件人地址 这种必备的根底信息。 明信片上能够没有文字内容,但想要被邮递就必须有收件人地址

2.2 路由进程

2.2.1 全体步骤

经过path构建了PostCard后调用了其navigation()办法,也便是开始了路由进程:

public Object navigation() {
    return navigation(null);
}
public Object navigation(Context context) {
    return navigation(context, null);
}
public Object navigation(Context context, NavigationCallback callback) {
    return ARouter.getInstance().navigation(context, this, -1, callback);
}

看到接连走了两个重载办法,最终走到ARouter的navigation办法,而且把自己传了进去。ARouter的navigation办法同样会走到_ARouter的同名办法:

 // @param context     Activity or null.
 // @param postcard    Route metas
 // @param requestCode RequestCode,startActivity的requestCode
 // @param callback    cb,路由回调:找到目的地、未找到、中止、抵达
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    //若有PretreatmentService的完结,就进行预处理。能够在真实路由前进行一些判别然后中止路由。
    PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);
    if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {
        // 预处理失回来false,路由取消.
        return null;
    }
    // 给路由设置context,例如发动activity需求。如果没有传就运用mContext,即初始化ARouter时传入的Application
    postcard.setContext(null == context ? mContext : context);
    try {
        // 1. 完善postcard信息(现在只要path、group,还需求知道具体目的地,例如要跳转到的activity信息)
        LogisticsCenter.completion(postcard);
    } catch (NoRouteFoundException ex) {
        //这里便是debug包中,没找到路由目的地时 经常出现的提示
        if (debuggable()) {
            runInMainThread(new Runnable() {
                public void run() {
                    Toast.makeText(mContext, "There's no route matched!\n" +" Path = [" + postcard.getPath() + "]\n" +
                            " Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show();
                }
            });
        }
        //没找到的回调
        if (null != callback) {
            callback.onLost(postcard);
        } else {
            // 没有callback的话, (如果有)就回调到下降服务 
            DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
            if (null != degradeService) {
                degradeService.onLost(context, postcard);
            }
        }
        return null;
    }
    //找到的回调
    if (null != callback) {
        callback.onFound(postcard);
    }
    // 2. 不是绿色通道的话,要先走拦截器
    if (!postcard.isGreenChannel()) {
        interceptorService.doInterceptions(postcard, new InterceptorCallback() {
            //拦截器处理成果:持续路由
            @Override
            public void onContinue(Postcard postcard) {
                // 3. 获取路由成果
                _navigation(postcard, requestCode, callback);
            }
             //拦截器处理成果:中止路由,回调中止
            @Override
            public void onInterrupt(Throwable exception) {
                if (null != callback) {
                    callback.onInterrupt(postcard);
                }
            }
        });
    } else {
        //绿色通道,不走拦截器,就获取路由成果
        return _navigation(postcard, requestCode, callback);
    }
    return null;
}

运用前面构建好的PastCard经过全体3个步骤,就完结了路由:

  1. 完善postcard信息:只要path、group还不可,还需求知道具体目的地,例如要跳转到的Activity信息。
  2. 拦截器处理:不是绿色通道的话,要先经过路由器的处理,成果是中止或许持续。
  3. 获取路由成果:例如进行方针Activity的跳转、获取IProvider完结方针、获取Fragment等。

2.2.2 获取路由成果

先来看比较简略的最终一个步骤——路由成果获取进程,也便是_navigation()办法:

private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    final Context currentContext = postcard.getContext();
    //依据路由类型来走对应逻辑
    switch (postcard.getType()) {
        case ACTIVITY:
            // Activity, 运用postcard.getDestination()来构建intent、传入Extras、设置 flags、action
            final Intent intent = new Intent(currentContext, postcard.getDestination());
            intent.putExtras(postcard.getExtras());
            int flags = postcard.getFlags();
            if (0 != flags) {
                intent.setFlags(flags);
            }
            // 当时的context不是activity,需求增加flag:FLAG_ACTIVITY_NEW_TASK
            //(发动startActivity需有任务栈的,application/service发动activity没有任务栈,所以必需求new_task_flag新建一个任务栈)
            if (!(currentContext instanceof Activity)) {
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            }
            String action = postcard.getAction();
            if (!TextUtils.isEmpty(action)) {
                intent.setAction(action);
            }
            // 最终在主线程履行 熟悉的startActivity,
            runInMainThread(new Runnable() {
                @Override
                public void run() {
                    startActivity(requestCode, currentContext, intent, postcard, callback);
                }
            });
            break;
        case PROVIDER:
            //provider,指的是想要获取的服务,即IProvider的完结类。直接从postCard中获取。
            return postcard.getProvider();
        case BOARDCAST:
        case CONTENT_PROVIDER:
        case FRAGMENT:
        //Broadcast、ContentProvider、Fragment,都是运用postcard.getDestination()反射创立实例
            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;
            } ...
    }
    return null;
}

从上面可见,postcard 经过完善后,路由类型type、目的地destination等都现已被赋了值。destination便是方针类的class方针。

办法内容便是依据路由类型来走对应逻辑:

  • Activity, 运用postcard.getDestination()来构建intent、传入Extras、设置 flags、action,最终在主线程履行 熟悉的startActivity
  • provider,指的是想要获取的服务,即IProvider的完结类。是直接从postCard中获取的,由于服务类是单例,只会在初次获取时创立()。
  • Broadcast、ContentProvider、Fragment,都是运用postcard.getDestination()反射创立实例

全体逻辑仍是比较简略的。这里你或许好奇 destination的值是怎样获取的,由于不管哪种类型的路由,都是要运用方针class,这个便是ARouter最为中心的内容——怎样获取 无直接依靠的模块的 class方针,也便是完善postcard信息的进程。 不过咱们先来把拦截器逻辑剖析完,最终再来看这个中心点。

2.2.3 拦截器

拦截器形式是开发中常用规划形式之一,路由中也能够设置拦截器,对途径进行判别决议是否需求中止。

未设置绿色通道的路由需求经过拦截器处理,也便是interceptorService的doInterceptions()办法。interceptorService是啥呢?

final class ARouter {
    ...
    private static InterceptorService interceptorService;
    ...
    //ARouter的初始化办法
    public static void init(Application application) {
        if (!hasInit) {
            logger = _ARouter.logger;
            hasInit = _ARouter.init(application);
            if (hasInit) {
                _ARouter.afterInit();
            }
        }
    }
   ...
}
    //_ARouter.java
    static void afterInit() {
        interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
    }

InterceptorService承继IProvider,可见interceptorService也是一个服务,在ARouter初始化后 获取,用于处理拦截器的逻辑。具体的完结类是InterceptorServiceImpl:

@Route(path = "/arouter/service/interceptor")
public class InterceptorServiceImpl implements InterceptorService {
...
    @Override
    public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
        //有拦截器
        if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
            ...
            LogisticsCenter.executor.execute(new Runnable() { //放入线程池异步履行
                @Override
                public void run() { //interceptorCounter 用于保证一切拦截器都走完,而且设置了超时
                    CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
                    try {//履行榜首个拦截器,如果没有中止 则递归调用持续后边的拦截器
                        _execute(0, interceptorCounter, postcard);
                        interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
                        if (interceptorCounter.getCount() > 0) {    // count>0阐明超时了,拦截器还没处理完.
                            callback.onInterrupt(new HandlerException("The interceptor processing timed out."));
                        } else if (null != postcard.getTag()) {    //Tag!=null阐明被某个拦截器回调中止了
                            callback.onInterrupt((Throwable) postcard.getTag());
                        } else {
                            callback.onContinue(postcard); // 一切拦截器处理完、没超时、也没反常,则持续路由
                        }
                    }...
                }
            });
        } else {
            //没有拦截器则持续路由
            callback.onContinue(postcard);
        }
    }
    private static void _execute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
        if (index < Warehouse.interceptors.size()) {
            //从Warehouse.interceptors中获取第index个拦截器,走process办法,如果回调到onContinue就持续下一个;
            IInterceptor iInterceptor = Warehouse.interceptors.get(index);
            iInterceptor.process(postcard, new InterceptorCallback() {
                @Override
                public void onContinue(Postcard postcard) {
                    counter.countDown();
                    _execute(index + 1, counter, postcard);  // 持续下一个
                }
                @Override
                public void onInterrupt(Throwable exception) {
                    postcard.setTag(null == exception ? new HandlerException("No message.") : exception);    // save the exception message for backup.
                    counter.cancel();
                    ...
                }
            });
        }
    }
    @Override //此init办法会在服务被创立后调用。这里便是反射创立一切的拦截器实例
    public void init(final Context context) {
        LogisticsCenter.executor.execute(new Runnable() {
            @Override
            public void run() {
                if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
                    //遍历Warehouse.interceptorsIndex ,运用存储与其间的拦截器class方针反射创立拦截器实例
                    for (Map.Entry<Integer, Class<? extends IInterceptor>> entry : Warehouse.interceptorsIndex.entrySet()) {
                        Class<? extends IInterceptor> interceptorClass = entry.getValue();
                        try {
                            IInterceptor iInterceptor = interceptorClass.getConstructor().newInstance();
                            iInterceptor.init(context);
                            //存入 Warehouse.interceptors
                            Warehouse.interceptors.add(iInterceptor);
                        }...
                    }
                    interceptorHasInit = true;
                    ...
                }
            }
        });
    }...
}

doInterceptions()办法中判别如果有拦截器,就放入线程池异步履行榜首个拦截器,且运用interceptorCounter 保证一切拦截器都走完,同时也设置了超时。 如果榜首个拦截器没有回调中止 则递归调用持续后边的拦截器。

拦截器的履行,是从Warehouse.interceptors中获取第index个拦截器,走process办法,如果回调到onContinue就持续下一个;若回调onInterrupt就中止路由。

拦截器的履行逻辑仍是比较明晰的。那么拦截器是怎样获取的呢?咱们来看下InterceptorServiceImpl的init办法:init()办法会在服务被创立后当即调用,如上所示便是遍历Warehouse.interceptorsIndex ,运用存储在其间的拦截器class方针 反射创立拦截器实例,然后存在存入 Warehouse.interceptors。 也便是说,ARouter初始化完结后就获取到了一切拦截器实例

那么Warehouse又是啥呢?interceptorsIndex是怎样存储的一切拦截器的class的?

2.2.4 路由元信息的收集

Warehouse意为库房,用于存放被 @Route、@Interceptor注释的 路由相关的信息,也便是咱们重视的destination等信息。既然是库房,那么便是有存有取

前面举的例子:moduleB建议路由跳转到moduleA的activity,moduleB没有依靠moduleA,只是在moduleA的activity上增加了@Route注解。 由于进行activity跳转需求方针Activity的class方针来构建intent,所以必须有一个中间人,把途径”/test/activity”翻译成Activity的class方针,然后moduleB才能完结跳转。(因此在ARouter的运用中 moduleA、moduleB 都是需求依靠 arouter-api的)

这个中间人那便是ARouter了,而这个翻译工所作用到的词典便是 Warehouse,它存着一切路由信息。

class Warehouse {
    //一切IRouteGroup完结类的class方针,是在ARouter初始化中赋值,key是path榜首级
    //(IRouteGroup完结类是编译时生成,代表一个组,即path榜首级相同的一切路由,包含Activity和Provider服务)
    static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>(); 
    //一切路由元信息,是在completion中赋值,key是path
    //初次进行某个路由时就会加载整个group的路由,即IRouteGroup完结类中一切路由信息。包含Activity和Provider服务
    static Map<String, RouteMeta> routes = new HashMap<>();
    //一切服务provider实例,在completion中赋值,key是IProvider完结类的class
    static Map<Class, IProvider> providers = new HashMap<>();
    //一切provider服务的元信息(完结类的class方针),是在ARouter初始化中赋值,key是IProvider完结类的全类名。
    //主要用于运用IProvider完结类的class建议的获取服务的路由,例如ARouter.getInstance().navigation(HelloService.class)
    static Map<String, RouteMeta> providersIndex = new HashMap<>();
    //一切拦截器完结类的class方针,是在ARouter初始化时收集到,key是优先级
    static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("...");
    //一切拦截器实例,是在ARouter初始化完结后当即创立
    static List<IInterceptor> interceptors = new ArrayList<>();
...
}

Warehouse存了哪些信息呢?

  • groupsIndex一切路由组元信息。是一切IRouteGroup完结类的class方针,是在ARouter初始化中赋值,key是path榜首级。IRouteGroup完结类是编译时生成,代表一个组,即path榜首级相同的一切路由,包含Activity和Provider服务)。
  • routes一切路由元信息。是在LogisticsCenter.completion中赋值,key是path。初次进行某个路由时就会加载整个group的路由,即IRouteGroup完结类中一切路由信息。包含Activity和Provider服务
  • providers一切服务provider实例。在LogisticsCenter.completion中赋值,key是IProvider完结类的class
  • providersIndex一切provider服务元信息(完结类的class方针)。是在ARouter初始化中赋值,key是IProvider完结类的全类名。用于运用IProvider完结类class建议的获取服务的路由,例如ARouter.getInstance().navigation(HelloService.class)
  • interceptorsIndex一切拦截器完结类class方针。是在ARouter初始化时收集到,key是优先级
  • interceptors一切拦截器实例。是在ARouter初始化完结后当即创立

其间groupsIndex、providersIndex、interceptorsIndex是ARouter初始化时就预备好的根底信息,为事务中随时建议路由操作(Activity跳转、服务获取、拦截器处理)做好预备。

那么Warehouse的信息是怎样收集到的呢? 下面就先来看下ARouter初始化具体做了哪些工作:

final class _ARouter {
    ...
    protected static synchronized boolean init(Application application) {
        mContext = application;
        LogisticsCenter.init(mContext, executor);
        ...
        return true;
    }

_ARouter的init办法中 调用了LogisticsCenter的init办法。LogisticsCenter意为物流中心,上面说到的完善postcard的completion操作也是此类供给。

//LogisticsCenter.java
//LogisticsCenter初始化,加载一切的路由元信息
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {...
    try {
        long startInit = System.currentTimeMillis();
        //先测验运用AGP transform 收集 根协助类 后 写好的注入代码(要先引进插件才行 apply plugin: 'com.alibaba.arouter')
        loadRouterMap();
        if (registerByPlugin) {
            //registerByPlugin为true阐明运用AGP加载ok了(通常都会用AGP,即registerByPlugin为true)
            logger.info(TAG, "Load router map by arouter-auto-register plugin.");
        } else {
            //若没有运用 AGP transform,就用ClassUtils.getFileNameByPackageName来收集dex中ROUTE_ROOT_PAKCAGE包下的一切类,即编译时生成的一切协助类
            //这样的话,便是运行时 遍历收集 会比较耗时,也便是init会较为耗时;而AGP transform 是在编译时完结收集的。
            //当时app是新装置时才会走(收集到的协助类会缓存到SP文件)
            Set<String> routerMap;
            if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
                logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
                // 这写协助类是在编译时由arouter-compiler生成
                routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
                if (!routerMap.isEmpty()) {
                    context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
                }
                PackageUtils.updateVersion(context);
            } else {
                //不是新装置的版本,就从SP文件中读取
                routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
            }...
            //遍历协助类,区分是哪种协助类,然后反射创立协助类实例后,调用其loadInto办法来填充Warehouse相应的Map
            for (String className : routerMap) {
                if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
                    //类名最初:com.alibaba.android.arouter.routes.ARouter$$Root
                    //填充Warehouse.groupsIndex,即一切IRouteGroup完结类的class方针
                    ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
                    //类名最初:com.alibaba.android.arouter.routes.ARouter$$Interceptors
                    //填充Warehouse.interceptorsIndex,即一切IInterceptor完结类的class方针
                    ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
                } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
                    //类名最初:com.alibaba.android.arouter.routes.ARouter$$Providers
                    //填充Warehouse.providersIndex,即一切provider的RouteMeta
                    ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
                }
            }
        }...
    }
}

LogisticsCenter初始化 便是加载一切的路由元信息的进程,有两种办法:

  1. 走loadRouterMap()办法:直接运用在编译时收集好的协助类信息,然后反射创立协助类实例后,调用其loadInto办法来填充Warehouse相应的Map。这需求开发者要先引进插件才行 apply plugin: ‘com.alibaba.arouter’。如果加载成功,registerByPlugin这个值就为true,不然false。
  2. 若榜首步没有加载成功,即registerByPlugin为false:就会运用ClassUtils.getFileNameByPackageName在运行时收集dex中”com.alibaba.android.arouter.routes“包下的一切类(即协助类),然后遍历,区分是哪种协助类。接着反射创立协助类实例后,调用其loadInto办法来填充Warehouse相应的Map。

两种办法比照:

  • 相同点:都是运用协助类信息反射创立协助类实例后,调用其loadInto办法来填充Warehouse相应的Map。
  • 不同点:在于协助类信息的收集办法。前者是在编译时收集就结束了,后者是运行时。

一般都是运用榜首种,由于运行时遍历dex收集会比较耗时,而榜首种在编译时现已收集好了。 榜首种办法的loadRouterMap()办法的完结逻辑 咱们稍后再看。

先看两个问题:

  • 上面说到的协助类是个啥? 怎样运用协助类填充Warehouse的?
  • 为啥协助类还要收集?还分 编译时收集、运行时收集?

2.2.4.1 拦截器元信息

咱们先来看拦截器元信息(拦截器class信息)是怎样经过协助类填充的:

“终于懂了” 系列:组件化框架 ARouter 完全解析(一) 原理详解

上图是ARouter工程编译后module-java的build目录,ARouter$$ 最初的这些类都是在ARouter在编译进程中生成,它们便是所谓的协助类:

ARouter$$Interceptors$$modulejava 这个类便是一个协助类,协助WareHouse填充WareHouse.interceptorsIndex。它完结接口IInterceptorGroup,loadInfo办法承受一个Map<Integer, Class<? extends IInterceptor>>,也便是WareHouse.interceptorsIndex的类型。loadInfo办法体内,是用接纳的map来put当时module一切拦截器的class,即运用 @Interceptor 注解并完结 IInterceptor 接口的类。

在上面LogisticsCenter的init办法中第二种加载办法中看到,确实是遍历收集到的协助类,然后运用类名判别是 ARouter$$Interceptors$$modulejava,接着就调用loadInfo办法,这就完结了对WareHouse.interceptorsIndex的赋值。 也便是说,有了ARouter$$Interceptors$$modulejava ,咱们就能在ARouter初始化时对WareHouse.interceptorsIndex进行赋值,就为创立一切拦截器实例做好了预备。

那么到这里,关于拦截器还有一个问题,ARouter$$Interceptors$$modulejava的loadInfo办法中 拦截器完结类class是怎样获取的呢?—— 当然是编译时对注解 @Interceptor 的解析,解析进程将在下篇中介绍。

拦截器协助类咱们看完了,再来看看其他三种协助类。

2.2.4.2 路由组元信息

路由组元信息的收集是经过 —— ARouter$$Root$$xxx —— 根协助类:即用来协助对 WareHouse.groupsIndex 赋值。这样就会把path榜首级相同的所以路由分到同一个组中。 一个module对应一个根协助类。xxx是module名,便是在build.gradle中配置的 AROUTER_MODULE_NAME 。

“终于懂了” 系列:组件化框架 ARouter 完全解析(一) 原理详解

如上图,ARouter$$Root$$modulejava 便是根协助类,协助WareHouse填充Warehouse.groupsIndex。完结自IRouteRoot接口,loadInfo办法承受一个Map<String, Class<? extends IRouteGroup>>,也便是WareHouse.groupsIndex 的类型。loadInfo办法体内,是用接纳的map来put当时module一切路由组协助类的class

在上面LogisticsCenter的init办法中同样 对遍历收集到的协助类判别类名,接着就调用loadInfo办法,这就完结了对WareHouse.groupsIndex 的赋值。

根协助类,目的便是对路由进行分组,分组的优点是防止一次性加载一切路由,减少反射耗时和内存占用的性能问题。

根协助类也是在编译时生成,具体生成进程将在下篇中介绍。

2.2.4.3 路由元信息

路由元信息的收集是经过 —— ARouter$$Group$$xxx —— 组协助类:即用来协助对 WareHouse.routes 赋值。也便是把同组的路由put到WareHouse.routes。 一个module或许有多个组,即对应有多个根协助类,xxx是组名,即path榜首级。

“终于懂了” 系列:组件化框架 ARouter 完全解析(一) 原理详解

如上图,ARouter$$Group$$test 便是组协助类,协助WareHouse填充Warehouse.routes。完结自IRouteGroup接口,loadInfo办法承受一个Map<String, RouteMeta>,也便是WareHouse.routes 的类型。loadInfo办法体内,是用接纳的map来put 当时组 的一切路由元信息其间最重要的便是 每个路由的方针类class

在上面LogisticsCenter的init办法中 没有看到对组协助类的处理。ARouter的规划是:在运用时才进行加载,即初次运用某个组的路由时,才会运用组协助类对 WareHouse.routes 进行填充。

组协助类,目的便是 初次运用时 一次性加载本组一切路由元信息。这比较契合实践运用场景:一般同组的路由都是同事务的内容,当时用户进入此事务时,就把本组路由元信息预备好,是比较合理的。

组协助类也是在编译时生成。

2.2.4.4 provider元信息

provider元信息 其实在上面 路由元信息 中现已包含了,为啥还要独自拎出来呢?咱们回头看下 _ARouter的navigation办法:

protected <T> T navigation(Class<? extends T> service) {
    Postcard postcard = LogisticsCenter.buildProvider(service.getName());
    if (null == postcard) {
        postcard = LogisticsCenter.buildProvider(service.getSimpleName());
    }
    if (null == postcard) {
        return null;
    }
    postcard.setContext(mContext);
    LogisticsCenter.completion(postcard);
    return (T) postcard.getProvider();
    ...
}
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
    ...
}

前面介绍了4个参数的办法,而上面这个传服务class的重载办法 便是独自给获取provider服务运用的。看到经过LogisticsCenter运用服务类name获取到了PostCard,然后经过完善PostCard,直接获取provider服务。

//LogisticsCenter.java
public static Postcard buildProvider(String serviceName) {
    RouteMeta meta = Warehouse.providersIndex.get(serviceName);
    if (null == meta) {
        return null;
    } else {
        return new Postcard(meta.getPath(), meta.getGroup());
    }
}

其间Postcard其实便是从 Warehouse.providersIndex 中获取到的RouteMeta后构建的。而Warehouse.providersIndex的赋值便是经过 ——ARouter$$Providers$$xxx —— Provider协助类

“终于懂了” 系列:组件化框架 ARouter 完全解析(一) 原理详解

协助类或许在其他文章中叫路由表,之前想叫做署理类,但觉得协助类更适宜,它们便是用来协助填充WareHouse中的元数据的。

2.2.5 AGP办法加载路由

咱们来看下路由信息收集的榜首种办法,这是一般都会运用到的办法,也便是loadRouterMap()办法:

public class LogisticsCenter {
    private static boolean registerByPlugin;
    private static void loadRouterMap() {
        registerByPlugin = false;
        //自动注册插件 会在此处刺进代码。调用此办法就注册了全部的 Routers、Interceptors、Provider
    }
   ...

你会惊奇地发现,loadRouterMap()竟然只要一行代码?!

反编译 ARouter demo APK后,检查LogisticsCenter:

“终于懂了” 系列:组件化框架 ARouter 完全解析(一) 原理详解

看到编译后的loadRouterMap()办法,多了几行register()办法的调用,而参数便是 一切的根协助类、拦截器协助类、provider协助类。

//LogisticsCenter.java
...
 private static void register(String className) {
        if (!TextUtils.isEmpty(className)) {
            try {
                Class<?> clazz = Class.forName(className);
                Object obj = clazz.getConstructor().newInstance();
                if (obj instanceof IRouteRoot) {
                    registerRouteRoot((IRouteRoot) obj);
                } else if (obj instanceof IProviderGroup) {
                    registerProvider((IProviderGroup) obj);
                } else if (obj instanceof IInterceptorGroup) {
                    registerInterceptor((IInterceptorGroup) obj);
                } ...
            } ...
        }
    }
    private static void registerRouteRoot(IRouteRoot routeRoot) {
        markRegisteredByPlugin();
        if (routeRoot != null) {
            routeRoot.loadInto(Warehouse.groupsIndex);
        }
    }
    private static void registerInterceptor(IInterceptorGroup interceptorGroup) {
        markRegisteredByPlugin();
        if (interceptorGroup != null) {
            interceptorGroup.loadInto(Warehouse.interceptorsIndex);
        }
    }
    private static void registerProvider(IProviderGroup providerGroup) {
        markRegisteredByPlugin();
        if (providerGroup != null) {
            providerGroup.loadInto(Warehouse.providersIndex);
        }
    }
    private static void markRegisteredByPlugin() {
        if (!registerByPlugin) {
            registerByPlugin = true; //符号经过AGP加载成功了
        }
    }

register()办法很简略,便是反射创立协助类实例,调用loadInto办法对Warehouse进行填充,和第二种路由信息收集办法的是一致的。 而第二种是在运行时遍历dex才找到的协助类,在榜首种办法就奇特的直接出现了?

这个奇特的操作,咱们将在第三篇文章做具体介绍,现在只需知道是在编译时进行扫描并动态在loadRouterMap()中刺进代码就能够了。

2.2.3 路由信息的完善

上面兜了一大圈,从路由全体进程、获取路由成果、拦截器、路由信息记载,到各个协助类的介绍,也便是说 咱们了解了 路由的建议、路由全体进程、路由成果获取,以及路由元信息的加载,那么现在就来看看路由元信息是怎样运用的。

//LogisticsCenter.java
public synchronized static void completion(Postcard postcard) {
    //完善postcard信息(现在只要path、group,还需求知道具体目的地,例如要跳转到的activity信息)
    RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
    if (null == routeMeta) {
        //没有从Warehouse.routes获取到:要么不存在、要么还没有加载本组路由
        //先看路由库房中是否有这个组协助类,没就反常。(库房里的已有 组协助类 是谁放进库房的呢?便是 在 ARouter.init中调用 LogisticsCenter.init的时分。它里边的 loadRouterMap() 中履行代码是 transform时收集到的 APT 生成 根协助类 的load办法。)
        if (!Warehouse.groupsIndex.containsKey(postcard.getGroup())) {
            throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
        } else {
            try {
                //这里,库房中有这个组的协助类,那么就能够 加载 这个组的一切路由 到内存
                addRouteGroupDynamic(postcard.getGroup(), null);
            } catch (Exception e) {
                throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
            }
            //库房有了这个组的路由信息,再重新完善
            completion(postcard);
        }
    } else {
        //有路由信息,就完善postcard
        postcard.setDestination(routeMeta.getDestination());
        postcard.setType(routeMeta.getType());
        postcard.setPriority(routeMeta.getPriority());
        postcard.setExtra(routeMeta.getExtra());
        ...
        switch (routeMeta.getType()) {
            case PROVIDER:  //provider, 获取实例
                // 要完结自IProvider
                Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
                IProvider instance = Warehouse.providers.get(providerMeta);
                if (null == instance) { // 没有,就反射创立
                    IProvider provider;
                    try {
                        provider = providerMeta.getConstructor().newInstance();
                        provider.init(mContext);
                        Warehouse.providers.put(providerMeta, provider); //实例存入库房
                        instance = provider;
                    } catch (Exception e) {...
                    }
                }
                postcard.setProvider(instance); //实例经过PostCard带出去
                postcard.greenChannel();    // Provider 不用经过拦截器
                break;
            case FRAGMENT:
                postcard.greenChannel();    // Fragment 不用经过拦截器
        }
    }
}
public synchronized static void addRouteGroupDynamic(String groupName, IRouteGroup group) {
    if (Warehouse.groupsIndex.containsKey(groupName)){//这里,库房中 有这个组的协助类
        //拿到这个 组协助类,实例化,调loadInfo 把 这个组一切的路由信息加载 到 库房中的routes。
        Warehouse.groupsIndex.get(groupName).getConstructor().newInstance().loadInto(Warehouse.routes);
        Warehouse.groupsIndex.remove(groupName);
    }...
}
  1. 测验经过path从库房中获取对应的路由元信息,如果没有获取到:要么不存在、要么还没有加载本组路由。
  2. 先看路由库房中是否有这个组协助类,没就抛出反常;有就经过addRouteGroupDynamic()加载这个组的一切路由,然后再调completion
  3. 有了path对应的路由元信息,就同步到postCard中,其间最重要的便是 方针类class——routeMeta.getDestination()。而且判别如是provider就创立服务实例并存入库房。

好了,到这里,咱们总算能够解答最开始提出的问题了:ARouter最为中心的内容——怎样获取 无直接依靠的模块的 class方针

  • 编译时ARouter依据注解 @Route 生成了各个协助类,协助类的loadInfo办法中包含了路由方针信息,最重要的是注解的类class,然后在ARouter初始化时依据 根协助类、provider协助类、拦截器协助类 对库房WareHouse的groupsIndex进行赋值(以及providersIndex、interceptorsIndex),然后在路由建议后,依据path经过WareHouse的groupsIndex 加载 同组的一切路由元信息,也便是拿到了方针class。
  • 笼统一下便是:moduleA先把方针class存入第三方库房——ARouter的WareHouse,然后muduleB建议路由时从库房中依据path获取方针class,ARouter便是这个库房的管理者。 就比如 邮政是函件的管理者,它是两方通讯者的中间人。

2.3 流程图

以上剖析内容整理成流程图:

“终于懂了” 系列:组件化框架 ARouter 完全解析(一) 原理详解

三、总结

咱们从路由建议开始使,介绍了整个路由具体进程:moduelA经过中间人ARouter把路由信息的存到库房WareHouse;moduleB建议路由时,再经过中间人ARouter从库房WareHouse取出路由信息,这要就完结了没有依靠的两者之间的跳转与通讯。其间触及Activity的跳转、服务provider的获取、拦截器的处理等。

需求重点了解的是:路由结构的全体思路,经过中间人ARouter运用WareHouse加载和获取路由信息;路由信息加载完结原理,各协助类作用和路由完善进程。

其间ARouter在编译时生成的协助类,是用于对一切运用@Route、@Interceptor注解的类信息的分组和收集,编译运行时对路由信息库房Warehouse的填充和运用。这里触及到的是Annotation Process ToolAPT)技能,即注解处理东西。

怎样运用编译时生成的协助类呢?除了运行时查找dex,还能够在编译时扫描协助类信息,而且直接在物流中心LogisticsCenter loadRouterMap()办法中直接刺进运用协助类的代码,这里触及 Android Gradle PluginAGP)技能,即Android的gradle插件相关技能。

ARouter怎样在编译时解析注解、怎样生成协助类以及对APT技能的介绍将在本系列第二篇中具体介绍。

ARouter怎样在编译时扫描方针协助类、怎样注入代码以及对AGP技能的介绍将在本系列第三篇中具体介绍。

好了本篇就到这里,欢迎持续重视~

你的 点赞、评论,是对我的巨大鼓励!

欢迎重视我的大众号 胡飞洋,文章更新可榜首时刻收到;

如果有问题或许想进群,号内有加我微信的进口,我拉你进技能讨论群。在技能学习、个人成长的道路上,咱们一同前进!

我正在参与技能社区创作者签约方案招募活动,点击链接报名投稿。