ARouter basic usage and principle analysis

Introduction to ARouter

ARouter is an open source routing framework developed by Alibaba that helps Android Apps undergo component transformation. It is a middleware in the Android platform that provides routing functions for pages and services. It can jump between activities in different modules.
The characteristic of ARouter is that it is highly flexible and can help decouple projects.
In addition to the well-known Activity jump, ARouter also supports obtaining Fragments, decoupling services to enable cross-module API calls, etc. For details, see the official documentation:
https://github.com/alibaba/ARouter/blob/master/README_CN.md

Overview of ARouter principle

ARouter uses the @Route annotation and uses APT technology to generate class files during compilation to store the mapping relationship between path and activityClass.
When the app process starts, it will get these class files, read the mapping relationship data stored in them into the memory, and save them in the routing table map.
When performing a route jump, pass in the routing address of the page to be reached through ARouter's build() method. ARouter finds the activityClass corresponding to the routing address in the routing table, and then new Intent() is passed in through ARouter's withString() method. Carrying parameters, calling intent.putExtra() internally, jumping through ARouter's navigation(), and calling startActivity(intent) internally.
In this way, different modules are not dependent on each other, but can successfully start each other's activities.

Basic usage of ARouter

Add dependencies and configuration

//注解处理插件
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'
    ...
}

Add annotation

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

Initialize SDK

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

Initiate routing operation

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

For more usage, see the official documentation:
https://github.com/alibaba/ARouter/blob/master/README_CN.md

ARouter architecture overview

ARouter project code structure

The ARouter project code structure is as above. The four core parts are in the red box. The architectural relationship is as follows:

ARouter architecture

The ARouter project contains four modules: API, Compiler, Gradle Plugin and Annotation.

API

ARouter API

The API module consists of launcher, core, exception, thread, facede, utils and base sub-modules.

  • launcher: Contains the launcher ARouter.
  • core: Contains classes such as logistics center LogsticsCenter and warehouse Warehouse.
  • exception: Contains some exception classes.
  • Thread: Contains CancellableCountDownLatch. The interceptor chain of ARouter is executed in the child thread, so it is used.
  • facede: Contains navigation callback NavigationCallback and interceptor IInterceptor and other interfaces.
  • utils: Contains ARouter's custom log printer and other tool classes.
  • base: There is only one UnitqueKeyTreeMap used to save the interceptor.

Compiler

ARouter Compiler

The Compiler module is used to generate routing tables. The annotation processors corresponding to the @Autowired, @Interceptor and @Route annotations are AutowiredProcessor, InterceptorProcessor and RouteProcessor respectively, all in the Compiler.

Register Plugin

ARouter Register Plugin

The Register Plugin module includes the registration code generator RegisterCodeGenerator and RegisterTransform. If the ARouter routing table loading plug-in is used, the routing table will be loaded by the Register plug-in.

Annotaion

ARouter Annotaion

The Anntaion module is relatively simple and only contains some annotations and enumeration classes.

APT principle

ARouter is very convenient to use, thanks to APT technology. The function of APT is to scan and process annotations in the code during the compilation phase, and then output Java files based on the annotations.
ARouter also uses two additional libraries to facilitate the implementation of annotation processors.

  • JavaPoet provides a way to call object methods to generate the required code, without the need to manually use StringBuilder to splice the code and then use IO to write the file.
  • Auto-Service provides a simple way to register APT, avoiding the originally cumbersome registration steps.

@Route

Definition of @Route:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {

    /**
     * Path of route
     */
    String path();
    ......
}
  • @Target({ElementType.TYPE}): Indicates that this annotation is a modified class
  • @Retention(RetentionPolicy.CLASS): Indicates that it needs to be retained until compilation time

There is one main parameter path when using this annotation:

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

In this way, the class annotated with @Route can be obtained during compilation, and the path can be obtained.

RouteProcessor

RouteProcessor is the annotation processor corresponding to the @Route annotation.

@AutoService(Processor.class)
@SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE, ANNOTATION_TYPE_AUTOWIRED})
public class RouteProcessor extends BaseProcessor 
  • The Auto-Service library completes automatic registration for Processor.
  • @SupportedAnnotationTypes({ANNOTATION_TYPE_ROUTE, ANNOTATION_TYPE_AUTOWIRED}): Indicates which annotations are processed by the current Processor.

RouteProcessor inherits from BaseProcessor and obtains the moduleName of each module in the init method.

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

The process method of RouteProcessor is where annotations are processed. It directly obtains all elements annotated with @Route.

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

After getting the annotated element, you will enter the this.parseRoutes(routeElements) method. This method uses JavaPoet to generate Java files. If you don't use this library, you can also use StringBuilder to write the content of Java files.

IRouteGroup

Let’s first take a look at the products generated by RouteProcessor. You can see the products generated by ARouter in the path below.
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 contains the necessary information for the elements annotated with @Route. The most obvious one is YourActivity.class. With it, we can jump to this Activity through Intent.

The class ARouter$$Group$$test inherits from IRouteGroup and implements the loadInto method in the interface.

The logic of the loadInto method is very simple. Pass in a map, use the annotated path value as the key, and put the element (RouteMeta) as the value into the map. If this method is completed, the registration of the Activity is completed.

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 implements the IRouteRoot interface and the content is very similar. Through the loadInto method, insert the content with the group name as Key and the IRouteGroup implementation class as Value into the Map.

By default, group is the content after the first slash in path (@Route(path="/group/xxx"))

If this method is called, you can get the class of the IRouteGroup implementation class through group. After the class is instantiated, you can get Activity.class through the above mentioned method.

The overall structure is shown in the figure below:
image.png

RouteProcessor.process()

Go back and continue looking at the process method of RouteProcessor

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

All elements annotated with @Route are obtained and put into the parseRoutes method to generate IRouteGroup and IRouteRoot. This uses the classes provided by JavaPoet to generate code in the form of method calls.

Generation of routing table

The process method of RouteProcessor processes classes declared with the @Route annotation, which is roughly divided into four steps:
1. Obtain routing elements
2. Create routing meta-information
3. Group routing meta-information
4. Generate routing files

In the above analysis, all elements annotated with @Route have been obtained through roundEnv.getElementsAnnotatedWith(), and then put into the parseRoutes method.

The routing meta information mentioned here refers to RouteMeta. RouteProcessor will associate the Activity, Provider, Service or Fragment declared with @Route annotation with a 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);
}

In this code, the construction of the RouteMeta class is completed, and the parameters received by the Activity through the @AutoWired annotation are also obtained. Then group all RouteMeta through the categories(routeMeta) method.

Why group? As the project iterates, the number of components will increase. Putting so much component information into a map will obviously cause a big problem in memory, and the loading time will also increase. The method adopted by Arouter is "grouping + on-demand loading". At the same time, grouping is also convenient for management.

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

There is a groupMap in the RouteProcessor. After the RouteMeta is created, the RouteProcessor will group the groups according to their group as the Key and put them into the groupMap. RouteMeta itself will be put into a Set. The groups of all RouteMeta in the Set are the same and serve as the Value of the Map.

After RouteProcessor groups RouteMeta, it will use JavaPoet to generate Group, Provider and Root routing files. The routing table is composed of these files. JavaPoet is Square's open source code generation framework.

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

The generated routing files are the products of the RouteProcessor seen earlier, which are the following:
image.png

Routing table loading

Loading the routing table is actually loading the class file generated by RouteProcessor.

When calling the init() initialization method of ARouter, ARouter will call the init() method of LogisticsCenter. In this method, loadRouterMap() will load the routing table through the plug-in first, and then determine whether the current routing table loading method is a plug-in. If not, Then load the routing table from 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() + "]");
        }
    }

Load routing table from Dex

Load routing table from Dex

The process of loading the routing table through Dex is roughly as shown in the figure above. Next, let’s take a look at the loading of the routing table from Dex in the init() method of LogisticsCenter step by step:

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

If the debug mode is set or it is a new version, scan all Dex files to find all files starting with com.alibaba.android.arouter.routes, and then update to SharePreferences. Otherwise, read the cache directly from SharePreferences to reduce parsing time.

ClassUtils is used here to read the Dex file and read the routing table from the Dex file.

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

After saving the routing table to SharedPreferences, it will determine whether the class is IRouteRoot, IInterceptorGroup or IProviderGroup based on the suffix of the class name, and then instantiate it into different objects and call the loadInto method to load the contents of the class file into the index.

Load routing table via plugin

If you want to shorten the initialization time of ARouter, you can use the Gradle plug-in of ARouter. This plug-in can automatically load the routing table, so that ARouter does not need to read class information when initializing, thus shortening the initialization time.
Use the Gradle plug-in to implement automatic loading of routing tables
The working principle of the plug-in is that when the code is compiled, the plug-in will find the loadRouterMap method of the LogisticsCenter class, and then insert routing-related code into the method, so that the routing table will not be scanned from the dex file during initialization.

Routing table jump

Use ARouter.getInstance().build(“/test/activity”).navigation() to initiate a routing operation and jump, which will call the navigation() method of _ARouter.
There are two overloads of the navigation() method of _ARouter. One is used to create services, and the other is used for routing jumps.

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

The service process of creating a service is relatively simple. Create a Postcard postcard through LogisticsCenter.buildProvider, including the most basic group and path information, then set the Context for Postcard, and finally obtain the routing meta information through LogisticsCenter.completion and fill it into 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;
    }

The routing jump process is a bit more complicated, with roughly the following steps: 1. Preprocessing service, 2. Improving Postcard, 3. Improving failure degradation strategy, 4. Interceptor link, 5. Jumping by type.

Preprocessing services

The preprocessing service allows us to determine whether to process the jump independently based on the content of PostCard before ARouter jumps. If so, return false in onPretreatment().

To implement the pretreatment service, you only need to implement the PretreatmentService interface and add a @Route annotation with any path content.

If the preprocessing service is implemented and onPretreatment() returns false, the jump process will be interrupted and processing will not continue.

Improve Postcard

After calling the preprocessing service, _ARouter will use LogisticsCenter to load the routing table, which is the routing file generated by RouteProcessor.

When _ARouter is initialized, LogisticsCenter will also be initialized. In the initialization method of LogisticsCenter, the routing table created by RouteProcessor will be read and then placed in the corresponding index.

With the index, when _ARouter calls the completion() method of LogisticsCenter, it can use the index to obtain routing meta information from the routes of the Warehouse.

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

First, if LogisticsCenter cannot find the corresponding RouteMeta based on the index, it means that the routes have not been filled. At this time, LogisticsCenter will obtain the RouteMeta of the group, then fill the path under the group into routes, and then call completion() again. At this time, you can retrieve the information to fill in the Postcard.

With routeMeta, get the value from routeMeta, set it to the postcard attribute, parse the parameters in the Uri, set it to the postcard attribute, and set it to the Bundle for subsequent jumps.

After filling in the Postcard information, LogisticsCenter will perform different operations according to the Postcard type, and when the type is PROVIDER and FRAGMENT, set the postcard's greenChannel, which actually means that Provider and Fragment will skip the interceptor link. .

Improve failure downgrade strategy

When ARouter encounters an exception in the process of improving Postcard information, if NavigationCallback is passed in during navigation, onLost will be called back for this jump failure. If there is no callback, the global downgrade strategy will be used:

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

Similar to the preprocessing service, to implement the downgrade strategy, you only need to implement the DegradeService interface and add a @Route annotation with any path content.

interceptor link

When it is not greenChannel, it will enter the interceptor link, otherwise it will jump directly to type. When improving Postcard above, Provider and Fragment set greenChannel, which will skip the interceptor link. Of course, you can also call the greenChannel() method before the navigation() method to allow the jump to skip the interceptor link.

Interceptors can be used to process events during jumps, such as login checks. Interceptors will be executed between jumps, and multiple interceptors will be executed in order of priority.

To implement an interceptor, you only need to implement the IInterceptor interface and use the annotation @Interceptor(priority = 8, name = “xxxx”). Priority is the priority of the interceptor. Multiple interceptors will be executed in order of priority.

The onContinue() or onInterrupt() method needs to be called in the process method. At least one of these methods needs to be called, otherwise the routing process will not continue. onContinue() indicates that the processing is completed and the control is exchanged to ARouter. onInterrupt() indicates that an exception occurs and the routing process needs to be interrupted.

Jump by type

After the interceptor is processed, the _navigation() method will be called in navigation(), which is also the specific method for jumping. In this method, it will be judged based on Postcard's routing type RouteType and jump based on type.

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

When the RouteType is Activity, the process of starting the Activity is the same as the usual process of starting the Activity. Create the Intent, pass in the destination, pass in the parameter extras, set the flags, set the action, and finally call startActivity() in the main thread to start the target page. .

When the RouteType is Provider, that is, if it is a custom service, the Provider in Postcard is returned. The custom service in ARouter finds the discovery service through dependency injection.

When RouteType is Fragment, Broadcast or ContentProvider, the instance will be created using reflection through destination. If it is Fragment, ARouter will also set parameters for it and then return the instance.

Reference article:
https://juejin.cn/post/6885932290615509000

Guess you like

Origin blog.csdn.net/yuantian_shenhai/article/details/132299670