1. Routing scheme

Disadvantages of native routing schemes:

  • Explicit: Direct class dependencies, heavy coupling

  • Implicit: Centralized management of rules makes collaboration difficult

  • Manifest has poor scalability

  • The jump process is out of control

  • Failure cannot be downgraded

ARouter’s advantages:

  • By using annotations, automatic registration of mapping relationship and distributed routing management are realized

  • Annotations are processed during compilation and mapping files are generated without reflection and without impact to runtime performance

  • Mapping relationships are classified by group, managed at multiple levels, and initialized as required

  • Flexible demotion strategy that calls back the jump result on every jump, avoiding StartActivity() that would throw an operational exception if it failed

  • Custom interceptor, custom interception order, you can intercept routes, such as login judgment and buried point processing

  • Dependency injection support can be used as a standalone dependency injection framework to implement cross-module API calls

  • Supports direct parsing of the standard URL to jump, and automatically inject parameters into the target page

  • Obtaining Fragments

  • Support multi-module use, support componentized development

… .

With all these benefits, it’s time to get to know ARouter.

Second, ARouter frame

The figure above is a basic frame diagram of ARouter based on a basic route navigation process, involving the main process, which will be introduced in detail below.

3. Route management

1. Register

With annotations, classes or variables that use annotations are collected at compile time and handled by the Android Process Tool for unified management.

There are three annotations: @autowired, @interceptor, and @route.

@Route

Annotations to define

String path(); // Path URL String String group() default""; // Group name, default level 1 pathname; Once set, a jump must assign String name() default"undefined"; // The name of the path used to generate JavaDoc int extras() default integer.min_value; // Additional configuration switch information; Int priority() default-1; // The priority of the pathCopy the code

Implement the @route annotation

BlankFragment               @Route(path = "/test/fragment") 
Test1Activity               @Route(path = "/test/activity1")
Copy the code

This annotation is used to describe the path URL information in the route. Classes annotated with this annotation will be automatically added to the routing table.

@Autowired

Annotations to define

boolean required() default false;
String desc() default "No desc.";
Copy the code

Implement the @autowired annotation

@Autowired
int age = 10;
@Autowired
HelloService helloService;
Copy the code

This annotation is used to pass parameters when the page jumps. Variables that use this annotation flag in the target Class will automatically assign the value of the passed parameter after the inject() call when the page is opened by the route.

@Interceptor

Annotations to define

int priority(); // Priority of the interceptor String name() default"Default"; // The name of the interceptor used to generate JavaDocCopy the code

Implement the @interceptor annotation

Generally used in IInterceptor implementation class, is the route jump process interceptor, regardless of module, global application.

@Interceptor(priority = 7) public class Test1Interceptor implements IInterceptor { @Override public void process(final Postcard postcard, final InterceptorCallback callback) { ............ }}Copy the code

2. Collect

Mapping files are automatically generated during compilation, and arouter- Compiler implements annotation processors that aim to generate mapping files and auxiliary files.

AbstractProcessor Three types of annotation processors, all of which implement AbstractProcessor, have the following main functions:

  • The annotated class file is first scanned through the annotation handler

  • Sort by different kinds of source files

  • Generate mapping files according to a fixed naming format

This allows the mapping file to be loaded with a fixed package name during runtime initialization.

For a detailed explanation of the source code for annotation processing, see Ali routing framework -ARouter source code parsing Compiler.

Specific to ARouter? Root? For app, take a look at the contents of the class file generated by the annotation handler:

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

The group class file it manages is loaded into the collection by calling the loadInto() method for subsequent route look-up.

3. Load

The previous collection was obtained during compiler processing, so the load is run time. To avoid memory and performance losses, ARouter proposed a “group managed, load on demand” approach. In the previous compilation process, corresponding mapping files have been generated for different types.

Taking the official demo as an example, an APP module has a Root node, which manages each Group and has multiple interfaces under each Group. In addition, the APP module also has the Interceptor node and the provider node.

The Interceptor node corresponds to the custom Interceptor, and the provider node corresponds to IOC to achieve cross-module API calls.

ARouter will load all root nodes at once and not any Group nodes at all during initialization, greatly reducing the number of nodes loaded during initialization. When a page in a group is accessed for the first time, all pages in the group are loaded.

The initial load

ARouter is actually a proxy class, and all of its functions are implemented by _ARouter. Both are singletons.

Public static void init(Application Application) {// The static function is initialized, independent of the objectif(! hasInit) { logger = _ARouter.logger; // The global static scalar _ARouter. Logger. info(consts.tag,"ARouter init start."); HasInit = _ARouter. Init (application); // hand over _ARouter to initializeif (hasInit) {
            _ARouter.afterInit();
        }

        _ARouter.logger.info(Consts.TAG, "ARouter init over."); // Print ARouter initialization log}}Copy the code

Take a look at the initialization method for _ARouter

protected static synchronized boolean init(Application application) { mContext = application; // Application context logisticScenter. init(mContext, executor); // Pass it to the logic center for initialization, and pass it to the line city object Logger.info (consts.tag,"ARouter init success!"); // Prints logs hasInit =true; // indicate whether initialization is complete // It's not a good idea. // if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) { // application.registerActivityLifecycleCallbacks(new AutowiredLifecycleCallback()); // } return true; }Copy the code

Move on to the initialization method of LogisticsCenter

public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException { mContext = context; // Statically hold Application context executor = tpe; / / static hold line city try {/ / These class was generate by arouter - compiler. / / by specifying the package name com. Alibaba. Android. Arouter. Routes, Find all the compile-time routes of directory under the name of the class (does not contain class loading) a List < String > classFileNames = ClassUtils. GetFileNameByPackageName (mContext, ROUTE_ROOT_PAKCAGE);for(String className: classFileNames) {/ / category list com. Alibaba. Android. Arouter. Routes. Arouter $\ \$Root
                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)) {/ / module list of interceptors com. Alibaba. Android. Arouter. Routes. Arouter $\ \$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)) {/ / the action of the IOC routing list com. Alibaba. Android. Arouter. Routes. Arouter $\ \$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() + "]"); }}Copy the code

Through the above code, the method of “group management, load on demand” is realized, and the nodes managed in the class generated by the corresponding three annotation processors are loaded into the routing set.

The memory Warehouse caches the list list of global application groups, IOC action route list, module interceptor list and three map objects.

class Warehouse { // Cache route and metas static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>(); Static Map<String, RouteMeta> routes = new HashMap<>(); static Map<String, RouteMeta> routes = new HashMap<>(); // Cache provider static Map<Class, IProvider> providers = new HashMap<>(); Static Map<String, RouteMeta> providersIndex = new HashMap<>(); // Cache interceptor // The list of interceptors in a module contains the mapping between interceptors in a module and their priorities static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]"); static List<IInterceptor> interceptors = new ArrayList<>(); // Sorted interceptor instance object}Copy the code

4. Route search

ARouter.getInstance().build("/test/activity2").navigation(); </pre>Copy the code

Using the above example, take a look at the ARouter route lookup. First look at the build process

1.build()

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

protected Postcard build(String path) {
        if (TextUtils.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); }returnbuild(path, extractGroup(path)); }}Copy the code

It uses the build() of the proxy class _ARouter and builds and returns the PostCard object. A Postcard object corresponds to a routing request that applies to the entire routing process.

This code consists of two parts:

  • Use IOC byType() to find the implementation class of the PathReplaceService. Class interface, which implements runtime dynamic routing.
  • Continue with the route navigation

Let’s look at the PathReplaceService. Class interface:

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);
}
Copy the code

It mainly contains two methods, forString() and forUri, to preprocess the path and achieve “dynamic route modification at runtime”.

Next, route navigation continues through Build (Path, extractGroup(path)), where the extractGroup() gets the default packet information from the path.

The Build () method then returns a Postcard object and passes in the corresponding path and grouping information.

PathReplaceService pService = arouter.getInstance ().navigation(PathReplaceService. Class); The navigation() method in Class _ARouter actually calls the navigation(Class<? Extends T> service) method.

2.navigation(Class<? extends T> service)

protected <T> T navigation(Class<? extends T> service) {
    try {
        Postcard postcard = LogisticsCenter.buildProvider(service.getName());

        // Compatible 1.0.5 compiler sdk.
        if (null == postcard) { // No service, or this service in old version.
            postcard = LogisticsCenter.buildProvider(service.getSimpleName());
        }

        LogisticsCenter.completion(postcard);
        return (T) postcard.getProvider();
    } catch (NoRouteFoundException ex) {
        logger.warning(Consts.TAG, ex.getMessage());
        returnnull; }}Copy the code
  • First LogisticsCenter. BuildProvider (service. The getName ()) according to the Warehouse saves providersIndex information search and build returns an object of it

  • Then execute LogisticsCenter.com pletion (it), the method based on Warehouse saves the routes in the routing information of perfecting it object, the method below will come out, and then concrete is introduced

Return to ARouter.getInstance().build(“/test/ Activity2 “).navigation(). After returning the PostCard object, the PostCard will call the corresponding navigation() method.

3.navigation()

Observe this method in PostCard

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

    public void navigation(Activity mContext, int requestCode) {
        navigation(mContext, requestCode, null);
    }

    public void navigation(Activity mContext, int requestCode, NavigationCallback callback) {
        ARouter.getInstance().navigation(mContext, this, requestCode, callback);
    }
Copy the code

We end up calling the navigation() method in ARouter, where we’re actually calling the navigation() method in _ARouter.

This method contains calls to look up callbacks, degrade handling, and interceptors handling specific routing operations.

protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final 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); // Failed to trigger route lookup}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);
                }
            }

            returnnull; } // The route meta information was found, triggering the route lookup callbackif(null ! = callback) { callback.onFound(postcard); } // Green channel verification requires interceptionif(! postcard.isGreenChannel()) { // It must be runinAsync thread, maybe interceptor cost too mush time made ANR. And interception function in the asynchronous thread interceptorService. DoInterceptions (it, newInterceptorCallback() {
                /**
                 * Continue process
                 *
                 * @param postcard route meta
                 */
                @Override
                public void onContinue(Postcard postcard) {
                    _navigation(context, 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(context, postcard, requestCode, callback);
        }

        return null;
    }
Copy the code

The most important of these two methods is LogisticsCenter.com pletion () and _navigation (), detailed below.

public synchronized static void completion(Postcard postcard) {
    if (null == postcard) {
        throw new NoRouteFoundException(TAG + "No postcard!"); RouteMeta RouteMeta = Warehouse. RouteMeta. Get (postcard.getPath());if (null == routeMeta) {    // Maybe its does't exist, or didn'T load. // May not load group manifest path, from the group manifest list to the corresponding group 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{// Add the group list to the memory store and remove the group 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); // Trigger perfect logic again}}else{ postcard.setDestination(routeMeta.getDestination()); // Destination class postcard.settype (routemeta.getType ()); // Routing class postcard.setPriority(routemeta.getPriority ()); // The routing priority is postcard.setextra (routemeta.getextra ()); // Additional configuration switch information Uri rawUri = postcard.geturi ();if(null ! = rawUri) { // Try toset 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) { throw new HandlerException("Init provider failed! " + e.getMessage()); } } postcard.setProvider(instance); postcard.greenChannel(); // Provider should skip all of interceptors break; case FRAGMENT: postcard.greenChannel(); // Fragment needn't interceptors
            default:
                break; }}}Copy the code

This approach is to perfect PostCard to realize a route navigation.

Here’s another method, _navigation().

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()) {
            caseACTIVITY:// If it is Acitvity, // 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); } // Navigationin 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 ((-1 != postcard.getEnterAnim() && -1 != postcard.getExitAnim()) && currentContext instanceof Activity) {    // Old version.
                            ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
                        }

                        if(null ! = callback) { // Navigation over. callback.onArrival(postcard); }}});break;
            casePROVIDER:// If it is IOC, return the target object instancereturn postcard.getProvider();
            case BOARDCAST:
            case CONTENT_PROVIDER:
            caseFRAGMENT:// If it is FRAGMENT, return the instance and populate the bundle 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;
    }
Copy the code

At this point we have completed a route jump.