ARouter 源码历险记 (三)

目录

ARouter 源码历险记 (一)

ARouter 源码历险记 (二)

ARouter 源码历险记 (三)

ARouter 源码历险记 (四)

ARouter 源码历险记 (五)

 

 

前言

    在分析真正的运行时源代码之前,我要知道接下去的源码分析思路以及代码介绍的流程都是出于笔者自己的思考习惯,没有按照严格的流程图一步步介绍。

ARouter.init

    先不画流程图,也不做具体分析,这里我们先直接跟着代码走,发现在流程中调用了LogisticsCenter的init方法,我们先来分析该方法。

LogisticsCenter.init

    首先介绍一个类,Warehouse,该类的主要作用就是放在内存中用来存储路由信息的类。接下去马上会用到其中的三个字段

static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();

    用来存储ARouter$$Root$$XXX 文件中所有的信息

static Map<String, RouteMeta> providersIndex = new HashMap<>();

    用来存储ARouter$$Providers$$XXX 文件中所有的信息

static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]");

    用来存储ARouter$$Interceptors$$XXX 文件中所有的信息

public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
        mContext = context;
        executor = tpe;

        try {
            // These class was generate by arouter-compiler.
            List<String> classFileNames = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);

            //
            for (String className : classFileNames) {
                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);
                }
            }

            if (Warehouse.groupsIndex.size() == 0) {
                logger.error(TAG, "No mapping files were found, check your configuration please!");
            }

            if (ARouter.debuggable()) {
                logger.debug(TAG, String.format(Locale.getDefault(), "LogisticsCenter has already been loaded, GroupIndex[%d], InterceptorIndex[%d], ProviderIndex[%d]", Warehouse.groupsIndex.size(), Warehouse.interceptorsIndex.size(), Warehouse.providersIndex.size()));
            }
        } catch (Exception e) {
            throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
        }
    }

    首先通过ClassUtils.getFileNameByPackageName方法获取com.alibaba.android.arouter.routes包中所有的类名。

    在ARouter介绍中其中一个特点的表示就是支持instant run,大多就是在这个方法里提现的。其中包含了很多instant run的内容以及android对于apk加载的知识,内容自称章节,所以在这里我们也不详细追究。索性的是,即便不追究,但是工具类方法一般都较为固定,并不妨碍源码阅读。

    PS: 如果使用了Instant run模式,那么必须调用ARouter.openDebug() 方法开启debug模式。但是在线上环境中一定要关闭openDebug选项。

    遍历所有获得的类,如果是ARouter$$Root$$XXX 那么调用该类的loadinto方法将数据存入Warehouse中。

    注意这里我们并不会去加载ARouter$$Group$$XXX文件及其中内容,这样的好处是我们不需要再初始化时就加载所有被@Route注解的类,防止因为使用arouter而导致应用加载变慢的情况。

ARouter.getInstance().navigation

    特地打断init流程的分析,主要还是想先分析navigation和build的流程,因为这是整个Router的路由核心,甚至在init的流程中也会多次用到navigation和build。

    然后build方法中同样包含了navigation调用,所以本着追根溯源的想法,还是先来分析navigation方法。

    navigation有两个重载的方法:


    public <T> T navigation(Class<? extends T> service) {
        return _ARouter.getInstance().navigation(service);
    }

    public Object navigation(Context mContext, Postcard postcard, int requestCode, NavigationCallback callback) {
        return _ARouter.getInstance().navigation(mContext, postcard, requestCode, callback);
    }

    第二个方法我们放在更后面去分析。

    首先navigation只是简单调用了_ARouter的方法,仔细看_ARouter类,我们发现它并不是public类。类似于代理模式,ARouter其实有点像_ARouter的代理类。这样设计得一个好处在ARouter的博文中也有提及——稳定性。因为开发者接触的是ARouter类,我们可以修改_ARouter的方法名等,但是只要对外暴露的功能不变,通过修改ARouter的代码就能够实现开发者不需要修改已接入代码的优点。

    来看_ARouter的navigation代码:

protected <T> T navigation(Class<? extends T> service) {
        try {
            Postcard postcard = LogisticsCenter.buildProvider(service.getSimpleName());
            LogisticsCenter.completion(postcard);
            return (T) postcard.getProvider();
        } catch (NoRouteFoundException ex) {
            logger.warning(Consts.TAG, ex.getMessage());
            return null;
        }
    }

看第一部buildProvider的代码:

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());
        }
    }

内容很简单,从之前存储的providersIndex中获取输入的类名的RouteMeta对象。

    仔细思考,发现一个问题,这个navigation方法传入的参数仅能是 IProvider 子接口并且该接口是某个被@Route注解的类的直接实现接口,否则如果从providersIndex中将会一无所获。关于providersIndex中存储到底是什么内容,有遗忘的可以参看ARouter 源码历险记 (一)中的分析。

    PS : 根据代码分析,IProvider的继承存在“最佳实践”  ,如果需要实现一个Service那么应该使用如下的继承线 IProvider->XXXService->XXXServiceImpl。

    回到代码,我们在buildProvider中生成了一个Postcard,但是其中只有两个字段被填入了值。于是,我们调用LogisticsCenter.completion方法来进一步处理。

public synchronized static void completion(Postcard postcard) {
        if (null == postcard) {
            throw new NoRouteFoundException(TAG + "No postcard!");
        }

        RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
        if (null == routeMeta) {    // Maybe its does't exist, or didn't load.
            Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());  // Load route meta.
            if (null == groupMeta) {
                throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
            } else {
                // Load route and cache it into memory, then delete from metas.
                try {
                    if (ARouter.debuggable()) {
                        logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] starts loading, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
                    }

                    IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
                    iGroupInstance.loadInto(Warehouse.routes);
                    Warehouse.groupsIndex.remove(postcard.getGroup());

                    if (ARouter.debuggable()) {
                        logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] has already been loaded, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
                    }
                } catch (Exception e) {
                    throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
                }

                completion(postcard);   // Reload
            }
        } else {
            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.
                Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri);
                Map<String, Integer> paramsType = routeMeta.getParamsType();

                if (MapUtils.isNotEmpty(paramsType)) {
                    // Set value by its type, just for params which annotation by @Param
                    for (Map.Entry<String, Integer> params : paramsType.entrySet()) {
                        setValue(postcard,
                                params.getValue(),
                                params.getKey(),
                                resultMap.get(params.getKey()));
                    }

                    // Save params name which need autoinject.
                    postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));
                }

                // Save raw uri
                postcard.withString(ARouter.RAW_URI, rawUri.toString());
            }

            switch (routeMeta.getType()) {
                case PROVIDER:  // if the route is provider, should find its instance
                    // Its provider, so it must be implememt IProvider
                    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;
                        try {
                            provider = providerMeta.getConstructor().newInstance();
                            provider.init(mContext);
                            Warehouse.providers.put(providerMeta, provider);
                            instance = provider;
                        } catch (Exception e) {
                            throw new HandlerException("Init provider failed! " + e.getMessage());
                        }
                    }
                    postcard.setProvider(instance);
                    postcard.greenChannel();    // Provider should skip all of interceptors
                    break;
                default:
                    break;
            }
        }
    }

    尝试用流程图来分析代码(画图好累?!)

    需要知道的内容如下:

    RouteMate中 param在什么情况下不为空?答 RouteMate的类型是Activity时!

    URI什么时候不为空?使用uri进行页面跳转的时候(这不是废话吗?!)

    greenChannel 表示忽略所有interceptor!

 

    回到navigation方法,之后直接返回了postcart.provider对象,间接表明provider不为空,即RouteMate是Provider类型。

ARouter.getInstance().build

先来看build的三个重载方法:

/**
     * Build postcard by path and default group
     */
    protected Postcard build(String path) {
        if (StringUtils.isEmpty(path)) {
            throw new HandlerException(Consts.TAG + "Parameter is invalid!");
        } else {
            PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
            if (null != pService) {
                path = pService.forString(path);
            }
            return build(path, extractGroup(path));
        }
    }

    /**
     * Build postcard by uri
     */
    protected Postcard build(Uri uri) {
        if (null == uri || StringUtils.isEmpty(uri.toString())) {
            throw new HandlerException(Consts.TAG + "Parameter invalid!");
        } else {
            PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
            if (null != pService) {
                uri = pService.forUri(uri);
            }
            return new Postcard(uri.getPath(), extractGroup(uri.getPath()), uri, null);
        }
    }

    /**
     * Build postcard by path and group
     */
    protected Postcard build(String path, String group) {
        if (StringUtils.isEmpty(path) || StringUtils.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);
        }
    }

    其中第一个和第三个都是使用path进行构建Postcard,区别在于是否传入了group参数,如果没传入,使用默认的group参数,还记得RouteProcessor中的默认group么?就是path的第一个单词。

    第二个是使用uri进行跳转,使用的也是默认的group。那么问题来了,如果想要一个页面能够被通过uri进行启动,那么它的group必须和默认分组相同!细细体会吧少年!

    另外再所有的build方法中都包含了一段相同的代码,获取了PathReplaceService,该ServiceReplaceService到底什么用呢?

public interface PathReplaceService extends IProvider {

    /**
     * For normal path.
     *
     * @param path raw path
     */
    String forString(String path);

    /**
     * For uri type.
     *
     * @param uri raw uri
     */
    Uri forUri(Uri uri);
}

    其实它就是一个实现了IProvider的接口,用ARoute官方的话来说,它就是一个Service(不是android四大组件,而是对外提供某个服务的服务器),该service的作用就是对输入的path和uri进行转化,在build中使用就是相当于一个包装器,在最初传入的uri 和path上进行包装后再跳转。

Postcard.navigation

    我们在如上使用build生成一个Postcard之后,会调用Postcard的navigation进行跳转,查看源代码,发现navigation有多达5个的方法重载,仔细一看,发现只是参数处理问题,最后统一都会调用到ARouter的第二个navigation方法,也就是_ARouter的navigation(Context context,Postcard postcard,int requestCode,NavigationCallback callback)方法 

protected Object navigation(final Context context, final Postcard postcard, final int requestCode, NavigationCallback callback) {
        try {
            LogisticsCenter.completion(postcard);
        } catch (NoRouteFoundException ex) {
            logger.warning(Consts.TAG, ex.getMessage());

            if (debuggable()) { // Show friendly tips for user.
                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 {    // No callback for this invoke, then we use the global degrade service.
                DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
                if (null != degradeService) {
                    degradeService.onLost(context, postcard);
                }
            }

            return null;
        }

        if (null != callback) {
            callback.onFound(postcard);
        }

        .....
    }

     调用completion,该方法之前分析过了,就是构造postcard中的内容,如果path对应的地址没有找到相应的Route进行路由的话,就会报NoRouteFoundException。关键在错误处理部分。

    如果手动传入了NavigationCallback 回调,就会调用它的onLost通知调用者调用丢失,等待调用者自己处理。如果没有手动设置该回调,那么就会去获取是否存在DegradeService服务(降级处理),如果开发者实现了DegradeService接口并且注册了@Route,那么就会自动调用它的onLost方法(宣传中的自动降级处理,look就是这么些代码,关键是思想!)。

    如果callback不为空,调用onFound的回调,这个回调厉害了,给开发者手动处理postcard的机会,想法是很好的,就怕开发者乱操作,导致库报错,然后大喊垃圾代码…………

    接下去的代码就是和interceptor相关了:

if (!postcard.isGreenChannal()) {   // It must be run in async thread, maybe interceptor cost too mush time made ANR.
            interceptorService.doInterceptions(postcard, new InterceptorCallback() {
                /**
                 * Continue process
                 *
                 * @param postcard route meta
                 */
                @Override
                public void onContinue(Postcard postcard) {
                    _navigation(context, postcard, requestCode);
                }

                /**
                 * Interrupt process, pipeline will be destory when this method called.
                 *
                 * @param exception Reson of interrupt.
                 */
                @Override
                public void onInterrupt(Throwable exception) {
                    logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
                }
            });
        } else {
            return _navigation(context, postcard, requestCode);
        }

    看代码,interceptorService到底是什么?什么时候初始化的?发现时在init之后被初始化的!那么问题来了,我们在init得时候就会调用到navigation方法,这样不会造成空指针异常吗?仔细思考后发现,我们在init阶段使用navigation实例化的全部都是provider类型的理由,这些路由的greenChannal会被设置为true,所以——老铁,没毛病……

    interceptorService的实例在api包中,文件名为InterceptorServiceImpl。先不分析interceptor的具体运行流程,先看callback。

    一个方法是继续运行,一个方法是打断后的回调。继续运行就是进一步调用_navigation的方法,接着看:

    

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

        switch (postcard.getType()) {
            case ACTIVITY:

                // Build intent
                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);
                }

                // Judgment activity start type.
                if (requestCode > 0) {  // RequestCode exist, need startActivityForResult, so this context must son of activity.
                    ((Activity) currentContext).startActivityForResult(intent, requestCode);
                } else {
                    currentContext.startActivity(intent);
                }

                break;
            case PROVIDER:
                return postcard.getProvider();
            case BOARDCAST:
            case CONTENT_PROVIDER:
            case FRAGMENT:
            case METHOD:
            case SERVICE:
            default:
                return null;
        }

        return null;
    }

    如果是Activity类型的跳转,构建了Intent启动activity一气呵成……关键还是flag,我们如果需要flag需要在Postcard中手动调用withFlag方法设置flag!

    如果是Provider类型的,直接返回该Provider的实例就行。问题在于Service,明明在Processor中实现了Service(四大组件)的跳转,这里真的什么都不做吗?而且还是返回null……好吧,也就是暂时来说service(四大组件)是跳转不了的喽?

转载于:https://my.oschina.net/zzxzzg/blog/864970

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值