ARouter实现原理解析

相关角色:

ARouter:负责提供客户端使用的Api接口,采用了门面模式,实际上内部委托给了_ARouter去处理
_ARouter:路由中心控制器,负责控制整个路由的流程,通过Postcard中的信息导航客户端到目标地址(启动某个Activity或者获取某个服务的实现等)
LogisticsCenter:后勤中心,负责注册路由信息到Warehouse和根据path或者Postcard到数据仓库中获取数据,再生成相关对象
Warehouse:数据仓库,负责存储路由配置信息和具体生成的IProvider对象等。该类基本上都是一些数据集合,没有任何逻辑处理
Postcard:明信片,RouteMeta类的子类,用于描述一个路由的具体信息,比如,目标组件类型(Activity||IProvider等)、目标组件需要的参数,
RouteMeta:路由信息描述类,存储目标地址的类型,路径,参数等信息,LogisticsCenter根据RouteMeta对象描述的信息创建明信片。
IRouteGroup:多个RouteMeta数据的容器,类似ViewGroup与View的关系
IProvider:服务提供者,每一个实现该接口的类视为一个独立的服务,外部客户端可以根据path获取到该服务。
IInterceptor:拦截器,客户端可以通过注册IInterceptor的实现类来实现路由的拦截,其拦截流程控制是在子线程中按照注册顺序依次调用拦截器的process方法将拦截权释放给客户端。其拦截控制器的实现在InterceptorServiceImpl类中。
PathReplaceService:路径替换服务接口,实现者需要将path转换为另一个path

初始化流程:

ARouter框架能将多个服务提供者隔离,减少相互之间的依赖。其实现的流程和我们平常的快递物流管理很类似,每一个具体的快递包裹就是一个独立的服务提供者(IProvider),每一个快递信息单就是一个RouteMeta对象,客户端就是快递的接收方,而使用@Route注解中的path就是快递单号。在初始化流程中,主要完成的工作就是将所有注册的快递信息表都在物流中心(LogisticsCenter)注册,并将数据存储到数据仓库中(Warehouse)。
初始化的入口是ARouter的init方法,其主要是控制初始化的流程,自身不处理具体实现,而是都委托给_ARouter去处理。

public static void init(Application application) {
        if (!hasInit) {
            //委托给_ARouter去初始化
            hasInit = _ARouter.init(application);
            if (hasInit) {
               //初始化之后调用afterInit
                _ARouter.afterInit();
            }
        }
    }

_ARouter方法的init方法实际是调用LogisticsCenter的init方法,下面是其核心代码:

                Set<String> routerMap;
                if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
                    //扫描apk中所有类,找到ROUTE_ROOT_PAKCAGE包下的类,实在子线程中完成的
                    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);    // Save new version name when router map update finish.
                } else {
                    logger.info(TAG, "Load router map from cache.");
                    routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
                }
                for (String className : routerMap) {
                    if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_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);
                    }
                }

LogisticsCenter的init方法主要完成了下面几件事:

  • 找到com.alibaba.android.arouter.routes包下的所有class文件类名,如果本地缓存的数据有效就从本地获取,如果有更新或者是debug模式,则通过扫描安装包的dex文件获取
  • 根据找到的类名去加载相关的实例到Warehouse中(类似与快递信息表入库)
    实际上com.alibaba.android.arouter.routes包下的class是由注解解析器自动生成的,主要IRouteRoot,IRouteGroup和IProviderGroup的实现类,比如当我们使用@Route注解某个类时,会自动将这个类的信息注入的到自动生成的上述实现类中。
    完成初始化之后会调用afterInit方法,其主要就是注入拦截控制服务(InterceptorServiceImpl)
static void afterInit() {
        interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
    }
//build方法主要是将path和group封装到Postcard中,可以理解成根据快递号生成一个快递信息表
protected Postcard build(String path, String group) {
        if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
            throw new HandlerException(Consts.TAG + "Parameter is invalid!");
        } else {
            PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
            if (null != pService) {
                path = pService.forString(path);
            }
            return new Postcard(path, group);
        }
    }

而navigation方法是根据快递信息表来生成具体的实例,这里的拦截服务控制器实际是InterceptorServiceImpl对象,后面分析具体路由的实现时再看其具体的代码实现。整个初始化流程到注入InterceptorServiceImpl后就基本完成了

如何实现路由功能:

这里以一个具体的使用场景来看下路由的具体实现,我们实现了一个微博分享的服务,并使用@Route标注该服务。如下:

@Route(path = "/service/WBShareService")
public class WBShareServiceImp implements IShareService extends IProvider{
      @Override
    public void doShareImage(String text, String title, String path, boolean onlyClient) {
    }
}

然后客户端需要分享图片到微博时的使用代码如下:

Object obj = ARouter.getInstance().build("/service/WBShareService").navigation();
if (obj instanceof IShareService) {
   ((IShareService)obj).doShareImage("", "", "", false);
}

前面我们分析过ARouter的build方法了,其就是根据path生成一个Postcard对象,这里我们接着分析navigation方法,postcard对象的navigation方法最终都是委托给_ARouter的navigation方法来处理。

protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        //到物流中心完成postcard的信息填充,因为最初生成的postcard对象只包含path信息,不包含其他有效信息,比如路由类型,携带的参数等
        LogisticsCenter.completion(postcard);
        //如果不是绿色通道,则通过拦截控制器依次调用不同的拦截器处理信息(类似与一个包裹在检查通道了经过多个扫描检查)
        if (!postcard.isGreenChannel()) {  
            //每个拦截器的拦截方法调用都是在子线程中执行的
            interceptorService.doInterceptions(postcard, new InterceptorCallback() {
                @Override
                public void onContinue(Postcard postcard) {
                    _navigation(context, postcard, requestCode, callback);
                }
                @Override
                public void onInterrupt(Throwable exception) {
                    if (null != callback) {
                        //只要有一个拦截器拦截该包裹,则回调onInterrupt方法宣告本次路由被终止
                        callback.onInterrupt(postcard);
                    }
                }
            });
        } else {
            //绿色通道则直接调用_navigation方法进行具体的导航
            return _navigation(context, postcard, requestCode, callback);
        }
        return null;
    }

可以看出navigation方法主要做了如下事情:

  1. 根据只包含path(理解成只有快递单号的快递信息表)的postcard去物流中心查找具体的路由信息(由编译时生成,在init时注入),完成后续步骤需要的数据填充。
  2. 如果不是绿色通道,则将postcard交予拦截控制器,委托各个拦截器在子线程执行检查是否拦截。
  3. 如果未拦截,则执行具体的导航功能
    这里先看下LogisticsCenter是怎么去填充信息到postcard中:
public synchronized static void completion(Postcard postcard) {
        //去数据仓库获取路由信息,该信息在初始化ARouter时已经注入
        RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
        if (null == routeMeta) { 
           //如果没有路由信息,则尝试去数据仓库查找
        } else {
            //找到路由信息后,则将配置的路由信息填充到Postcard对象中
            postcard.setDestination(routeMeta.getDestination());
            postcard.setType(routeMeta.getType());
            postcard.setPriority(routeMeta.getPriority());
            postcard.setExtra(routeMeta.getExtra());

            Uri rawUri = postcard.getUri();
            if (null != rawUri) {   // Try to set params into bundle.
                //这里主要是完成参数的填充
            }
            //针对不同的路由类型进行处理
            switch (routeMeta.getType()) {
                case PROVIDER:  
                    //如果是服务提供者,则尝试获取其具体实例,如果没有,则根据路由信息构造一个实例,初始化并存储到数据仓库,
                    Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
                    IProvider instance = Warehouse.providers.get(providerMeta);
                    if (null == instance) { // There's no instance of this provider
                        IProvider provider;
                        provider = providerMeta.getConstructor().newInstance();
                            provider.init(mContext);
                            Warehouse.providers.put(providerMeta, provider);
                            instance = provider;
                    }
                    postcard.setProvider(instance);
                    //服务提供者被设置成绿色渠道,不用接受拦截检查
                    postcard.greenChannel();   
                    break;
                case FRAGMENT:
                   //fragment也不用拦截检查
                    postcard.greenChannel();  
                default:
                    break;
            }
        }
    }

信息填充完之后,看一下具体的路由实现:

private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
        final Context currentContext = null == context ? mContext : context;

        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 (-1 != flags) {
                    intent.setFlags(flags);
                } else if (!(currentContext instanceof Activity)) {    // Non activity, need less one flag.
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                }

                // Navigation in main looper.
                new Handler(Looper.getMainLooper()).post(new Runnable() {
                    @Override
                    public void run() {
                        if (requestCode > 0) {  // Need start for result
                            ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
                        } else {
                            ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
                        }

                        if ((0 != postcard.getEnterAnim() || 0 != postcard.getExitAnim()) && currentContext instanceof Activity) {    // Old version.
                            ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
                        }

                        if (null != callback) { // Navigation over.
                            callback.onArrival(postcard);
                        }
                    }
                });

                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类型,则将数据填充到intent中之后,调用ActivityCompat的startActivity或者startActivityForResult方法启动activity。
  • 如果是PROVIDER类型,则直接返回其服务提供者
  • 如果是BOARDCAST || CONTENT_PROVIDER || FRAGMENT,则创建其需要的实体,并填充数据,再返回该实体对象
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容

  • ARouter探究(一) 前言 ARouter 是 Alibaba 开源的一款 Android 页面路由框架,特别...
    Jason骑蜗牛看世界阅读 1,329评论 1 3
  • 前言 随着项目业务逻辑和功能点日益递增, 逻辑的耦合程度也逐渐升高, 组件化技术可以很好的解决这个问题, 公司大佬...
    SharryChoo阅读 1,096评论 0 9
  • Arouter框架适合项目比较大,模块多的时候,可以实现解耦,不需要知道跳转的是哪个activity,只需要知道配...
    破晓11阅读 3,461评论 0 2
  • 不知道有没有人会和我一样,关注某一件事情,思想总被文章的作者给左右到,一会儿觉得这个写的非常有道理,说到心眼里去了...
    二求人生阅读 271评论 0 0
  • 汽车穿驰在沙棘丛生的小道上,随之溅起的石子恶狠狠的敲打着车身。我一阵阵心痛压抑在内心深处,只能云淡风轻的听...
    清泉石下阅读 385评论 0 1