美文网首页
ARouter基本使用及原理分析

ARouter基本使用及原理分析

作者: 愿天深海 | 来源:发表于2023-08-14 15:45 被阅读0次

    ARouter简介

    ARouter是阿里开源的一款帮助Android App进行组件化改造的路由框架,是Android平台中对页面和服务提供路由功能的中间件,可以实现在不同模块的Activity之间跳转。
    ARouter的特点是灵活性强还能帮助项目解耦。
    除了广为人知的Activity跳转之外,ARouter还支持获取Fragment,解耦服务使得跨模块API调用等等,具体见官方文档:
    https://github.com/alibaba/ARouter/blob/master/README_CN.md

    ARouter原理概述

    ARouter使用@Route注解,在编译时期通过APT技术生成类文件用于存储path和activityClass的映射关系。
    在app进程启动的时候会拿到这些类文件,把里面存储的映射关系数据读到内存里,保存在路由表map中。
    在进行路由跳转时,通过ARouter的build()方法传入要到达页面的路由地址,ARouter在路由表中找到路由地址对应的activityClass,然后new Intent(),通过ARouter的withString()方法传入携带参数,内部调用intent.putExtra(),通过ARouter的navigation()跳转,内部调用startActivity(intent)。
    这样就实现了在不同模块之间,不相互依赖,却顺利启动对方的Activity。

    ARouter基本用法

    添加依赖与配置

    //注解处理插件
    plugins {
        ...
        id 'kotlin-kapt'
    }
    
    //注解处理选项
    kapt {
        arguments {
            arg("AROUTER_MODULE_NAME", project.getName())
        }
    }
    
    //依赖
    dependencies {
        // 替换成最新版本, 需要注意的是api要与compiler匹配使用,均使用最新版可以保证兼容
        implementation 'com.alibaba:arouter-api:x.x.x'
        kapt 'com.alibaba:arouter-compiler:x.x.x'
        ...
    }
    

    添加注解

    // 在支持路由的页面上添加注解(必选)
    // 这里的路径需要注意的是至少需要有两级,/xx/xx
    @Route(path = "/test/activity")
    public class YourActivity extend Activity {
        ...
    }
    

    初始化SDK

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

    发起路由操作

    // 1. 应用内简单的跳转(通过URL跳转在'进阶用法'中)
    ARouter.getInstance().build("/test/activity").navigation();
    
    // 2. 跳转并携带参数
    ARouter.getInstance().build("/test/1")
                .withLong("key1", 666L)
                .withString("key3", "888")
                .withObject("key4", new Test("Jack", "Rose"))
                .navigation();
    

    更多用法见官方文档:
    https://github.com/alibaba/ARouter/blob/master/README_CN.md

    ARouter架构概览

    ARouter项目代码结构

    ARouter项目代码结构如上,红框内为最核心的4部分,架构关系如下,

    ARouter架构

    ARouter项目中包含了API、编译器Compiler、插件Gradle Plugin和注解Annotation4 个模块。

    API

    ARouter API

    API 模块由launcher、core、exception、thread、facede、utils和base子模块组成。

    • launcher:包含了启动器 ARouter。
    • core:包含物流中心 LogsticsCenter 和仓库 Warehouse 等类。
    • exception:包含了一些异常类。
    • thread:包含了CancellableCountDownLatch,ARouter 的拦截器链是放在子线程中执行的,就用到了它。
    • facede:包含了导航回调 NavigationCallback 和 拦截器IInterceptor 等接口。
    • utils:包含了 ARouter 自定义的日志打印器等工具类。
    • base:只有一个用于保存拦截器的 UnitqueKeyTreeMap。

    Compiler

    ARouter Compiler

    Compiler 模块用于生成路由表,@Autowired、@Interceptor 和 @Route 注解对应的注解处理器分别是 AutowiredProcessor、InterceptorProcessor 以及 RouteProcessor ,都在 Compiler 中。

    Register Plugin

    ARouter Register Plugin

    Register Plugin 模块包含了注册代码生成器 RegisterCodeGenerator 和 RegisterTransform,如果使用了ARouter的路由表加载插件,那这个路由表就会由Register插件加载。

    Annotaion

    ARouter Annotaion

    Annotaion模块比较简单,只包含了一些注解和枚举类。

    APT原理

    ARouter的使用非常方便,得益于APT技术。APT的作用是在编译阶段扫描并处理代码中的注解,然后根据注解输出Java文件。
    ARouter为了方便实现注解处理器还额外用了两个库。

    • JavaPoet,提供了调用对象方法的方式生成需要的代码,而不再需要人为地用StringBuilder去拼接代码,再使用IO写入文件。
    • Auto-Service,提供了简便的方式去注册APT,避免了原本繁琐的注册步骤。

    @Route

    @Route的定义:

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.CLASS)
    public @interface Route {
    
        /**
         * Path of route
         */
        String path();
        ......
    }
    
    • @Target({ElementType.TYPE}):表示这个注解是修饰类的
    • @Retention(RetentionPolicy.CLASS):表示需要保留到编译时

    使用该注解时有一个主要的参数path:

    // 在支持路由的页面上添加注解(必选)
    // 这里的路径需要注意的是至少需要有两级,/xx/xx
    @Route(path = "/test/activity")
    public class YourActivity extend Activity {
        ...
    }
    

    这样编译时能获取到@Route所注解的类,并且能获取到path路径。

    RouteProcessor

    RouteProcessor是@Route注解对应的注解处理器。

    @AutoService(Processor.class)
    @SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE, ANNOTATION_TYPE_AUTOWIRED})
    public class RouteProcessor extends BaseProcessor 
    
    • Auto-Service这个库为Processor完成了自动注册.
    • @SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE, ANNOTATION_TYPE_AUTOWIRED}):表明了当前Processor是处理哪些注释的。

    RouteProcessor继承于BaseProcessor,在init方法中获取到了每个模块的moduleName。

            // Attempt to get user configuration [moduleName]
            Map<String, String> options = processingEnv.getOptions();
            if (MapUtils.isNotEmpty(options)) {
                moduleName = options.get(KEY_MODULE_NAME);
                generateDoc = VALUE_ENABLE.equals(options.get(KEY_GENERATE_DOC_NAME));
            }
    

    RouteProcessor的process方法是对注解处理的地方,它直接获取了所有使用@Route注解的元素。

    Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);
    

    拿到使用注解的元素后就会进入this.parseRoutes(routeElements)方法。这个方法使用JavaPoet生成Java文件。如果不用这个库也可以使用StringBuilder去写Java文件的内容。

    IRouteGroup

    先来看一下RouteProcessor生成的产物,在下图路径下可以看到ARouter的生成产物


    image.png
    public class ARouter$$Group$$test implements IRouteGroup {
      @Override
      public void loadInto(Map<String, RouteMeta> atlas) {
        atlas.put("/test/activity", RouteMeta.build(RouteType.ACTIVITY, YourActivity.class, "/test/activity", "test", null, -1, -2147483648));
      }
    }
    

    RouteMeta是包含了@Route所注解的元素的必要信息,最明显的就是YourActivity.class,有了它,我们就可以通过Intent跳转到这个Activity了。

    ARouter$$Group$$test这个类继承自IRouteGroup ,实现了接口中的loadInto方法。

    loadInto方法逻辑很简单,传入一个map,将注解的path值作为key,将元素(RouteMeta)作为value作为Value放入map。如果完成了这个方法,就完成了Activity的注册。

    IRouteRoot

    public class ARouter$$Root$$app implements IRouteRoot {
      @Override
      public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
        routes.put("test", ARouter$$Group$$test.class);
      }
    }
    

    ARouter$$Root$$app实现了IRouteRoot接口,内容非常相似。通过loadInto方法,往Map中插入以group名为Key,IRouteGroup实现类为Value的内容。

    group默认就是path中第一个斜杠之后的内容(@Route(path="/group/xxx"))

    如果调用了这个方法,那么可以通过group拿到IRouteGroup实现类的class,有了class实例化之后就能通过前面所说的拿到Activity.class了。

    整体的结构如下图所示:


    image.png

    RouteProcessor.process()

    回过头来继续看RouteProcessor的process方法

        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            if (CollectionUtils.isNotEmpty(annotations)) {
                Set<? extends Element> routeElements = roundEnv.getElementsAnnotatedWith(Route.class);
                try {
                    logger.info(">>> Found routes, start... <<<");
                    this.parseRoutes(routeElements);
    
                } catch (Exception e) {
                    logger.error(e);
                }
                return true;
            }
    
            return false;
        }
    

    获取了所有使用@Route注解的元素,将它们放进了parseRoutes方法用于生成IRouteGroup和IRouteRoot。这里面使用JavaPoet提供的类,通过方法调用的形式生成代码。

    路由表的生成

    RouteProcessor的process方法对于声明了 @Route 注解的类的处理,大概分为4个步骤:
    1、获取路由元素
    2、创建路由元信息
    3、把路由元信息进行分组
    4、生成路由文件

    在上面的分析中,通过roundEnv.getElementsAnnotatedWith(),已经获取了所有使用@Route注解的元素,然后将它们放进了parseRoutes方法。

    这里说的路由元信息指的是 RouteMeta,RouteProcessor 会把声明了 @Route 注解的的 Activity、Provider、Service 或 Fragment 和一个 RouteMeta 关联起来。

    for (Element element : routeElements) {
        ......
        // Activity or Fragment
        if (types.isSubtype(tm, type_Activity) || types.isSubtype(tm, fragmentTm) || types.isSubtype(tm, fragmentTmV4)) {
            // Get all fields annotation by @Autowired
            Map<String, Integer> paramsType = new HashMap<>();
            Map<String, Autowired> injectConfig = new HashMap<>();
            injectParamCollector(element, paramsType, injectConfig);
    
            if (types.isSubtype(tm, type_Activity)) {
                // Activity
                logger.info(">>> Found activity route: " + tm.toString() + " <<<");
                routeMeta = new RouteMeta(route, element, RouteType.ACTIVITY, paramsType);
            } else {
                // Fragment
                logger.info(">>> Found fragment route: " + tm.toString() + " <<<");
                routeMeta = new RouteMeta(route, element, RouteType.parse(FRAGMENT), paramsType);
            }
    
            routeMeta.setInjectConfig(injectConfig);
        } 
        ......
        categories(routeMeta);
    }
    

    在这段代码中,完成了RouteMeta类的构建,还获取了Activity通过@AutoWired注解的接收参数。然后通过categories(routeMeta)方法,对所有RouteMeta进行分组。

    为什么要分组呢?随着项目的迭代,组件数量会越来越多,将这么多的组件信息都放到一个map里,显然会对内存造成很大的问题,且加载耗时也会随之增加。Arouter采用的方法就是“分组+按需加载”,同时,分组也利于管理。

        private void categories(RouteMeta routeMete) {
            if (routeVerify(routeMete)) {
                logger.info(">>> Start categories, group = " + routeMete.getGroup() + ", path = " + routeMete.getPath() + " <<<");
                Set<RouteMeta> routeMetas = groupMap.get(routeMete.getGroup());
                if (CollectionUtils.isEmpty(routeMetas)) {
                    ......
                    routeMetaSet.add(routeMete);
                    groupMap.put(routeMete.getGroup(), routeMetaSet);
                } else {
                    routeMetas.add(routeMete);
                }
            }
            ......
        }
    

    在 RouteProcessor 中有一个 groupMap,在 RouteMeta 创建好后,RouteProcessor 会根据其group作为Key进行分组,放入到 groupMap 中。RouteMeta本身会放入一个Set,Set中所有RouteMeta的group都是相同的,作为Map的Value。

    当 RouteProcessor 把 RouteMeta 分组好后,就会用 JavaPoet 生成 Group、Provider 和 Root 路由文件,路由表就是由这些文件组成的,JavaPoet 是 Square 开源的代码生成框架。

                // 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);
    

    生成的路由文件就是前面所看到RouteProcessor的产物,就是下面这些:


    image.png

    路由表的加载

    加载路由表,其实就是加载RouteProcessor所生成的类文件。

    在调用ARouter的init()初始化方法时,ARouter会调用LogisticsCenter的init()方法,在该方法中,会loadRouterMap()优先通过插件加载路由表,然后判断当前路由表加载方式是否为插件,不是的话则从Dex中加载路由表。

        /**
         * LogisticsCenter init, load all metas in memory. Demand initialization
         */
        public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
            ......
            try {
                long startInit = System.currentTimeMillis();
                //load by plugin first
                loadRouterMap();
                if (registerByPlugin) {
                    //通过插件加载路由表
                    logger.info(TAG, "Load router map by arouter-auto-register plugin.");
                } else {
                    //从Dex中加载路由表
                    ......
                }
                ......
            } catch (Exception e) {
                throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
            }
        }
    

    从Dex中加载路由表

    从Dex中加载路由表

    通过Dex加载路由表的流程大致如上图,接下去来一点一点看一下LogisticsCenter的init()方法中从Dex中加载路由表部分:

                    // It will rebuild router map every times when debuggable.
                    if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
                        logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
                        // These class was generated by 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);    // Save new version name when router map update finishes.
                    } 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>()));
                    }
    

    如果是设置了debug模式或者是新版本的情况下,扫描所有Dex文件查找所有com.alibaba.android.arouter.routes开头的文件,然后更新到SharePreferences。否则直接从SharePreferences读缓存,减少解析时间。

    这里使用了ClassUtils读取Dex文件,并从Dex文件中读取路由表。

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

    把路由表保存到 SharedPreferences 后,就会根据类名的后缀判断类是 IRouteRoot 、IInterceptorGroup 还是 IProviderGroup ,然后实例化成不同的对象并调用loadInto方法把类文件的内容加载到索引中。

    通过插件加载路由表

    如果想缩短ARouter初始化的时间,可以用ARouter的Gradle插件,这个插件能自动加载路由表,这样ARouter初始化的时候就不需要读取类的信息,从而缩短初始化时间。


    使用 Gradle 插件实现路由表的自动加载

    插件的工作原理就是在代码编译的时候,插件会找到LogisticsCenter类的loadRouterMap方法,然后在方法中插入路由相关的代码,这样初始化的时候就不会从dex文件中扫描路由表了。

    路由表的跳转

    使用ARouter.getInstance().build("/test/activity").navigation()发起路由操作,进行跳转,会调用_ARouter的navigation()方法。
    _ARouter的navigation()方法有两种重载。一种是用于创建服务service,一种是用于路由跳转。

        protected <T> T navigation(Class<? extends T> service) {
            try {
                Postcard postcard = LogisticsCenter.buildProvider(service.getName());
    
                // Compatible 1.0.5 compiler sdk.
                // Earlier versions did not use the fully qualified name to get the service
                if (null == postcard) {
                    // No service, or this service in old version.
                    postcard = LogisticsCenter.buildProvider(service.getSimpleName());
                }
    
                if (null == postcard) {
                    return null;
                }
    
                // Set application to postcard.
                postcard.setContext(mContext);
    
                LogisticsCenter.completion(postcard);
                return (T) postcard.getProvider();
            } catch (NoRouteFoundException ex) {
                logger.warning(Consts.TAG, ex.getMessage());
                return null;
            }
        }
    

    创建服务service流程相对比较简单,通过LogisticsCenter.buildProvider创建一张Postcard明信片,包含最基本的group和path信息,然后给Postcard设置Context,最后通过LogisticsCenter.completion获取路由元信息并填充至Postcard中。

        protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
            //预处理服务
            PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);
            if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {
                // Pretreatment failed, navigation canceled.
                return null;
            }
    
            // Set context to postcard.
            postcard.setContext(null == context ? mContext : context);
    
            try {
                //完善Postcard
                LogisticsCenter.completion(postcard);
            } catch (NoRouteFoundException ex) {
              //完善失败降级策略
              ......
            }
            ......
            if (!postcard.isGreenChannel()) {   // 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(postcard, requestCode, callback);
                    }
    
                    /**
                     * Interrupt process, pipeline will be destory when this method called.
                     *
                     * @param exception Reson of interrupt.
                     */
                    @Override
                    public void onInterrupt(Throwable exception) {
                        if (null != callback) {
                            callback.onInterrupt(postcard);
                        }
    
                        logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
                    }
                });
            } else {
                //按类型跳转
                return _navigation(postcard, requestCode, callback);
            }
    
            return null;
        }
    

    用于路由跳转流程相对复杂一点,大致有这么几步:1、预处理服务,2、完善Postcard,3、完善失败降级策略,4、拦截器链路,5、按类型跳转。

    预处理服务

    预处理服务可以让我们在ARouter进行跳转前,根据PostCard的内容判断是否要独立地对这次跳转进行处理,是的话则在onPretreatment()中返回false即可。

    实现预处理服务只需要实现PretreatmentService接口,并加上一个path内容任意的@Route注解即可。

    如果实现了预处理服务,并且onPretreatment()中返回false,则中断跳转流程,不继续往下处理。

    完善Postcard

    调用完预处理服务后,_ARouter就会用LogisticsCenter来加载路由表,路由表也就是RouteProcessor生成的路由文件。

    在_ARouter初始化时,会把LogisticsCenter也进行初始化,而LogisticsCenter的初始化方法中,会读取RouteProcessor创建好的路由表,然后放到对应的索引 index中。

    有了索引,当_ARouter调用LogisticsCenter的completion()方法时,就可以用索引从Warehouse的routes 中获取路由元信息。

    public synchronized static void completion(Postcard postcard) {
            ......
            RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
            if (null == routeMeta) {
                ......
            } 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 auto inject.
                        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 implement 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) {
                                logger.error(TAG, "Init provider failed!", e);
                                throw new HandlerException("Init provider failed!");
                            }
                        }
                        postcard.setProvider(instance);
                        postcard.greenChannel();    // Provider should skip all of interceptors
                        break;
                    case FRAGMENT:
                        postcard.greenChannel();    // Fragment needn't interceptors
                    default:
                        break;
                }
            }
        }
    

    首先如果LogisticsCenter根据索引查找不到对应的RouteMeta,那就说明routes还没有被填充,此时LogisticsCenter就会获取group的RouteMeta,然后把group下的路径填充到routes中,然后再调用一次completion() ,这时就可以取填充Postcard的信息了。

    有了routeMeta之后,从routeMeta中取值,设置到postcard属性中,解析Uri中的参数,设置到postcard属性中,用于后续跳转时设置到Bundle里。

    填充完了 Postcard 的信息后,LogisticsCenter 会根据 Postcard 的类型来做不同的操作,并且当类型是PROVIDER和FRAGMENT的的时候,设置postcard的greenChannel,其实就是说Provider和Fragment是会跳过拦截器链路的。

    完善失败降级策略

    当ARouter在完善Postcard信息的过程中遇到了异常,如果在navigation时有传入NavigationCallback,则针对此次跳转失败,回调onLost,如果没有callback,将使用全局的降级策略:

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

    同预处理服务类似,实现降级策略只需要实现DegradeService接口,并加上一个path内容任意的@Route注解即可。

    拦截器链路

    当不是greenChannel时,将进入拦截器链路,否则直接进入按类型跳转。在上面完善Postcard时,Provider和Fragment设置了greenChannel,是会跳过拦截器链路的。当然也可以在navigation()方法前调用greenChannel()方法,让该次跳转跳过拦截器链路。

    拦截器可以用来在跳转过程中处理事件,比如做登陆检查,拦截器会在跳转之间执行,多个拦截器会按优先级顺序依次执行。

    实现拦截器只需要实现IInterceptor接口,使用注解@Interceptor(priority = 8, name = "xxxx"),priority为该拦截器优先级,多个拦截器会按优先级顺序依次执行。

    在process方法中需要调用onContinue()或onInterrupt()方法,至少需要调用其中一种方法,否则不会继续路由流程。onContinue()表示处理完成,交换控制权给 ARouter,onInterrupt()表示出现异常需要中断路由流程。

    按类型跳转

    当处理完拦截器后,navigation()中就会调用_navigation()方法,这也是具体进行跳转的方法。在这个方法中,会根据Postcard的路由类型RouteType来判断,按类型进行跳转。

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

    当RouteType为Activity时,启动Activity的流程和平时启动Activity的流程是一样的,创建Intent、传入destination、传入参数extras、设置flags、设置action,最终在主线程中调用startActivity()启动目标页面。

    当RouteType为Provider时,也就是自定义服务的话,返回Postcard中Provider,ARouter中的自定义服务是通过依赖注入来查找发现服务。

    当RouteType为Fragment、Broadcast或ContentProvider时,会通过destination用反射创建实例,如果是Fragment,ARouter还会为它设置参数,然后返回实例。

    参考文章:
    https://juejin.cn/post/6885932290615509000

    相关文章

      网友评论

          本文标题:ARouter基本使用及原理分析

          本文链接:https://www.haomeiwen.com/subject/vdysudtx.html